Piszemy węża na komórkę

emsiweds

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ń.

#Gra bedzie przystosowana do telefonów Siemens.
#Obecne musi być menu, gdzie użytkownik będzie miał możliwość ustawienia szybkości.
#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.
#Gra powinna być czarno-biała, żeby działała na telefonach bez kolorowego wyświetlacza.
#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.
#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.
#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

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 :)

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

IMHO za dużo zaciemniających kod komentarzy

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

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

Marooned- dzieki :)

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

Ale kodu nie trzeba 'boldować' - wrzuć caly kod w znaczniki <cpp>