Membuat Game Snake dengan Bahasa C (Windows Console)

Membuat game memang berbeda dengan membuat aplikasi biasa. Untuk menjadi game developer, yang perlu dilakukan adalah membuat sebanyak (sesering) mungkin game. Nah, kebetulan semester ini aku mengajar mata kuliah Struktur Data. Saat membahas mengenai queue dengan double linked list, jadi ada ide untuk membuat permainan Snake.

Permainan snake dengan windows console
Permainan snake dengan windows console

Catatan: Tutorial ini menggunakan konsep linked list untuk membuat program. Jika Anda merasa awam dengan istilah ini, silakan baca tutorial alternatif untuk membuat game Snake yang sama tanpa linked list.

Snake adalah permainan yang biasa dijumpai pada ponsel-ponsel tahun 2000an. Pada game ini kita menggerakan ular untuk memakan makanan yang terletak di layar secara acak. Setiap kali memakan makanan, tubuh ular bertambah panjang. Tantangannya yaitu ular tersebut tidak boleh menabrak tembok maupun tubuhnya sendiri.

Pada tulisan kali ini, aku akan membagikan sedikit tutorial untuk membuat program permainan Snake dengan menggunakan bahasa C, double linked list, dan Windows API. Karena tutorial ini menggunakan library Windows.h untuk input/output, maka tutorial ini terbatas pada Microsoft Visual C saja.

Catatan: Mungkin tutorial ini kelihatan panjang. Padahal source code sesungguhnya hanya 289 baris (termasuk komentar). Untuk yang lebih suka membaca langsung dari source code, silakan langsung unduh di sini. Source code sudah diberi penjelasan-penjelasan.

Agar lebih mudah saat mencoba, mari kita lihat dulu alur programnya secara garis besar. Di sini, program utama dibagi menjadi tiga bagian, yaitu init (awal), game loop, dan ending (akhir).

Flowchart
Flowchart permainan Snake

Pada bagian awal, kita menyiapkan beberapa variabel yang diperlukan beserta nilai awalnya.

  • timestamp
    Timestamp ini merupakan penanda waktu yang akan kita gunakan untuk menghitung jeda pergerakan ular.
  • score
    Score untuk menampung nilai yang diperoleh pemain.
  • ular dengan 3 segmen
    Pada permulaan permainan kita set ular sepanjang 3 segmen.
  • posisi makanan
    Pada permulaan permainan kita tempatkan makanan secara acak.

Bagian game loop merupakan bagian yang deksekusi terus-menerus berulang-ulang selama permainan berlangsung. Di sini kita membaca input keyboard yang ditekan pemain, melakukan perubahan situasi (state), dan mencetak (render) isi permainan di layar. Umumnya layar akan di-render setiap putaran. Jadi, jika komputer berhasil melakukan 60 kali putaran dalam satu detik, artinya layar sudah ter-render ulang sebanyak 60 kali. Pada game berbasis grafis, di sini kita bisa menghitung jumlah frame per detik (FPS). Namun untuk kali ini, kita hanya akan me-render layar setiap 200 milidetik sekali, atau sekitar 5 FPS.

Bagian akhir digunakan untuk menampilkan nilai, dan “bersih-bersih” sebelum keluar dari program.

Langsung saja, mari kita mulai koding!

Memulai

Mari kita buat project baru di Visual Studio, dengan jenis Win32 Console Application.

Pertama-tama, include dulu beberapa pustaka yang akan dipakai.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys\timeb.h>
#include <Windows.h>

Double Linked List

Berhubung game ini merupakan contoh aplikasi yang dibuat untuk mata kuliah Struktur Data, maka di sini kita akan menggunakan double linked list dengan konsep queue. Jika Anda belum tahu apa itu linked list, silakan gunakan tutorial alternatif yang menggunakan array tanpa linked list.

Linked list ini kita gunakan untuk menampung ular. Satu node/elemen pada linked list sama dengan satu segmen ular. Tiap node berisi posisi koordinat (x,y) segmen di layar. Berikut ini bentuk strukturnya. Kita beri nama Segment.

