October 12, 2010

Snake Game!

Untuk gamenya dapat dimainkan di sini : http://greenfootgallery.org/scenarios/1942
:D

Source code telah diubah agar lebih sistematis. Tapi dasarnya sih tetep seperti dibawah ini :D

Akhirnya seteleah sekian lama, saya ngeblog juga hehe..
Okee, sekarang kita akan membuat Snake Game! game ular ularan itu lho yang ada di handphone handphone lama. Namun sekarang kita buat versi Greenfootnya!
Tanpa banyak basa basi, ayoo kita mulai!

Snake Game... Start!
Kali ini, kita akan bahas pembuatan game ini per tipe/kelas hohoho!
Berikut ini adalah kelas kelas yang ada dalam game ini:
  • DuniaUlar, yang merupakan subclass dari World.
  • KepalaUlar, yang akan membuat kepala ular, yang menentukan kendali.
  • KepalaUlar.BadanUlar, inner-class(kelas dalam)nya KepalaUlar, yang menentukan badan dari kepala ular tersebut(yang akan dimasukan kedalam parameter konstruktor BadanUlar).
  • MakananUlar, makanan ular :D.
Dan ada interface pembantu yang akan digunakan, sekalian untuk belajar apa itu interface di Java, serta penggunaannya sebagai tipe. Adapun interface interfacenya disini adalah:
  • BisaDiikuti, menunjukan bahwa objek yang mengiplementasikan interface ini bisa diikuti.
  • BisaDimakan, menunjukan bahwa objek yang mengiplementasikan interface ini bisa dimakan.
Oke! mari kita bahas kelas kelas tersebut satu persatu!

DuniaUlar..
Berikut ini merupakan keseluruhan source code dari DuniaUlar.java

import greenfoot.*; // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
import java.awt.Color;

/**
* Dunia Ular!
*
* @author Keenan M. Gebze
* @version 1.0
*/
public class DuniaUlar extends World
{
/**
* Konstruktor untuk DuniaUlar.
* Membuat latar belakang DuniaUlar.
* Menambahkan objek objek yang diperlukan.
*/
public DuniaUlar()
{
/**
* Membuat DuniaUlar dengan ukuran 20x20, dan setiap kotak 20 pixel * 20 pixel
*/
super(20, 20, 20);

/**
* Mengisi latar belakang dengan warna 0, 100, 0, kira kira hijau tua.
*/
GreenfootImage latar = new GreenfootImage(20,20);
latar.setColor(new Color(0, 100, 0));
latar.fill();
setBackground(latar);

/**
* Membuat dan menambahkan KepalaUlar "utama", Dan menambah makanan masuk kedalam DuniaUlar ini.
*/
KepalaUlar utama = new KepalaUlar();
addObject(utama, getWidth()/2, getHeight()/2);
addObject(new MakananUlar(), Greenfoot.getRandomNumber(getWidth()), Greenfoot.getRandomNumber(getWidth()));
}
}


Kelas ini berfungsi untuk membuat Dunia Ularnya dan menambahkan ular kedalamnya.

GreenfootImage latar = new GreenfootImage(20,20);
latar.setColor(new Color(0, 100, 0));
latar.fill();
setBackground(latar);


Baris pertama kode ini adalah menciptakan gambar baru berukuran 20px*20px dengan nama "latar".
"latar" ini di-set warnanya, namun BELUM DIGAMBAR! Bisa diumpamakan warnanya masih berada di kuas. Lalu, barulah kita menolehkan warnanya pada gambar tersebut dengan method fill() (isi).
Setelah penggambaran gambar "latar", maka tinggal meng-set background DuniaUlar ini dengan gambar latar "latar", yang sudah kita buat tadi.
Oh iya, kita meng-set warna dengan objek Color pada java.awt.Color, yang sudah kita import di atas.
Untuk mencari cari warna baru, sebaiknya baca dokumentasi java.awt.Color disini.

KepalaUlar utama = new KepalaUlar();
addObject(utama, getWidth()/2, getHeight()/2);
addObject(new MakananUlar(), Greenfoot.getRandomNumber(getWidth()), Greenfoot.getRandomNumber(getWidth()));


Lalu cuplikan kode ini, pertama kita membuat objek KepalaUlar kedalam variabel "utama", dan menambahkan KepalaUlar"utama" ini kedalam world, pada posisi tengah tengah (setengah dari lebar, setengah dari tinggi DuniaUlar).
Setelah menambahkan KepalaUlar utama kedalam DuniaUlar, kita menambahkan makanan ular yang langsung dibuat(tanpa membutuhkan variabel) kedalam posisi acak x, dan posisi acak y.
Seharusnya baris kode ini ditulis setelah kita selesai membuat objek KepalaUlar-nya. Tapi tidak apa apa karena kita sudah tau bakal ada KepalaUlar, heheheh.

