Java » Java ME

Piszemy węża na komórkę

  • 2007-12-24 11:39
  • 6 komentarzy
  • 1643 odsłony
  • Oceń ten tekst jako pierwszy
Pracę tą napisałem kilka miesiecy temu na informatyke w szkole. Podstawowym wymogiem było duzo komenatrzy :P. Pomyślałem że komuś mogłoby się to przydać... Gra nie działa zbyt dobrze (zwalnia przy dłuższym wężu, nie ma zapamiętywania rekordów...)- mozna by było ją usprawnić np. zastępując Vector własną listą z wykorzystaniem iteratora ( java ME nie posiada iteratora dla list!), ale chyba nie o to chodzi...

P.S.  Artykuł przekopiowałem z worda- nie chce mi się go porządnie rozmieszczać :)

Gra "Wąż"- jest prosta- sterując wężem musimy zjeść jak najwięcej
jedzenia. Kiedy wąż dotknie samego siebie lub wyjdzie poza plansze- przegrywamy.

Zaczynając pisanie tej gry ( a także później podczas samego pisania) poczyniłem kilka podstawowych założeń.

  1. Gra bedzie przystosowana do telefonów Siemens.
  2. Obecne musi być menu, gdzie użytkownik będzie miał możliwość ustawienia szybkości.
  3. Sterowanie wężem powinno odbywać się w dwóch systemach - pierwszy (prosty) za pomocą kursorów lub klawiszy 2,4,6,8 i drugi (złożony) za pomocą klawiszy narożnikowych (1,3,7,9). Ten drugi system opiera się na tym że wąż w danej chwili ma możliwość wyboru tylko dwóch kierunków. Każdy z narożnikowych klawiszy odpowiedzialny jest za 2 kierunki (np. 1 za górę i lewo) Przykładowo kiedy wąż idzie w górę może skręcić w prawo ( 3,9 ) lub w lewo (1,7). Najlepiej zobaczyć jak to działa w praktyce.
  4. Gra powinna być czarno-biała, żeby działała na telefonach bez kolorowego wyświetlacza.
  5. Wąż porusza się o 3 piksele na raz i składa się z kwadracików 3x3 piksele- powód tego jest prosty- przy mniejszej ilości pikseli przesunięcia gra działa za wolno.
  6. Jedzenie będzie mogło pojawiać się pod wężem - powód - nie trzeba sprawdzać ułożenie węża = większa płynność gry.
  7. Na głównym ekranie gry będą wyświetlane punkty.

No i zaczynamy!
Na początku oczywiście trzeba zaimportować potrzebne pakiety.
/* Te dwa pakiety importujemy praktycznie zawsze, kiedy piszemy Midlet pierwszy
służy do budowy interfejsu, a drugi  (najważniejszy) zawiera jedną klasę o
nazwie MIDlet, która definiuje interakcje między aplikacją i środowiskiem
uruchomieniowym.
*/
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
/*
W tym pakiecie znajdują się m.in. klasy Vector i Random, które zostały użyte w
dalszej części programu.
*/
import java.util.*;
 