/** Struktur **********/ 
 
/** 
    Struktur untuk menampung data tiap segment dari snake  
    (double linked list) 
 */ 
struct Segment { 
    int x, y; 
    struct Segment *next, *prev; 
};

Kita tambahkan dua buah pointer global, head dan tail. Head menunjuk ke node pertama, dan tail untuk node terakhir.

/** Variabel global **********/

// Pointer bagian kepala dan ekor snake (linked list)
struct Segment *head = NULL, *tail = NULL;

Untuk game ini, kita menggunakan konsep queue. Artinya, node pada linked list akan ditambahkan di awal (head), dan ketika dihapus, yang hilang adalah bagian akhir (tail). Istilahnya first in first out.

Berikut ini fungsi untuk melakukan penambahan push() dan penghapusan pop().

/** Fungsi-fungsi **********/ 
 
/** 
    Push segment ke snake (pada bagian head). 
 */ 
void push(int x, int y) { 
    // Buat segment (node) baru 
    struct Segment *node = (struct Segment *) malloc(sizeof(struct Segment)); 
    node->next = NULL; 
    node->prev = NULL; 
    node->x = x; 
    node->y = y; 
 
    // Jika snake (linked list) masih kosong 
    if (head == NULL) { 
        head = tail = node; 
    } 
    // Jika snake sudah ada, pasangkan segment ke head 
    else { 
        node->next = head; 
        head->prev = node; 
        head = node; 
    } 
} 
 
/** 
    Pop bagian ekor snake. 
 */ 
void pop() { 
    // Jika hanya ada satu segment (node) pada snake (linked list) 
    if (head == tail) { 
        free(head); 
        head = tail = NULL; 
    } 
    // Jika ada lebih dari satu segment, buang bagian ekor 
    else { 
        tail = tail->prev; 
        free(tail->next); 
        tail->next = NULL; 
    } 
}

Ular 3 Segment

Sekarang mari kita coba buat ular sepanjang 3 segmen pada bagian main(). Oke, supaya mudah untuk mengubah-ubah pengaturan panjang awalnya, kita simpan nilai 3 tersebut di variabel global snake_size. Ketiga segmen ini kita tempatkan di baris pertama (y = 0), di kolom ke 1, 2, dan 3 (x = 0 s.d. 2).

/** Konfigurasi permainan **********/ 
 
// Panjang segment snake saat awal permainan 
int snake_size = 3;

/** 
    Program utama 
 */ 
int main() { 
    // Pertama-tama, push segment (node) ke kanan  
    // sebanyak 3 segment (sesuai nilai variable snake_size) 
    for (int i = 0; i < snake_size; i++) { 
        push(i, 0); 
    } 

    return 0;
}

Ayo kita tes sejenak untuk memastikan tidak ada error sejauh ini dengan F5.

Pop All

Karena node yang pada linked list dibuat secara dinamis, kita harus menghapus kembali semuanya sebelum program berakhir untuk menghindari memory leak. Untuk itu, kita tambahkan fungsi pop_all().

/** 
    Pop semua segment 
 */ 
void pop_all() { 
    while (head != NULL) { 
        pop(); 
    } 
}

Kemudian, panggil fungsi tersebut sebelum return 0 di main() (baris 12).

/** 
    Program utama 
 */ 
int main() { 
    // Pertama-tama, push segment (node) ke kanan  
    // sebanyak 3 segment (sesuai nilai variable snake_size) 
    for (int i = 0; i < snake_size; i++) { 
        push(i, 0); 
    } 
 
    // Pop semua segment 
    pop_all();
    return 0;
}

Rendering

Setelah ular dibuat, kita akan mencetak ular tersebut di layar. Untuk mencetak, kita buat fungsi display(). Fungsi display() ini akan membaca nilai x dan y setiap node lalu mencetak satu karakter ‘O’ di posisi tersebut.

Untuk bisa mencetak di posisi (x,y), kita harus memindahkan kursor ke posisi tersebut. Untuk itu kita buat juga fungsi gotoxy().