Okeee! kita sudah berhasil menyiapkan tempatnya, mari kita buat ULARNYA!


Ular.... lar... lar...
lar lar lar.... -_____-
Yang pertama harus diketahui tentang ular ini adalah, bahwa sebenernya kita hanya memainkan "kepalanya" saja, sedangkan badannya? hanya mengikuti :D.
Lalu badannya saya masukan kedalam kelas KepalaUlar sebagai Innerclass agar lebih enak aja :D. Jika class BadanUlar dikeluarkan dari class KepalaUlar tidak akan mempengaruhi apa apa!

mari kita bahas KepalaUlarnya! (tanpa innerclass BadanUlar!)
import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
import java.awt.Point;
import java.awt.Color;
import java.util.ArrayList;

/**
 * Kepala ular, yang mengontrol seluruh gerakan ular.
 * Berwarna merah.
 *
 * @author Keenan M. Gebze
 * @version 1.0
 */
public class KepalaUlar extends Actor implements BisaDiikuti
{
    /**
     * Membuat enumerasi arah.
     */
    enum Arah {
        ATAS, BAWAH, KIRI, KANAN;
    }
  
    /**
     * posisiAkhir, menunjukan posisi terakhir dari ular ini.
     * arah, menunjukan arah ular ini.
     * bagianBadan, bagian bagian badan yang akan bertambah bila ular ini makan.
     *
     * kita buat semua variabel disini private (privat) karena
     * variabel ini tidak digunakan oleh kelas lainnya (hanya digunakan oleh kelas ini).
     * hal ini bisa mengurangi penggunaan memori.
     */
    private Point posisiAkhir;
    private Arah arah = Arah.KANAN;
    private ArrayList<BadanUlar> bagianBadan;

    /**
     * Membuat Kepala Ular baru.
     *
     * Membuat gambar ular.
     * Mempersiapkan variabel bagianBadan.
     * Mengawali bagianBadan dengan 6 bagian badan.
     */
    public KepalaUlar() {
        /**
         * Membuat gambar untuk KepalaUlar ini.
         */
        GreenfootImage gambar = new GreenfootImage(20,20);
        gambar.setColor(Color.BLUE);
        gambar.fill();
        setImage(gambar);
      
        /**
         * Membuat list array untuk menunjukan jumlah badan kita.
         */
        bagianBadan = new ArrayList<BadanUlar>();
        bagianBadan.add(new BadanUlar(this));
        for(int i=0; i<=6; i++) {
            bagianBadan.add((bagianBadan.size() > 0)?new BadanUlar(bagianBadan.get(bagianBadan.size()-1)):new BadanUlar(this));
        }
    }
  
    /**
     * Fungsi ini dipanggil oleh Greenfoot ketika objek KepalaUlar masuk ke World w.
     * Setelah masuk ke World, kita hanya ingin memperbaharui badannya, tergantung dari
     * ArrayList bagianBadan tadi.
     *
     * Fungsi ini hanya dipanggil sekali, yaitu ketika objek ini masuk ke Dunia (World)
     */
    public void addedToWorld(World w) {
        perbaharuiBadan();
    }
      
    /**
     * Fungsi ini dipanggil oleh Greenfoot selalu.
     */
    public void act()
    {
        if(Greenfoot.isKeyDown("up") && arah != Arah.BAWAH) {
           arah = Arah.ATAS;
        }else if(Greenfoot.isKeyDown("down") && arah != Arah.ATAS) {
           arah = Arah.BAWAH;
        }else if(Greenfoot.isKeyDown("left") && arah != Arah.KANAN) {
           arah = Arah.KIRI;
        }else if(Greenfoot.isKeyDown("right") && arah != Arah.KIRI) {
           arah = Arah.KANAN;
        }
      
        posisiAkhir = new Point(getX(), getY());
      
        switch(arah) {
            case ATAS: setLocation(getX(), getY()-1); break;
            case BAWAH: setLocation(getX(), getY()+1); break;
            case KIRI: setLocation(getX()-1, getY()); break;
            case KANAN: setLocation(getX()+1, getY()); break;
            default: arah=Arah.KANAN;
        }
      
        if(getOneObjectAtOffset(0,0, MakananUlar.class) != null) {
            MakananUlar makanan = (MakananUlar) getOneObjectAtOffset(0,0, MakananUlar.class);
            makan(makanan);
        }else if(getOneObjectAtOffset(0,0, KepalaUlar.BadanUlar.class) != null) {
            getWorld().removeObject(this);
        }
    }
  
