1. 程式人生 > >Java圖形界面實戰案例——實現打字母遊戲

Java圖形界面實戰案例——實現打字母遊戲

建議 popu 會有 做的 clas 良好的 rri asc 不一定

實現打字母的遊戲

 這次這個案例能夠說是頭幾次所講的內容的一個技術匯總,主要是 運用了幾大塊的知識。我們先來定義一下案例的背景:在一個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.,假設能夠生成9797+26的隨機數。

我們能夠通過強制類型轉換獲得隨機字母,怎樣生成9797+26之間的隨機數呢?要知道Math.random()產生的01之間的隨機數。我們須要轉換。我想有人知道怎麽做了——(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

}

}

“`
這應該是眼下博主寫博客以來寫的最多的一次,當然你們看的學的認為非常辛苦,博主自己寫的也非常辛苦。好好的而消化,多練幾遍,要輕松的理解這些東西並不是易事,良好的邏輯能力須要培養。親自寫代碼,自己改錯,非常鍛煉編程能力,堅持不懈的努力,終究會成功,這次就到這裏了。下期再見了,下期更完圖形界面應該是告一段落了,要真的開始寫項目了。

Java圖形界面實戰案例——實現打字母遊戲