/** 
    Pindahkan posisi kursor di layar 
    Fungsi ini spesifik untuk OS windows. 
*/ 
void gotoxy(int x, int y) { 
    COORD pos; 
    pos.X = x; 
    pos.Y = y; 
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos); 
} 
 
/** 
    Gambar snake (linked list) di layar 
 */ 
void display() { 
    struct Segment *p = head; 
    while (p != NULL) { 
        // Cetak di posisi x,y 
        gotoxy(p->x, p->y); 
        printf("O"); 
        p = p->next; 
    } 
}

Sekarang, mari panggil display() di main(), jalankan program dan lihat hasilnya (baris 16-19).

/** 
    Program utama 
 */ 
int main() { 
    // Pertama-tama, push segment (node) ke kanan  
    // sebanyak 3 segment (sesuai nilai variable snake_size) 
    for (int i = 0; i < snake_size; i++) { 
        push(i, 0); 
    } 
 
    // Tampilkan kondisi permainan saat ini di layar...
 
    // Bersihkan layar
    system("cls");
 
    // Cetak (render) snake di layar
    display();

    getchar();
    // Pop semua segment 
    pop_all();
    return 0;
}
Hore, ular sepanjang 3 segmen berhasil muncul di layar!
Hore, ular sepanjang 3 segmen berhasil muncul di layar!

Game Loop

Bagaimana caranya agar ular bisa bergerak? Caranya, adalah dengan membuat infinite loop untuk me-render ulang layar setiap putarannya. Dengan demikian, setiap ada perubahan situasi (state) pada linked list, entah itu jumlah node atau nilai x dan y nya, perubahan itu akan langsung tercermin di layar.

Mari kita taruh bagian rendering tadi ke dalam infinite loop (perhatikan baris 11-20).

/** 
    Program utama 
 */ 
int main() { 
    // Pertama-tama, push segment (node) ke kanan  
    // sebanyak 3 segment (sesuai nilai variable snake_size) 
    for (int i = 0; i < snake_size; i++) { 
        push(i, 0); 
    } 
  
    // Game loop. Bagian di dalam while akan dieksekusi terus menerus 
    while (true) { 
        // Tampilkan kondisi permainan saat ini di layar... 
 
        // Bersihkan layar 
        system("cls"); 
 
        // Cetak (render) snake di layar 
        display(); 
    }

    getchar(); 
    // Pop semua segment 
    pop_all();
    return 0;
}

Untuk menggerakkan ular ke kanan setiap 200ms, pertama-tama, di dalam game loop kita menghitung berapa waktu yang sudah terlewati, jika waktu yang berlalu sudah lebih atau sama dengan 200ms, maka kita geser ular. Sama dengan sebelumnya, agar nilai 200 ini mudah diubah-ubah, kita simpan dalam variabel global snake_speed.

// Kecepatan gerakan snake dalam ms 
int snake_speed = 200;

Untuk menghitung interval waktu yang berlalu, kita gunakan fungsi ftime() untuk mendapat kan penanda waktu.

Cara menggeser ular, adalah dengan melakukan pop(), lalu push() kembali di posisi koordinat head dengan nilai x ditambah 1 karena saat ini kepala ular mengarah ke kanan.

(Perhatikan baris 6-8, dan 17-40)

/** 
    Program utama 
 */ 