    public Point posisiAkhir() {
        return posisiAkhir;
    }
  
    public BisaDiikuti yangDiikuti() {
        return null;
    }
  
    /**
     * Makan makanan ular.
     *
     * @param makanan makanan ular yang akan dimakan.
     */
    public void makan(MakananUlar makanan) {
        makanan.dimakan();
        bagianBadan.add(new BadanUlar(bagianBadan.get(bagianBadan.size()-1)));
        perbaharuiBadan();
    }
  
    /**
     * Memperbaharui badan tergantung dari ArrayList<BadanUlar> bagianBadan.
     */
    private void perbaharuiBadan() {
        for(BadanUlar bu : bagianBadan) {
            getWorld().addObject(bu, ((Actor)bu.yangDiikuti()).getX(), ((Actor)bu.yangDiikuti()).getY());
        }
    }
}


Lihat awal classnya.

public class KepalaUlar extends Actor implements BisaDiikuti{.....

implements BisaDiikuti menunjukan bahwa class ini bisa Diikuti, dan harus mempunyai fungsi yang ada di interface BisaDiikuti.
Lalu lihat, ada sesuatu yang baru, Enum type(Tipe Enum), alias enumerasi!

enum Arah {
        ATAS, BAWAH, KIRI, KANAN;
    }


Enumerasi memudahkan kita dalam pengkodean. Tanpa enumerasi, kode disini akan lebih kacau dan membuat class KepalaUlar menjadi lebih sulit. Jika ingin belajar tentang enumerasi, silahkan lihat tutorial javanya disini.
Tapi dengan tutorial ini, semoga cukuplah untuk mengerti apa itu tipe enum dan penggunaannya :-).
Lihat nih contohnya:

private Arah arah = Arah.KANAN;

Jauh lebih mudah bukan? daripada menggunakan seperti ini:

private static final int ARAH_KANAN = 0;
private static final int ARAH_KANAN = 1;
private static final int ARAH_KANAN = 2;
private static final int ARAH_KANAN = 3;
private arah = 0; // berarti arah kanan.. 

Lame.. :D itulah keuntungan dari Enumerasi.

Wah lihat, ada yang baru lagi.

private ArrayList<BadanUlar> bagianBadan;
 
Kode diatas menggunakan apa yang disebut dengan Generics. Anda dapat belajar mengenai Generics disini.
Maksud dari kode diatas adalah untuk menyiapkan sebuah Array yang semuanya nantinya akan dan hanya akan berisikan objek objek dari tipe BadanUlar. Hampir sama seperti Array biasa, namun keuntungan menggunakan ArrayList adalah daya tampungnya yang bisa berubah ubah.

BadanUlar[] bagianBadan ={.....} //tidak bisa diubah.

Jadi itulah mengapa saya menggunakan ArrayList! untuk dokumentasi ArrayList, dapat dilihat disini.

Lanjuut.

for(int i=0; i<=6; i++) {
            bagianBadan.add((bagianBadan.size() > 0)?new BadanUlar(bagianBadan.get(bagianBadan.size()-1)):new BadanUlar(this));
        }for(int i=0; i<=6; i++) {
            bagianBadan.add((bagianBadan.size() > 0)?new BadanUlar(bagianBadan.get(bagianBadan.size()-1)):new BadanUlar(this));
        }


Maksud dari kode ini sebenarnya sederhana, cuma memang penulisannya yang agak ribet.
Maksud dari kode ini adalah untuk membuat KepalaUlar ini agar mempunyai 6 buah BadanUlar pada awal awal ia diciptakan, jadi dia tidak hanya kepala saja.
Sebenarnya, kode ini berfungsi untuk memasukan objek objek BadanUlar kedalam ArrayList<BadanUlar> yang nantinya akan dimasukan kedalam DuniaUlar.

Oke, sekarang kita akan belajar mengenai kelas BadanUlar!

BadanUlar
Sesuai dengan nama kelasnya, BadanUlar adalah kelas yang digunakan untuk membuat bagian badan dari sesuatu yang diikutinya (alias kelas yang mengimplementasikan BisaDiikuti)
Berikut ini adalah source code dari kelas BadanUlar (yang berada dalam kelas KepalaUlar):

