Java图形界面实战案例——实现打字母游戏
实现打字母的游戏
这次这个案例能够说是头几次所讲的内容的一个技术汇总,主要是 运用了几大块的知识。我们先来定义一下案例的背景:在一个300*400的窗口上。有10个随机产生的字母下落,在键盘上敲击字母,若是敲对了就消掉。初始化的成绩为1000分,每次敲对一个字母就加上10分,假设在字母落到了屏幕的下方还没有敲对的话则判定为失败,就扣除100分。
我们还是老样子。先来进行步骤的划分
1.做满天星
2.把星星改成随机的10个字母
3.让字母落下,假设字母落出了屏幕就生成新的字母,并从屏幕的上方又一次出现
4.接收键盘输入并消除匹配的字母
5.实现积分程序
6.临时先不说吧。先把第六步完毕了。自然而然就能看到这一步须要什么了
## **1.做满天星** ##
如今做的星星,有些条件有所改变,须要一个300*400的窗口,须要10颗星星。如今大家先自己写一遍代码,随后再看看我的代码
import java.awt.*;
public class MyChar {
public static void main(String[] args) {
// TODO Auto-generated method stub
Frame w = new Frame();
w.setSize(300, 400);
MyPanel mp = new MyPanel();
w.add(mp);
w.show();
}
}
class MyPanel extends Panel
{
public void paint(Graphics g)
{
for(int i=0;i<10;i++)
{
g.drawString("*", (int)(Math.random()*300), (int)(Math.random()*300));
}
}
}
这里有几个地方须要注意一下。窗口的背景不再是黑色的,那么字体的颜色也不用再设置成白色的了,黑色的部分可能是个意外,为什么在纵坐标上我们不乘上400?这个问题肯定非常多人都会想到,这里我来告诉各位,你想想我们的终于目的是让字母下落。等着用户看到键盘上相应的键。假设乘上400。就有可能有些字母一出来就在屏幕的最下方,这貌似并不符合我们设计的游戏的逻辑。
其实,我们前面的经验表明,假设乘上400。有些字母一出现可能会由于数值太大而看不见。
2.把星星改成随机的10个字母
第二步是生成10个随机字母,我们索性将这些在这一步将坐标放到数组变量里,以便准备给下落动作使用。 问题是,随机的字母怎样产生?我们从未讲过数据类型,我认为一上来就介绍8中基本数据类型毫无意义,假设没实用到就会非常快的忘记,回想一下我们已经使用的数据类型都有什么。
毫无疑问。第一个想到的就是int,整数类型(简称整型),这是眼下为止我们使用最多的数据类型。既然有整型那么就一定有小数型,在非常多语言里都有两张表达小护士的数据类型,一个float。另一个是double。看到单词我们就能知道个大概,double是两个的意思。这是由计算机的特点决定的。double用的两倍的空间来存储数据,所以能够想象的到double能够表示很多其它的数字,详细到小数来看,结果就是更加的精确。可是也就更加的浪费资源。其实,整型也有short和long之分,这个也非常好理解。从字面上去解释。short就是短的意思,相应的就是短整型,long是长的意思。相应的就是长整型,两者的差别就是表示的整型数据的长度不同,short int所表示的数据范围是 —32768to+32767。long int 表示的是-2,147,483,648 to 2,147,483,647 这样一来就非常直观了嘛,long所表示的范围比short大的多得多 言归正传,另外,我们还用到了boolean类型,你说我没讲到boolean,其实我们在if或者循环里用做条件的表达式结果,boolean简单。里面就两个可能的值:true or false。 另一个是byte类型,由于计算机是用0和1最为主要的运算单位的,可是表达的东西太少了。所以人们将0和1组成一个组来存放稍大一点的数。8个二进制数组合起来就是一个byte,这成为编程中最经常使用的基本数值单位。 介绍到我们关注的一个数据类型,即char。
char是字符类型。就是放字符的。字符用单引號引起来。比方'a',这个我们没实用过。之前用双引號"*"不算。由于这是字符串String。这个String不放在数据类型里讨论。String是对象不是基本数据类型,这句话意味着这里讨论的数据类型不是对象,你可能认为不是就不是呗,可是从学术的角度或者一直面向对象的程序猿看来,就难以适应了,有人抨击Java不是纯粹的面向对象语言,指的就是8中基本数据类型不是对象,从程序猿的角度来看,不是对象意味着点点不会出现方法。有的时候这是个问题,为什么Java的设计者不能再纯粹一点呢?这是有原因的,设计者们看的非常深非常远,从效率的角度考虑,基本数据类型的操作比对象更有效率,只是为了照应面向对象程序猿,Java提供了基本数据类型的封装类,帮助我们在须要的时候将这个数变成对象。 为什么要搞这么多的数据类型呢?一是为了更好的使用内存,声明变量的时候假设指定了这个变量是boolean类型的。程序就不用为这个变量准备int类型那么大的空间了。而是不同数据类型之间的运算规则又是不同的。我们来看以下两个程序的运算结果。
public class MyTest {
public static void main(String[]args){
char a='1';
char b='2';
System.out.println(a+b);
}
}
执行一下你会发现得到了一个意想不到的数字99,再看看以下这段类似的代码
public class MyTest {
public static void main(String[]args){
char a=1;
char b=2;
System.out.println(a+b);
}
}
这段代码我想就算不同意也能够猜出结果是3了,为什么会有这种差异,留给各位以后去探索吧 既然有不同的数据类型,就会遇到不同数据类型之间转换的问题,其实我们之前已经使用过这种转换。我们用(int)将随机数产生的小数转换成整数。这叫做强制类型转换。
其实也有隐含的转换。比方我将小数硬放到一个整形变量里,自然放不了。系统会自己主动帮我转换成整型再放。我们就马上遇到一结果问题,既然类型不同,转换之后会成为什么呢?这里面一定会有比較合理的规则。比方,我们之前将小数转换成证书会谁去掉小数部分。我不须要告诉你全部的转换规则。在知道了各种数据类型,以及怎样转换的情况下,你全然能够自己试一下。大多数情况下,一看结果就知道规则是什么了。 兴许还会在须要的时候继续讨论数据类型这个话题,如今再多说想必也是吸收不进去了。
我们来尝试一下char和int的相互转换
public class MyTest {
public static void main(String[]args){
char a='a';
System.out.println((int)a);
}
}
```
执行结果是97,这是'a'这个字符在计算机里的编码,为了统一,这种编码已经成了统一,后来成了国际标准也就是后来的ASCII编码。那么再来一段代码
public class MyTest {
public static void main(String[]args){
char a=’a’;
System.out.println((char)a);
}
}
结果非常明显吧。一看就能猜出来是a,结果也确实是a,还记得我们的任务是什么?这个阶段的任务就是随机生成字母。也就是说,生成从a到z的随机字母,我们看到a相应的ASCII码是97。那么相应的z就是97+26.,假设能够生成97到97+26的随机数。我们能够通过强制类型转换获得随机字母,怎样生成97到97+26之间的随机数呢?要知道Math.random()产生的0到1之间的随机数。我们须要转换。我想有人知道怎么做了——(char)(Math.random()*26+97)。
其实,这边另一个问题待解决。
g.drawString()方法须要三个參数,这是我们知道的。第一个參数是一个字符串(String),第二个和第三个各自是X和Y坐标,是整数(int),我们的问题是,得到了随机字符,可是那是字符不是字符串,字符没法满足这种方法的需求。我们须要将字符强行转换为字符串。依据此前的经验。也许有人会用这种办法啦转换——(string)c,这里假设c是字符变量,这是不行的。这种做法仅仅是适合基于数据类型的转换,另一种情况也是用这种转换方式。只是如今不讲,兴许会说明,我这儿有一个省事的方法。在须要转换的地方写" "+c。由于" "是一个全然空的字符串,要知道在大多数的情况下。一个变量加到字符串上,都会被自己主动转换成字符串,不管是基本数据类型还是对象。唯一的问题就是这样做的办法好像效率太低了,计算机须要更加长的时间去执行这个操作,字符串想叫的做法无疑会让这个程序的执行效率变得非常低,所以假设是正式的编程,不建议这样做类型转换。
正确的做法是把c变成对象——new Character(c),全部的对象都有to String()方法。我们用这种方法来转换得到字符串。
这样就遗留了一个问题,为什么全部的对象都要有to String()方法呢?不急,后面我会说道的。
充分理解上面的讨论之后,你能够尝试一下完毕这一步的代码。让300*400的窗口上有10个随机的字母。
import java.awt.*;
public class MyChar {
public static void main(String[] args) {
// TODO Auto-generated method stub
Frame w = new Frame();
w.setSize(300, 400);
MyPanel mp = new MyPanel();
w.add(mp);
w.show();
}
}
class MyPanel extends Panel
{
int x[]=new int[10];
int y[]=new int[10];
char c[] = new char[10];
MyPanel()
{
for(int i=0;i<10;i++)
{
x[i]=(int)(Math.random()*300);
y[i]=(int)(Math.random()*300);
c[i]=(char)(Math.random()*26+97)
}
}
public void paint(Graphics g)
{
for(int i=0;i<10;i++)
{
g.drawString(new Character(c[i]).toString(),x[i],y[i]);
}
}
}
## 3.让字母落下,假设字母落出了屏幕就生成新的字母,并从屏幕的上方又一次出现 ##
假设这一步没有问题了,我们来完毕第三步,及时让字母落下。假设字母落出屏幕就生成新的字母,并从屏幕的上方又一次的出现。
落下的代码不难,差点儿和下雪的代码一样。不同的是。下雪的代码在超出屏幕的处理上是让Y值回到0。为了有更好的效果,在这个案例上。我们还要让X获得一个新的随机数。另外这个字符也要产生一个字符。
同样建议你先自己试着完毕任务。然后再看代码,假设没有思路,还是回头看看前面。看看还有哪里没有彻底的掌握。
import java.awt.*;
public class MyChar {
public static void main(String[] args) {
// TODO Auto-generated method stub
Frame w = new Frame();
w.setSize(300, 400);
MyPanel mp = new MyPanel();
w.add(mp);
Thread t = new Thread(mp);
t.start();
w.show();
}
}
class MyPanel extends Panel implements Runnable
{
int x[]=new int[10];
int y[]=new int[10];
char c[] = new char[10];
MyPanel()
{
for(int i=0;i<10;i++)
{
x[i]=(int)(Math.random()*300);
y[i]=(int)(Math.random()*300);
c[i]=(char)(Math.random()*26+97);
}
}
public void paint(Graphics g)
{
for(int i=0;i<10;i++)
{
g.drawString(new Character(c[i]).toString(),x[i],y[i]);
}
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true)
{
for(int i=0;i<10;i++)
{
y[i]++;
if(y[i]>400)
{
y[i]=0;
x[i]=(int)(Math.random()*300);
y[i]=(int)(Math.random()*26+97);
}
}
try
{
Thread.sleep(30);
}catch(Exception e){ }
repaint();
}
}
}
## 4.接收键盘输入并消除匹配的字母 ##
前三部基本都已经完毕,如今做的是第4步,接收用户键盘的输入,假设匹配上就消除这个字符。其实。我们并不真正的去消除字符,而是让这个字符又一次从屏幕的上面再出来,这将是个新生成的字符。
接收用户输入我想不是问题了,那么拿到用户的输入以后怎么知道屏幕上有没有的这个字符呢?字符都存在在那个数组里,看来我们得到数组里去找有没有。假设有就处理。
还是自己先去尝试一下再看一下代码。只是这一步我不在提供全部的代码,仅仅是部分。我仅仅是认为大家已经有能力看得懂了,所以不再和之前一样提供全部的代码了,以下仅仅是列出了事件处理的程序
public void keyPressed(KeyEvent arg0)
{
//将用户输入的字符存入KeyC中
char keyC=arg0.getKeyChar();
//扫描整个数组,看看有没有匹配的字符
for(int i=0;i<10;i++)
{
if(keyC==c[i])
{
//找到了
y[i]=0;
x[i]=(int)(Math.random()*300);
c[i]=(char)(Math.random()*26+97);
break; //防止上同一时候有多个同样的字符被一次性的消除掉
}
}
}
break这里做一个说明。break的作用是跳出这个循环,在这里加上break的目的是假设找到了那么这次就不找了。
增加了上述的代码,别忘了要实现接口和注冊事件。假设成功了,那么恭喜你,打字母的游戏已经出来了。
只是你在调试的过程中也已经意识到了这个问题,就是假设有多个同样的字母同一时候出现。那么被消除掉的那个字母就不一定是最先面的那一个了。这貌似也是不符合这个游戏的逻辑,由于消除掉的顺序是在数组里。字母在数组里是依照什么样的顺序排列的。那么消除就是依照这种顺序去消除,这就和y坐标没有关系了,大家也应该反应过来了这也就是我在前面留下的第六个问题,就是消除掉最以下的字母
## 5.实现积分程序 ##
如今还是先来看第五步,计入积分。
我们似乎须要一个变量来记录得分。那么就依据案例的要求,就假设初始值就是1000,假设字母在落到最以下的时候还没有被消除掉那么就要扣除10分,假设用户输入的字符屏幕上没有那就扣除100分。自己想想写写看该怎么去完毕
class MyPanel extends Panel implements Runnable
{
int x[]=new int[10];
int y[]=new int[10];
char c[] = new char[10];
int sorce=1000;
public void paint(Graphics g)
{
for(int i=0;i<10;i++)
{
g.drawString(new Character(c[i]).toString(),x[i],y[i]);
}
//显示成绩
g.setColor(Color.RED);
g.drawString(“你的成绩是:”+sorce, 5, 10);
}
}
以下的代码是实现扣分功能的代码,你看到"-="运算符了吧。类似的还有"+= *= /+"之类的,这里是score = score-1000的简单写法
public void run() {
// TODO Auto-generated method stub
while(true)
{
for(int i=0;i<10;i++)
{
y[i]++;
if(y[i]>400)
{
y[i]=0;
x[i]=(int)(Math.random()*300);
y[i]=(int)(Math.random()*26+97);
sorce-=100;
}
}
}
}
最后我们来看看怎样实现敲对了加分的功能,敲错了减分的功能
public void keyPressed(KeyEvent arg0)
{
//将用户输入的字符存入KeyC中
char keyC=arg0.getKeyChar();
//扫描整个数组。看看有没有匹配的字符
for(int i=0;i<10;i++)
{
if(keyC==c[i])
{
//找到了
y[i]=0;
x[i]=(int)(Math.random()*300);
c[i]=(char)(Math.random()*26+97);
break; //防止上同一时候有多个同样的字符被一次性的消除掉
}
else
{
score-=100;
}
}
}
你认为这种代码能够吗?肯定不能够啊。在我们看来。假设数组里的10个字符中有一个'a'。你却敲'a'这个字符,你希望的道德结果是加10分。可是最坏的结果是循环了10次,最后一个匹配上了。匹配上了这次尽管说是加了10分,可是前面没有匹配上的9次怎么办?扣900分吗?这样不符合逻辑啊。这是一个非常经典的问题,语法上没有错误可是逻辑上存在问题,因此我们就须要一个标识,假设没有匹配上,标识的值不变,一旦匹配上了,标识改变。等到循环完了,看一下标识,有非常多情况下我们都会用到这个小算法
详细的实现来看,boolean变量貌似是最适合的。由于boolean变量仅仅有两种状态,我们先设定boolean变量的初值是false(假的),假设if匹配上课了,就把他变成true。
boolean mark =false;
for(int i=0;i<10;i++)
{
if(keyC==c[i])
{
mark=true;
break;
}
}
我们来分析一下上面的代码,開始mark的值是false,然后循环,假设这10次循环都没有if成功,那么mark的值就会一直不改变,还是false,一旦if匹配成功,mark的值就变成了true,我们能够在循环结束后,更具mark的值来推断有没有匹配上的内容。
详细到了案例的实现代码
public void keyPressed(KeyEvent arg0)
{
//将用户输入的字符存入KeyC中
char keyC=arg0.getKeyChar();
//扫描整个数组,看看有没有匹配的字符
boolean mark =false;
for(int i=0;i<10;i++)
{
if(keyC==c[i])
{
//找到了
y[i]=0;
x[i]=(int)(Math.random()*300);
c[i]=(char)(Math.random()*26+97);
mark=true;
break; //防止上同一时候有多个同样的字符被一次性的消除掉
}
}
if(mark)
{
score+=100;
}
else
{
score+=10;
}
}
这次将的东西我也承认确实是有那么一点点的多,我建议大家还是先停一下,把前面的代码再自己多写写,多多理解理解再继续下去。由于最后一步的逻辑有点棘手,不是非常好理解
## 6.怎样消除最以下的字符 ##
如今開始完毕第6步,我们的目的是找到多个匹配成功的字符的最以下一个,而且清除掉。当中清除不难。找到匹配的字符也不难,焦点在最以下一个字符上,怎样推断这是最先面的自字符?这非常easy,就是Y坐标最大的。假设不是计算机来完毕这件事情,让人来做,那就是找到所以的匹配字符,摆在那里,看看谁的Y最大。计算机没有一次性比較多个值的能力。每次仅仅能比較出一个值,我们的思路是,先找到一个以后,然后把Y值记录下来,再找到一个,再推断,假设新的Y值大于原来的,那么就用这个Y值替换掉原来的Y值,否则什么都不做,老的字符在以下,找一圈以后,记录下的Y值是最大的了。我们能够理解这个逻辑,我拿到了一个字符,记住了它的Y坐标,在拿到一个看看是不是大于刚才那个。假设是就将旧的Y给丢了,假设不是就把如今这个新的Y个妞丢了。最后我手里就一个字符,这个字符就在最以下,它相应的数据位置就是我们要清除的字符标号。我们将数组位置叫做数组下标,也就是说每次我们还得记录保留下来的数组下标。
另一个小问题,即便有非常多匹配的字符,推断的逻辑是同样的。我们全然能够将这个推断放在循环里。让这个逻辑周而复始的去做。可是第一个字符的匹配逻辑不同,它不须要推断,见到存下来就好,能不能将第一个字符的逻辑和其它逻辑统一呢?假设能的话,我们就能够节省下一段代码了,我想到了一个办法,就存放在最以下Y坐标的变量初始值设置成绝对不可能小的数,这样第一个字符就去推断。当然,由于老的值一定小,所以第一个字符的值也会被存下来。
public void keyPressed(KeyEvent arg0)
{
//将用户输入的字符存入KeyC中
char keyC=arg0.getKeyChar();
//扫描整个数组。看看有没有匹配的字符
int nowY=-1;
int nowIndex=-1;
for(int i=0;i<10;i++)
{
if(keyC==c[i])
{
if(y[i]>nowY)
{
nowY=y[i];
nowIndex=i;
}
}
}
if(nowIndex!=-1)
{
y[nowIndex]=0;
x[nowIndex]=(int)(Math,random()*300);
c[nowIndex]=(char)(Math.random()*26+97);
score+=10;
}
else
{
score-=10;
}
}
我们来看看上面这段代码,nowY存放着最以下符合条件的Y坐标,nowIndex存放着最以下符合条件的数组的下标,boolean变量似乎也不须要了,由于全然能够依据nowIndex来推断有没有找到。break也不须要了。由于我们找到一个匹配的字符不算完,还要继续寻找,最后我放上全部的代码
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class MyChar {
public static void main(String[] args) {
// TODO Auto-generated method stub
Frame w = new Frame();
w.setSize(300, 400);
MyPanel mp = new MyPanel();
w.add(mp);
Thread t = new Thread(mp);
t.start();
w.addKeyListener(mp);
mp.addKeyListener(mp);
w.show();
}
}
class MyPanel extends Panel implements Runnable,KeyListener
{
int x[]=new int[10];
int y[]=new int[10];
char c[] = new char[10];
int score=1000;
MyPanel()
{
for(int i=0;i<10;i++)
{
x[i]=(int)(Math.random()*300);
y[i]=(int)(Math.random()*300);
c[i]=(char)(Math.random()*26+97);
}
}
public void paint(Graphics g)
{
for(int i=0;i<10;i++)
{
g.drawString(new Character(c[i]).toString(),x[i],y[i]);
}
//显示成绩
g.setColor(Color.RED);
g.drawString(“你的成绩是:”+score, 5, 10);
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true)
{
for(int i=0;i<10;i++)
{
y[i]++;
if(y[i]>400)
{
y[i]=0;
x[i]=(int)(Math.random()*300);
y[i]=(int)(Math.random()*26+97);
score-=100;
}
}
try
{
Thread.sleep(30);
}catch(Exception e){ }
repaint();
}
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
//将用户输入的字符存入KeyC中
char keyC=e.getKeyChar();
//扫描整个数组,看看有没有匹配的字符
int nowY=-1;
int nowIndex=-1;
for(int i=0;i<10;i++)
{
if(keyC==c[i])
{
if(y[i]>nowY)
{
nowY=y[i];
nowIndex=i;
}
}
}
if(nowIndex!=-1)
{
y[nowIndex]=0;
x[nowIndex]=(int)(Math.random()*300);
c[nowIndex]=(char)(Math.random()*26+97);
score+=10;
}
else
{
score-=10;
}
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
}
“`
这应该是眼下博主写博客以来写的最多的一次,当然你们看的学的认为非常辛苦,博主自己写的也非常辛苦。好好的而消化,多练几遍,要轻松的理解这些东西并不是易事,良好的逻辑能力须要培养。亲自写代码,自己改错,非常锻炼编程能力,坚持不懈的努力,终究会成功,这次就到这里了。下期再见了,下期更完图形界面应该是告一段落了,要真的開始写项目了。