int main() {  

    // Untuk menyimpan penanda waktu saat snake bergerak
    struct timeb last_timestamp; 
    ftime(&last_timestamp); // Set nilai awal
 
    // Pertama-tama, push segment (node) ke kanan  
    // sebanyak 3 segment (sesuai nilai variable snake_size) 
    for (int i = 0; i < snake_size; i++) { 
        push(i, 0); 
    } 
    // Game loop. Bagian di dalam while akan dieksekusi terus menerus 
    while (true) { 
        // Ambil penanda waktu saat ini 
        struct timeb current_timestamp; 
        ftime(&current_timestamp); 
 
        // Selisih waktu terakhir dengan waktu sekarang dalam ms 
        int interval = 1000 * (current_timestamp.time - last_timestamp.time) + (current_timestamp.millitm - last_timestamp.millitm); 
 
        // Snake bergerak setiap 200 ms (sesuai nilai variable snake_speed) 
        // Dihitung dengan membandingkan selisih waktu sekarang dengan waktu  
        // terakhir kali snake bergerak. 
        if (interval >= snake_speed) { 
            // Tentukan posisi x,y ke mana snake akan bergerak. 
            int x, y; 
            x = head->x + 1; 
            y = head->y;
  
            // Pop ekor, lalu push segment ke depan head sehingga  
            // snake tampak bergerak maju.  
            pop(); 
            push(x, y);
 
            // Perbarui penanda waktu 
            last_timestamp = current_timestamp;
        }

        // Tampilkan kondisi permainan saat ini di layar... 
 
        // Bersihkan layar 
        system("cls"); 
 
        // Cetak (render) snake di layar 
        display(); 
    }

    ...
}

Coba jalankan lagi. Sekarang ular sudah bisa bergerak!

Tapi layar tampaknya berkedip-kedip. Hal ini terjadi karena program mencoba mengosongkan layar dengan system(“cls”); sebelum menggambar lagi. Umumnya pembuat game akan melakukan teknik double buffering untuk menghindari layar berkedip (flicker). Namun untuk menyederhanakan tutorial ini, kita akan lakukan pendekatan lain, yaitu dengan me-render ulang layar hanya ketika ular bergerak. Sehingga rendering hanya terjadi setiap 200ms sekali (5 FPS).

Caranya mudah, kita pindahkan baris-baris rendering ke dalam blok if(interval >= snake_speed) { } (baris 30-36).

/** 
    Program utama 
 */ 
int main() { 
    ...
 
    // Game loop. Bagian di dalam while akan dieksekusi terus menerus 
    while (true) { 
        // Ambil penanda waktu saat ini 
        struct timeb current_timestamp; 
        ftime(&current_timestamp); 
 
        // Selisih waktu terakhir dengan waktu sekarang dalam ms 
        int interval = 1000 * (current_timestamp.time - last_timestamp.time) + (current_timestamp.millitm - last_timestamp.millitm); 
 
        // Snake bergerak setiap 200 ms (sesuai nilai variable snake_speed) 
        // Dihitung dengan membandingkan selisih waktu sekarang dengan waktu  
        // terakhir kali snake bergerak. 
        if (interval >= snake_speed) { 
            // Tentukan posisi x,y ke mana snake akan bergerak. 
            int x, y; 
            x = head->x + 1; 
            y = head->y;
  
            // Pop ekor, lalu push segment ke depan head sehingga  
            // snake tampak bergerak maju.  
            pop(); 
            push(x, y); 

            // Tampilkan kondisi permainan saat ini di layar... 
 
            // Bersihkan layar 
            system("cls"); 
 
            // Cetak (render) snake di layar 
            display();
 
            // Perbarui penanda waktu 
            last_timestamp = current_timestamp;
        }
    }

    ...
}

Mengontrol Arah Gerakan Ular

Untuk bisa mengontrol arah gerakan ular, kita membuat sebuah variabel global tambahan bernama dir. Variabel ini memberitahu arah push() berikutnya, apakah ke kanan, bawah, kiri, atau atas. Arah ini akan ditentukan berdasarkan input tombol panah yang ditekan.

Pertama-tama, buat variabel global dir, dengan nilai awal ke arah kanan. VK_RIGHT adalah konstanta berisi kode untuk tombol panah kanan.

// Arah kepala saat awal permainan 
int dir = VK_RIGHT;

Sekarang kita modifikasi penentuan nilai x dan y untuk melakukan push() berdasarkan variabel dir. Lalu di dalam game loop, dilakukan juga pengecekan tombol yang sedang ditekan. Jika merupakan salah satu dari empat tombol panah di keyboard, maka ubah nilai dir.

(Perhatikan baris 17-30, 56-73)

/** 
    Program utama 
 */ 