/**
* BadanUlar.
*
* Nampaknya lebih bagus kalau dimasukan kedalam kelas KepalaUlar.
*/
public class BadanUlar extends Actor implements BisaDiikuti{

private BisaDiikuti bagianUlar;
private Point posisiAkhir;

public BadanUlar(BisaDiikuti bagianLainnya) {
this.bagianUlar = bagianLainnya;
GreenfootImage gambar = new GreenfootImage(20,20);
gambar.setColor(Color.BLACK);
gambar.fill();
setImage(gambar);
}

public void act() {
posisiAkhir = new Point(getX(), getY());
setLocation((int)bagianUlar.posisiAkhir().getX(), (int)bagianUlar.posisiAkhir().getY());
if(((Actor)yangDiikuti()).getWorld() == null) {
getWorld().removeObject(this);
}
}

public BisaDiikuti yangDiikuti() {
return bagianUlar;
}

public Point posisiAkhir() {
return posisiAkhir;
}
}


Kelas ini sebenarnya sangat sederhana, kita bahas tiap elemennya

private BisaDiikuti bagianUlar;
private Point posisiAkhir;


field "bagianUlar" mewakili objek yang akan kita ikuti (alias mengimplementasikan interface BisaDiikuti), interface BisaDiikuti, menjamin bahwa objek tersebut bisa diikuti dengan mewajibkan kelas yang mengimplementasikannya agar mempunyai fungsi yang ada dalam interface itu, dalam hal ini adalah fungsi "yangDiikuti()" yang mengembalikan objek BisaDiikuti dari field "bagianUlar".

public BisaDiikuti yangDiikuti() {
            return bagianUlar;
}



Diatas adalah metode yang wajib kita pergunakan, namun isinya boleh bebas, dalam hal ini, kita hanya mengembalikan / me-return bagianUlar lain yang kita ikuti.

public Point posisiAkhir() {
            return posisiAkhir;
        }


Sedangkan field/variabel "posisiAkhir" mewakili posisi terakhir benda ini, yang nantinya akan diikuti oleh objek yang mengikuti kita. Variabel ini kita return pada fungsi "posisiAkhir()" yang merupakan fungsi yang harus kita tulis (karena mengimplementasikan BisaDiikuti). Objek yang mengikuti kita, dapat menggunakan fungsi ini untuk mendapatkan posisi terakhir kita dan... mengikutinya!
Sedangkan kelas Point merupakan kelas yang diimport dari java.util.Point. Point sendiri merupakan kelas yang dapat menampung/menunjukan posisi X, dan posisi Y, ya Point memang merupakan kelas yang sederhana, heheheh. Untuk dokumentasi dari kelas Point, anda dapat membacanya disini.

public BadanUlar(BisaDiikuti bagianLainnya) {
            this.bagianUlar = bagianLainnya;
            GreenfootImage gambar = new GreenfootImage(20,20);
            gambar.setColor(Color.BLACK);
            gambar.fill();
            setImage(gambar);
        }


Ini konstruktor dari kelas BadanUlar, seperti biasa seperti kelas kelas lainnya, kita hanya akan meng-set gambar dari badan ular ini (dengan ukuran 20 x 20, dan berwarna hitam).

Oke sekarang fungsi act-nya BadanUlar, bagaimanakah badan ular ber-act-si? heheh..

public void act() {
            posisiAkhir = new Point(getX(), getY());
            setLocation((int)bagianUlar.posisiAkhir().getX(),(int)bagianUlar.posisiAkhir().getY());
            if(((Actor)yangDiikuti()).getWorld() == null) {
                getWorld().removeObject(this);
            }
        }

  • Pada baris pertama, kita menampung posisi akhir kita kedalam field "posisiAkhir" yang telah kita buat tadi dengan sebuah objek yang menampung posisi x dan posisi y kita didalam DuniaUlar.
  • Lalu pada baris ke-2, kita memindahkan objek badan ular ini ke posisi akhir bagian ular yang kita ikuti. Kita perlu mengubahnya menjadi int (int, Integer) karena fungsi getX() dan getY() dari kelas Point mengembalikan nilai double(bukan int) tidak seperti getX() dan getY()nya kelas Actor. Kenapa double tidak bisa? karena fungsi "setLocation()" membutuhkan int. "setLocation(int x, int y)".
  • Inti dari baris ke-3 adalah, jika objek yang kita ikuti sudah tidak mempunyai World (DuniaUlar) atau bisa kita baca, objek yang kita ikuti tidak lagi ada di dunia "DuniaUlar"(Sebenarnya World, karena kita memanggil fungsi "getWorld()"). Lalu kita menghapus diri sendiri. Kode ini bisa kita ubah nanti bila kita tidak ingin objek ini menghilang begitu saja. Bisa saja kita tambah suara :D.