/*
Oto nasza główna klasa, dziedziczy ona oczywiście z klasy MIDlet, implementujemy
także klasę nasłuchującą zdarzenia.
*/
    public class Waz extends MIDlet implements CommandListener  {
        Wezyk glowna; // Obiekt wężyka - to jest nasz wąż
        int speed; // Szybkość gry
        /* Obiekt Command odpowiedzialny jest za to, że przy ustawianiu
        szybkości węża możemy cofnąć się do głównego menu. */
        private Command backCommand = new Command("Wróć", Command.BACK, 1);
        /* Obiekt odpowiedzialny za to co jest aktualnie widziane przez
        użytkownika na ekranie. */
        private Display display;
        /* Menu będące listą elementów; List.IMPLICIT ustawia listę jako
        taką z której wybranym elementem jest element zaznaczony. */
        private List list = new List("Menu", List.IMPLICIT);
        /* Obiekt Form, będący odpowiedzialny za opcje w których ustawiamy
        szybkość węża */
        Form opcje = new Form("opcje");
        /* Jest to puste pole tekstowe gdzie można wpisać jedną cyfrę 0-9
        będącą szybkością węża */
        TextField pole = new TextField("Szybkość","",1,TextField.NUMERIC);
 
        public Waz() { // Konstruktor klasy Waz
            display = Display.getDisplay(this); // potrzebne żeby cokolwiek wyświetlało
            // dodawanie elementów do menu; null oznacza że element jest bez obrazka
            list.append("Start", null); 
            list.append("Szybkość", null);
            list.append("Wyjście", null);
            // ustawienie nasłuchiwania zdarzeń dla menu
            list.setCommandListener(this); 
            // dodanie pola tekstowego do opcji gdzie ustawiamy szybkość węża
            opcje.append(pole);
            // ustawienie nasłuchiwania zdarzeń dla opcji
            opcje.setCommandListener(this); 
            // dodanie od opcji możliwości cofnięcia się do głównego menu
            opcje.addCommand(backCommand); 
    }
 
        /* Metody startApp(), pauseApp(), destroyApp(boolean unconditional)
        muszą być obecne w midlecie choćby miałyby być puste- składają się one na
        tzw. cykl życia midletu. Oczywiście z pustą metodą startApp
        ( startuje ona całą aplikacje) trudno jest cokolwiek zdziałać, ale pause i
        destroy mogą być jak najbardziej puste. */
        public void startApp() {
            display.setCurrent(list); // przy starcie midletu widoczne jest menu
        }
        public void pauseApp() {}
        public void destroyApp(boolean unconditional) {}
 
        /* Metoda odpowiedzialna za nasłuchiwanie zdarzeń. */
        public void commandAction(Command c, Displayable s) { 
            if (c == List.SELECT_COMMAND) { // jeżeli został wybrany któryś z elementów menu
                /* Jeżeli jest to Start to tworzony jest nowy obiekt wezyka
                (dzięki temu nie trzeba wyłączać całej aplikacji żeby
                zagrać jeszcze raz) oraz jest on wyświetlany. */
                if (list.getString(list.getSelectedIndex()) == "Start") { 
                    glowna=new Wezyk();
                    display.setCurrent(glowna);
                }
                /* Jeżeli jest to Szybkość to wyświetlana jest Form
                opcje (opcje gdzie ustawia się szybkość). */
                if (list.getString(list.getSelectedIndex()) == "Szybkość"){ 
                display.setCurrent(opcje);
                }
                /* Jeżeli jest to wyjście to program się zamyka. */
                if (list.getString(list.getSelectedIndex()) == "Wyjście"){
                    notifyDestroyed();
                }
            }
        }
 
    /*I tym właśnie sposobem zakończyliśmy prace nad menu i opcjami.
        Teraz czas na przystąpienie do najważniejszej części programu
        czyli samego węża.*/
 
    /* Nasza klasa Wezyk dziedziczy z Canvas co umożliwia m.in. rysowanie;
        implementujemy także interfejs odpowiedzialny za tworzenie i używanie
        wątków(Runnable) oraz nasłuchiwanie zdarzeń (CommandListener). */
    class Wezyk extends Canvas implements Runnable,CommandListener {
        /* Obiekt Command odpowiedzialny za możliwość powrotu do menu głównego. */
        private Command anuluj = new Command("Menu główne", Command.BACK, 1);
        /* Zmienna int określająca liczbę punktów; oczywiście na początku jest 0. */
        int punkty = 0;
        /* Łańcuch tekstowy który przyjmuje wartość 'Przegrałeś' j
        eśli użytkownik przegrał. */
        String state = ""; 
        boolean gra = true; // określa czy użytkownik jeszcze gra
        /* Określa czy użytkownik ruszył się już wężem (na początku gry wąż stoi). */
        boolean start = false; 
        int foody; // współrzędna y jedzenia
        int foodx; // współrzędna x jedzenia
        int kierunek; // kierunek ruchu weza
        int py=0; // przesunięcie węża pionowe
        int px=0; // przesunięcie węża poziome
        /* Zmienna określająca pozycje ostatniego elementu w wektorze,
        czyli mówi ona nam jak długi jest wąż. */
        int licznik=7;
        /* Współrzędna x i y konkretnego elementu węża. te dwie zmienne są
        potrzebne do różnych obliczeń i działań i przyjmują różne
        wartości w dalszym kodzie. */
        int ostx;
        int osty;
        /* Jeżeli jest true to znaczy że użytkownik nie ruszył się jeszcze wężem. */
        boolean ustawilem=true;
        /* W tym wektorze znajdują się współrzędne x elementów weza; wektor jest
        to jakby tablica z tym że może ona się dowolnie rozszerzać i ma
        kilka bardzo przydatnych metod ułatwiających znacząco prace. */
        Vector x = new Vector (10,10);
        Vector y = new Vector (10,10); // Tu znajdują się współrzędne y
        Random r= new Random(); // Obiekt klasy losującej liczby
        Thread runner; // Obiekt wątku
        int posx = getWidth(); // int określajacy szerokość planszy
        int posy = getHeight(); // i wysokość
        /* Zmienna określająca ile czasu trzeba odjąć w przerwach miedzy ruchami
        węża od stałej ilości czasu; zależy ona od
        szybkości jaką ustawił użytkownik. */
        int czas;
 
        public Wezyk() { // konstruktor wezyka
            setCommandListener(this); // ustawianie nasłuchiwania zdarzeń
            addCommand(anuluj); //  ustawienie możliwości powrotu do menu głównego gry
            /* Uruchamianie wątku; ten sposób gwarantuje, że nic się nie
            sknoci, ani wątek nie uruchomi się drugi raz */
            if (runner == null) { 
                runner = new Thread(this);
                runner.start();
            }
        }
 
        public void keyPressed(int code) { // metoda pobierająca jaki przycisk został wciśnięty
            int game = getGameAction(code); //  zmienna która mówi jak przycisk został wciśnięty
 
            /* GAME A, B, C, D to przyciski 1,3,7,9 na telefonie;
            to jest sterowanie narożnikami
            Góra- kierunek ==1
            Prawo- kierunek ==2
            Dół ? kierunek ==3
            Lewo ? kierunek ==4*/
            switch (game) {
                case Canvas.GAME_A :
                    /* Jeżeli wciśnięty przycisk 1 (Game A) to jeżeli
                    wąż porusza się obecnie:
                    - w prawo lub lewo to na wskutek wciśnięcia przycisku skręci do góry
                    - do dołu lub do góry to skręci w lewo
                    Następne trzy są analogiczne. */
                    if(kierunek==2 | kierunek==4) kierowanie(1);
                    else if(kierunek==1 | kierunek==3) kierowanie(4);
 
                    break;
 
                case Canvas.GAME_B :
                    if(kierunek==2 | kierunek==4) kierowanie(1);
                    else if(kierunek==1 | kierunek==3) kierowanie(2);
 
                    break;
 
                case Canvas.GAME_C :
                    if(kierunek==2 | kierunek==4) kierowanie(3);
                    else if(kierunek==1 | kierunek==3) kierowanie(4);
 
                    break;
 
                case Canvas.GAME_D :
                    if(kierunek==2 | kierunek==4) kierowanie(3);
                    else if(kierunek==1 | kierunek==3) kierowanie(2);
 
                    break;
 
            /* UP, RIGHT, LEFT, DOWN to kursory na telefonie a także przyciski
            2,4,6,8; to już jest proste sterowanie. */
 
                case Canvas.UP :
                    /* Wąż nie może zacząć poruszać się w przeciwną stronę niż
                    porusza się w danej chwili; w tym przypadku jeżeli wąż
                    porusza się w dół to nie może zacząć się poruszać do góry. */
                    if(kierunek!=3){ 
                        kierowanie(1);
                    }
                    break;
 
                case Canvas.DOWN  :
                    if(kierunek!=1){
                        kierowanie(3);
                    }
                    break;
 
                case Canvas.RIGHT    :
                    if(kierunek!=4){
                        kierowanie(2);
                    }
                    break;
 
                case Canvas.LEFT  :
                    if(kierunek!=2){
                        kierowanie(4);
                    }
                    break;
                /* Jeżeli wciśnięty zostaje jakiś inny przycisk np.
                5 to nic się nie dzieje. */
                default: 
                break;
 
            }
        }
 
    /* Teraz przystępujemy do metody paint, w której będziemy rysować węża. */
 
        public void paint(Graphics g){
            /* Ustawienie początkowego położenia weża; odbywa się to tylko raz
            podczas całego życia węża. */
            if(ustawilem){
                poczatek(); // Metoda ustawiająca początkowe położenie węża
                if(speed==0) speed=1; // Jeżeli użytkownik wpisał 0 to ustawiana jest wartość 1
                /* Im większy speed tym więcej czasu później będzie odjęte od
                pauzy miedzy ruchami w skutek tego wąż
                będzie poruszał się szybciej. */
                czas=10*speed; 
                ustawilem=!ustawilem; // Dzięki temu ustawienie odbędzie się tylko raz
                food(); // Metoda umieszczająca jedzonko na planszy
    }
            /* Ten fragment jest jednym z najważniejszych w całym programie;
            w powyższych linijkach odbywa się pobranie współrzędnych ostatniego
            elementu z wektorów, następnie dodanie do nich wartości
            reprezentujących kierunek w jakim porusza się wąz, i usunięcie
            ostatniego elementu z wektorów: */
            if(gra){
                int aaa = ((Integer)x.elementAt(licznik)).intValue();
                int bbb = ((Integer)y.elementAt(licznik)).intValue();
                x.addElement(new Integer(aaa+px));
                x.removeElementAt(0);
                y.addElement(new Integer(bbb+py));
                y.removeElementAt(0);
 
                g.setColor(255,255,255); // Ustawiamy biały kolor
                /* Rysujemy biały prostokąt o wymiarach planszy który zmazuje
                co było wcześniej narysowane- tak się robi animacje ;
                nie zmazujemy jednak pierwszych 13 rzędów pikseli gdzie
                znajduje się pole wyświetlające punkty. */
                g.fillRect(0, 13, getWidth(),getHeight());
                g.setColor(0,0,0); // Ustawiamy czarny kolor
                // Pętla w której następuje rysowanie poszczególnych elementów węża:
                for ( int i=0; i<licznik; i++ ){
                    /* (Integer)x.elementAt(i)- ten fragment pobiera z wektora
                    obiekt reprezentujący współrzędne  x i następuje rzutowanie
                    na obiekt Integer ;   następnie za pomocą metody
                    intValue obiekt Integer zamieniany jest
                    na normalna zmienna liczbową; */
                    ostx=((Integer)x.elementAt(i)).intValue(); 
                    osty=((Integer)y.elementAt(i)).intValue();
                    g.fillRect(ostx,osty,3,3); //
                }
                /* Po wykonaniu pętli zmienne ostx i osty zawierają wpsółrzedne
                pierwszego fragmentu węzą (głowy). */
                g.drawArc(foodx,foody,2,2,0,359); // rysowanie jedzenia, które jest pustym okręgiem
                /* Jeżeli głowa węża znajdzie się w odległości do dwóch pikseli
                to następuje pochłoniecie jedzonka. */
                if((ostx<=foodx+2 && ostx>=foodx-2) && (osty<=foody+2 && osty>=foody-2)) {
                    /* Metoda zamieniająca jedzenie na kolejny fragment węża;
                    dzięki temu wąż się przedłuża. */
                    zjadlem();
                    /* Dodawanie punktów; im większa ustawiona prędkość węża
                    tym więcej punktów otrzymujemy. */
                    punkty=punkty +speed; 
                    food(); // metoda ustawiająca jedzonko na planszy
                }
                /* Metoda sprawdzająca czy waz nie wyszedł za plansze oraz czy
                nie dotknął swojego ogona. */
                stan();
                /* Rysowanie łańcucha tekstowego state; state przyjmuje wartość
                'Przegrałeś' jeżeli użytkownik przegrał- wtedy dopiero jest
                widoczne to na ekranie. */
                g.drawString(state, posx/2, posy/2, Graphics.HCENTER | Graphics.TOP); 
                g.setColor(255,255,255); // biały kolor
                /* Rysujemy prostokąt zmazujący co to było wcześniej
                narysowane w polu punktów. */
                g.fillRect(0,0, getWidth(), 13); 
                g.setColor(0,0,0); // czarny kolor
                /* Rysujemy linie oddzielającą pole punktów od planszy z wężem. */
                g.drawLine(0,13,posx,13);
                /* Rysujemy łańcuch tekstowy podający liczbę punktów.*/
                g.drawString(punkty+"", posx-10,2, Graphics.HCENTER | Graphics.TOP); 
            }
        }
 
        public void run() { // metoda wątku
            while(true){ // nieskończona pętla
                /* Przerwa w milisekundach, miedzy kolejnymi przerysowaniami;
                jest mniejsza jeżeli użytkownik ustawił wyższą prędkość. */
                sleep(160-czas); 
                repaint(); // przerysowanie
            }
        }
 
        /* Metoda ustawiająca początkowe elementy węża (początkową długość). */
        public void poczatek () { // 
            for ( int i=0; i<8; i++ ){
                x.addElement(new Integer(40));
                y.addElement(new Integer(40));
            }
        }
 
        /* Metoda wywoływana po naciśnięciu przycisków kierowania. */
        public void kierowanie (int kier) { 
            start=true; //  użytkownik wystartował, wąz się rusza
 
            if(kier==1) { // kierunek do góry = zmniejsza się przesuniecie y węża
                py=-3;
                px=0;
                kierunek=1;
            }
 
            else if(kier==2) { // kierunek w prawo = zwiększa się przesuniecie x weza
                py=0;
                px=3;
                kierunek=2;
            }
 
            else if(kier==3){ // kierunek do dołu = zwiększa się przesuniecie y weza
                py=3;
                px=0;
                kierunek=3;
            }
 
            else if(kier==4){ // kierunek w lewo = zmniejsza się przesuniecie x weza
                py=0;
                px=-3;
                kierunek=4;
            }
        }
 
    /* Metoda sprawdzająca czy waz nie wyszedł za plansze oraz czy nie dotknął
        swojego ogona. */
        public void stan() {
            /* Działa tylko jeżeli użytkownik wystartował już weza; gdyby tak
            nie było to gracz od razu by przegrał, ponieważ na początku wąż
            się nie rusza i ?dotyka? samego siebie. */
            if(start) {
                int aaa = ((Integer)x.elementAt(licznik)).intValue(); // wspólrzędna x głowy węza
                int bbb = ((Integer)y.elementAt(licznik)).intValue(); // współrzędna y głowy weza
                // Sprawdzanie czy wąż nie wyszedł poza plansze:
                if( (kierunek==1 && bbb<13) || (kierunek==3 && bbb>posy)
                    || (kierunek==2 && aaa>posx) || (kierunek==4 && aaa<0) ) { 
                    state="Przegrales";
                    gra=!gra;
                }
 
                /* Sprawdzanie czy wąż nie dotknął samego siebie- porównywane
                są po kolei wszystkie współrzędne elementów węża ze
                współrzędnymi głowy węża. */
                for( int i=0; i<licznik; i++ ) { 
                    if( bbb==((Integer)y.elementAt(i)).intValue() &&
                        aaa==((Integer)x.elementAt(i)).intValue()) {
                        state="Przegrales";
                        gra=!gra;
                    }
                }
            }
        }
 
    /* Metoda wywoływana kiedy wąż zje jedzenie- następuje wtedy dodanie
        następnego elementu weza na jego końcu. */
        public void zjadlem() {
            // współrzędne x przedostatniego elementu węża
            int przedx = ((Integer)x.elementAt(1)).intValue();
            // współrzędne y przedostatniego elementu weza
            int przedy = ((Integer)y.elementAt(1)).intValue();
            // współrzędne x ostatniego elementu weza
            int ostx = ((Integer)x.elementAt(0)).intValue();
            // współrzędne y ostatniego elementu weza
            int osty = ((Integer)y.elementAt(0)).intValue(); 
 
            /* Sprawdzanie różnicy we współrzędnych ostatniego
            i przedostatniego elementu weza. */
            int yy= (przedy-osty); 
            int xx= (przedx-ostx);
 
            // Dodanie współrzędnej do wektora na jego końcu.
            x.insertElementAt(new Integer(ostx-xx),0); 
            y.insertElementAt(new Integer(osty-yy),0);
            licznik++; // zwiększenie zmiennej liczącej ilość elementów węża
        }
 
    /* Metoda która ustawia na planszy nowe jedzenie. */
        public void food(){
            /* Losowanie liczby z przedziału wysokość planszy-20 (należy odjąć
            20 ze względu na obszar gdzie pokazywane są punkty). */
            foody=(r.nextInt()%(posy-20)); 
            foodx=(r.nextInt()%(posx-4));
            // jeżeli zostaje wylosowana liczba ujemna to zostaje zamieniona na dodatnią
            if(foody<0) foody=foody*(-1); 
            if(foodx<0) foodx=foodx*(-1);
            /* Nie pozwala na rysowanie jedzenie na obszarze gdzie są wyświetlane punkty i na granicy planszy. */
            foody=foody+16; 
            foodx=foodx+2;
        }
 
    /* Metoda zatrzymująca program na liczbę time milisekund. */
        void sleep(int time) { 
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) { }
        }
    /* Metoda nasłuchująca zdarzenia. */
        public void commandAction(Command c, Displayable s) { 
            if (c == anuluj) { // wyjście z gry do menu
                display.setCurrent(list);
            }
        }
    }
}




Paweł P.
Poprawa kodu i drobnych błędów: Sznurek

6 komentarzy

sznurek 2007-12-24 11:41

Te błędy z wielkością liter wynikły z tego, że poprawiałem cały kod autora tekstu (formatowanie i nazwy zgodnie z zaleceniami Suna - klasy z wielkiej litery itp.). A co do opcji z szybkością to nie wiem - nawet nie testowałem tego kodu :)

Marek K 2007-12-23 22:33

W liniach 18 i 67 powinno być Wezyk a nie wezyk?
Poza tym w opcjach nie działa "Szybkość" (przynajmniej na OTA).
Pozdrawiam.

mszulc 2005-01-29 10:52

IMHO za dużo zaciemniających kod komentarzy


Maciej Szulc
Solution Manager
Mobile Systems
www.mobile-systems.net

emsiweds 2004-08-28 23:11

wro- mam nadzieje że nie powie tak mój informatyk po wakacjach :P

Marooned- dzieki :)

wro 2004-08-27 21:17

fajny temat, tylko trochę nie da się tego czytać :-)

Marooned 2004-08-27 00:13

Ale kodu nie trzeba 'boldować' - wrzuć caly kod w znaczniki &lt;cpp>