int main() { 
    ...
 
    // Game loop. Bagian di dalam while akan dieksekusi terus menerus 
    while (true) { 

        ...
 
        // Snake bergerak setiap 200 ms (sesuai nilai variable snake_speed) 
        // Dihitung dengan membandingkan selisih waktu sekarang dengan waktu  
        // terakhir kali snake bergerak. 
        if (interval >= snake_speed) { 
            // Tentukan posisi x,y ke mana snake akan bergerak.  
            // Posisi dilihat dari koordinat segment kepala (head)  
            // dan arah (variable dir)
            int x, y;  
            switch (dir) { 
            case VK_LEFT: 
                x = head->x - 1; 
                y = head->y; 
                break; 
            case VK_RIGHT: 
                x = head->x + 1; 
                y = head->y; 
                break; 
            case VK_UP: 
                x = head->x; 
                y = head->y - 1; 
                break; 
            case VK_DOWN: 
                x = head->x; 
                y = head->y + 1; 
                break; 
            }
  
            // Pop ekor, lalu push segment ke depan head sehingga  
            // snake tampak bergerak maju.  
            pop(); 
            push(x, y); 

            // Tampilkan kondisi permainan saat ini di layar... 
 
            // Bersihkan layar 
            system("cls"); 
 
            // Cetak (render) snake di layar 
            display(); 

            // Perbarui penanda waktu 
            last_timestamp = current_timestamp;
        }
 
        // Ubah arah jika tombol panah ditekan 
        if (GetKeyState(VK_LEFT) < 0) { 
            dir = VK_LEFT; 
        } 
        if (GetKeyState(VK_RIGHT) < 0) { 
            dir = VK_RIGHT; 
        } 
        if (GetKeyState(VK_UP) < 0) { 
            dir = VK_UP; 
        } 
        if (GetKeyState(VK_DOWN) < 0) { 
            dir = VK_DOWN; 
        } 
 
        // Keluar dari program jika menekan tombol ESC 
        if (GetKeyState(VK_ESCAPE) < 0) { 
            return 0; 
        }
    }

    ...
}

Kita juga bisa menambahkan pengecekan untuk keluar dari program jika pemain menekan tombol ESC.

Coba jalankan lagi program, sekarang kita bisa menggerakan ular dengan bebas!

Collision Detection

Salah satu aspek yang penting dalam permainan ini adalah pengecekan apakah kepala ular bertabrakan dengan dinding atau dirinya sendiri. Di sini kita bisa melakukan pengecekan saat program memeroleh posisi x dan y yang baru, sebelum melakukan pop() dan push().

Jika posisi x berada di luar batasan 0-79 (panjang console) atau posisi y berada diluar batasan 0-24 (tinggi console), maka ular telah menabrak dinding, dan permainan berakhir. Sama seperti sebelum-sebelumnya, untuk nilai panjang dan lebar console bisa kita simpan di variabel global console_width dan console_height.

// Panjang console 
int console_width = 80; 
 
// Tinggi console 
int console_height = 25;

Pengecekan berikutnya yaitu mengecek apabila posisi x dan y sama dengan posisi salah satu node, yang artinya ular menabrak dirinya sendiri. Untuk mengeceknya, kita buat fungsi check_collision().

/** 
    Memeriksa apakah terdapat salah satu segment 
    snake (linked list) di koordinat x,y. 
    Return 0 artinya tidak bertumpuk, 1 artinya bertumpuk. 
 */ 
int check_collision(int x, int y) { 
    struct Segment *p = head; 
    while (p != NULL) { 
        if (p->x == x && p->y == y) { 
            return 1; 
        } 
        p = p->next; 
    } 
    return 0; 
}

Berikut ini baris-baris yang ditambahkan di main() untuk melakukan pengecekan tadi, serta tambahan baris yang dilakukan di luar game loop, setelah permainan berakhir (game over) (lihat baris 18-32, 44-48).

/** 
    Program utama 
 */ 