Lihat, sederhana bukan? :D, fungsi act() kelas BadanUlar hanya terdiri dari 3 baris :D.
Pembahasan kelas sederhana BadanUlar pun sudah selesai. Mari kita bahas yang enak enak.

MakananUlar.... (kayanya sih ngga enak -__-)
Kelas ini sangat sederhana, teridiri dari konstruktor yang memberikan warna merah pada gambar, dan sebuah fungsi yang menunjukan bahwa buah ini telah dimakan!

import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
import java.awt.Color;

/**
 * Makanan Ular.
 *
 * @author Keenan M. Gebze
 * @version 1.0
 */
public class MakananUlar  extends Actor implements BisaDimakan
{
    /**
     * Membuat makanan ular.
     * Menggabar dan memberi warna merah.
     */
    public MakananUlar() {
        GreenfootImage gambar = new GreenfootImage(20,20);
        gambar.setColor(Color.RED);
        gambar.fill();
        setImage(gambar);
    }
   
    /**
     * Fungsi yang menunjukan bahwa makanan ini sudah dimakan.
     */
    public void dimakan() {
        getWorld().addObject(new MakananUlar(), Greenfoot.getRandomNumber(getWorld().getWidth()),  Greenfoot.getRandomNumber(getWorld().getWidth()));
        getWorld().removeObject(this);
    }
}


Fungsi "dimakan()" adalah untuk membuat makanan baru dan melatakannya secara acak, lalu menghapus dirinya sendiri. Fungsi dimakan merupakan fungsi yang wajib dituliskan karena kelas MakananUlar mengimplementasikan interface BisaDimakan. Sebenernya tidak terlalu berguna sih, cuma interface BisaDimakan saya buat agar kita bisa lebih mengenal tentang interface, hehe.

Para Interface
Pembahasan kita mengenai kelas kelas utama sudah selesai, mari kita lihat interfacenya! Untuk penjelasan mengenai apa itu interface, silahkan baca disini.

BisaDiikuti

import java.awt.Point;
/**
 * Objek yang menggunakan interface ini berarti bisa di ikuti.
 *
 * @author Keenan M. Gebze
 * @version 1.0
 */
public interface BisaDiikuti
{
    Point posisiAkhir();
    BisaDiikuti yangDiikuti();
}


 BisaDimakan

/**
 * Objek yang bisa dimakan.
 *
 * @author Keenan M. Gebze
 * @version 1.0
 */
public interface BisaDimakan
{
    public void dimakan();
}


Kalian bisa liat fungsi dimakan() dipanggil pada saat fungsi makan() pada KepalaUlar dipanggil.

Selesai!
Kalian telah belajar untuk membuat game Ular ini disini, untuk download source dan memainkan game ini (lewat applet) silahkan kunjungi website greenfootgallery.org dimana saya mengupload game ini disitu.
http://www.greenfootgallery.org/



Keenan Mandela Gebze, 14 Oktober 2010.

8 comments:

  1. gan,,,kalau mengganti makanannya menjadi gambar kodok gmna???

    ReplyDelete
    Replies
    1. kode untuk untuk gambar di MakananUlar nya harus dihapus dulu, lalu bisa 2 cara:
      cara pertama pake greenfoot, klik kanan di kelasnya, setImage, ganti deh.
      Cara kedua di kodenya, di constructor, masukin gambar kodok dengan fungsi setImage(String s);

      Delete
  2. script munculin jOptionPane itu yang mana ??

    ReplyDelete
    Replies
    1. Download source codenya, ada pada fungsi gameKalah() pada kelas DuniaUlar

      Delete
  3. Scipt buat ngemakannya di mana gan? ga paham itu di atas kata-katanya,comment thank's you,di tunggu gan secepatnya

    ReplyDelete
    Replies
    1. if(getOneObjectAtOffset(0,0, MakananUlar.class) != null) {
      MakananUlar makanan = (MakananUlar) getOneObjectAtOffset(0,0, MakananUlar.class);
      makan(makanan);
      }else if(getOneObjectAtOffset(0,0, KepalaUlar.BadanUlar.class) != null) {
      getWorld().removeObject(this);
      }

      Delete
  4. Gan mau tanya nih, kalo buat game snake yang paling sederhana deh dari java ntar bisa di maenin di commendprompt, langkah awal yang di buat apa ya gan? Mohon bantuannya gan

    ReplyDelete