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