int main() { 
    ...
 
    // Game loop. Bagian di dalam while akan dieksekusi terus menerus 
    while (true) { 

        ...
        // Snake bergerak setiap 200 ms (sesuai nilai variable snake_speed) 
        // Dihitung dengan membandingkan selisih waktu sekarang dengan waktu  
        // terakhir kali snake bergerak. 
        if (interval >= snake_speed) { 

            ...
 
            // Jika posisi kepala (head) menabrak tembok pembatas,  
            // maka permainan berakhir (keluar dari game loop) 
            if (x < 0 || x >= console_width || y < 0 || y >= console_height) { 
                break; 
            } 
 
            // Jika posisi kepala (head) menabrak dirinya sendiri 
            // (posisi sama dengan salah satu segment), maka permainan  
            // berakhir (keluar dari game loop) 
            if (check_collision(x, y) == 1) { 
                break; 
            } 
             
            // Jika tidak terjadi tabrakan (collision), maka snake  
            // boleh bergerak maju.. 
            // Pop ekor, lalu push segment ke depan head sehingga  
            // snake tampak bergerak maju.  
            pop(); 
            push(x, y); 

            // Tampilkan kondisi permainan saat ini di layar... 
            ...
        }
        ...
    }

    // Setelah keluar dari game loop, berarti permainan berakhir (game over) 
    system("cls"); 
    printf("GAME OVER\n"); 
 
    printf("Press ENTER to exit..."); 
    getchar(); 

    // Pop semua segment 
    pop_all(); 
    return 0;
}

Jalankan program sekali lagi, lalu coba arahkan ular ke dinding. Untuk pengetesan tabrakan terhadap diri sendiri, bisa dilakukan dengan mengubah snake_size dengan nilai yang lebih besar, agar ular cukup panjang untuk menabrak dirinya sendiri.

Tampilan layar saat terjadi tabrakan. Permainan berakhir.
Tampilan layar saat terjadi tabrakan. Permainan berakhir.

Makanan!

Ini adalah bagian terakhir dari tutorial ini, makanan! Ular perlu melahap makanan untuk menjadi lebih panjang. Untuk itu, kita perlu menempatkan makanan di koordinat acak. Untuk menaruh koordinat makanan, kita tambahkan dua variabel global food_x dan food_y.

// Posisi makanan 
int food_x, food_y;

Meskipun makanan ditaruh secara acak, ada dua hal yang perlu diperhatikan:

  1. Makanan harus berada di dalam layar console berukuran 80×25.
  2. Makanan tidak boleh bertumpuk dengan ular saat ditempatkan.

Maka dari itu, kita buat sebuah fungsi place_food() untuk menaruh makanan dengan memerhatikan kedua syarat tersebut. Untuk syarat nomor 2, kita bisa memanfaatkan fungsi check_collision() yang baru saja dibuat.

/** 
    Taruh makanan secara acak, namun memastikan  
    makanan tidak bertumpuk dengan salah satu segment  
    snake (linked list) 
 */ 
void place_food() { 
    // Jika makanan bertumpuk dengan salah satu segment 
    // snake, ulangi penempatan makanan secara acak. 
    do { 
        food_x = rand() % console_width; 
        food_y = rand() % console_height; 
    } while (check_collision(food_x, food_y) == 1); 
}

Di awal program sebelum memasuki game loop, kita menempatkan makanan pertama. Berikutnya, makanan akan ditempatkan ulang jika posisi x dan y baru dari ular sama dengan koordinat makanan, yang artinya ular memakan makanan. Dalam hal ini, kita hanya melakukan push() tanpa melakukan pop(), sehingga jumlah node bertambah.

Jangan lupa pula untuk melakukan rendering makanan di layar.

Di samping itu, kita juga bisa menerapkan sistem penilaian, misalnya nilai bertambah 100 jika ular memakan makanan. Lalu pada akhir permainan (saat game over), nilai yang sudah terkumpul ditampilkan kepada pemain.

(Perhatikan baris 5-6, 12-13, 21-22, 41-53, 64-66, 80)

/** 
    Program utama 
 */ 
int main() { 
    // Randomize 
    srand(time(NULL)); 
 
    // Untuk menyimpan penanda waktu saat snake bergerak 
    struct timeb last_timestamp; 
    ftime(&last_timestamp); // Set nilai awal 
 
    // Untuk menyimpan nilai 
    int score = 0; 
 
    // Pertama-tama, push segment (node) ke kanan  
    // sebanyak 3 segment (sesuai nilai variable snake_size) 
    for (int i = 0; i < snake_size; i++) { 
        push(i, 0); 
    } 
 
    // Tempatkan makanan secara acak 
    place_food(); 
 
    // Game loop. Bagian di dalam while akan dieksekusi terus menerus 
    while (true) { 

        ...

        // Snake bergerak setiap 500 ms (sesuai nilai variable snake_speed) 
        // Dihitung dengan membandingkan selisih waktu sekarang dengan waktu  
        // terakhir kali snake bergerak. 
        if (interval >= snake_speed) { 

            ...

            // Jika tidak terjadi tabrakan (collision), maka snake  
            // boleh bergerak maju.. 
 
            // Pop ekor, lalu push segment ke depan head sehingga  
            // snake tampak bergerak maju.  
            // Namun jika posisi x,y ke mana kepala (head) snake akan  
            // bergerak berada di posisi makanan, tidak perlu pop  
            // sehingga segment bertambah panjang.  
            if (x == food_x && y == food_y) { 
                // Dalam hal snake memakan makanan, maka nilai bertambah 
                score += 100; 
 
                // Lalu makanan ditempatkan ulang secara acak 
                place_food(); 
            } 
            else { 
                pop(); 
            } 
            push(x, y); 
  
            // Tampilkan kondisi permainan saat ini di layar... 
 
            // Bersihkan layar 
            system("cls"); 
 
            // Cetak (render) snake di layar 
            display(); 
 
            // Cetak (render) makanan di layar 
            gotoxy(food_x, food_y); 
            printf("X"); 
 
            // Perbarui penanda waktu 
            last_timestamp = current_timestamp;
        } 

        ...

    } 
 
    // Setelah keluar dari game loop, berarti permainan berakhir (game over) 
    // Tampilkan nilai yang diraih pemain 
    system("cls"); 
    printf("GAME OVER\n"); 
    printf("Your score : %d\n\n", score); 
 
    printf("Press ENTER to exit..."); 
    getchar(); 

    ...

}

Selesai! Uji coba program untuk terakhir kalinya, dan game sudah siap dimainkan!

Layar game over menunjukkan permainan berakhir beserta nilai yang diperoleh.
Layar game over menunjukkan permainan berakhir beserta nilai yang diperoleh.

Source code program selengkapnya beserta program hasil kompilasi bisa diunduh di sini.

6 thoughts on “Membuat Game Snake dengan Bahasa C (Windows Console)

  1. kegunaan dari :
    interval, current_timestamp, dan last_timestamp untuk apa? di dalam codingan :
    interval = (current_timestamp.time – last_timestamp.time) + (current_timestamp.millitm – last_timestamp.millitm);

    • Pada contoh di atas nilai snake_speed = 200ms, artinya ular bergerak maju satu langkah setiap 200 milidetik sekali. Setiap kali ular bergerak maju, maka waktu dicatat di last_timestamp.

      Nah dengan menghitung selisih antara waktu saat ini (current_timestamp) dengan waktu terakhir ular bergerak (last_timestamp) maka kita bisa ketahui berapa lama waktu sudah berlalu (interval). Jika waktu yang berlalu sudah mencapai batas 200ms (snake_speed) maka ular bergerak maju.

      Ilustrasinya:
      00:00.000 Ular bergerak (last_timestamp = 00:00.000)
      00:00.050 Menunggu
      00:00.100 Menunggu
      00:00.200 Ular bergerak (last_timestamp = 00:00.200)
      00:00.300 Menunggu
      00:00.400 Ular bergerak (last_timestamp = 00:00.400)
      … dst

    • Oh 1000 itu untuk mengubah satuan ke dalam milidetik. Karena interval yang diperoleh dari selisih current_timestamp dan last_timestamp adalah dalam satuan detik. Sedangkan snake_speed dalam satuan milidetik. Supaya kedua variabel interval dan snake_speed bisa dibandingkan, maka satuannya disamakan terlebih dulu.

Leave a Comment