Membuat Permainan Congklak dengan Bahasa C (Bagian 3)

Mereset kembali papan untuk babak berikutnya

Melanjutkan tutorial permainan congklak dengan bahasa C, rupanya permainan ini tidak berakhir hanya dalam satu babak. Setelah babak pertama selesai, biji-bijian yang telah terkumpul di rumah disebarkan kembali ke setiap lubang kecil milik pemain, masing-masing sebanyak 7 biji. Karena biji yang terkumpul tidak sama, maka akan ada lubang yang tidak dapat terisi. Jika lubang tidak dapat diisi dengan 7 biji, lubang tersebut dianggap hangus. Sisa biji dikembalikan ke rumah masing-masing.

Aturan main untuk babak selanjutnya serupa, dengan satu tambahan peraturan bahwa lumbung yang sudah hangus tidak boleh diisi lagi (dilewati).

Untuk itu kita akan mengubah fungsi reset_papan(), yang semula menyebarkan 49 biji ke lubang pemain, kini dibuat untuk menyebarkan biji sesuai jumlah yang ditentukan pada parameter.

/**
 * Mereset lubang dengan 7 biji pada masing-masing lubang kecuali 
 * lubang besar.
 * 
 * @param biji1 Jumlah biji pemain 1
 * @param biji2 Jumlah biji pemain 2
 */
void reset_papan(int biji1, int biji2) {
  // Isi 7 biji pada lubang kecil pemain 1
  for (int i = 6; i >= 0; i--) {
    // Pindahkan 7 biji ke lubang
    // Lubang yang sudah hangus (-1) tidak boleh digunakan lagi
    if ((biji1 >= 7) && (lubang[i] >= 0)) {
      lubang[i] = 7;
      biji1 -= 7;
    }
    // Jika biji kurang dari 7, lubang tidak diisi namun hangus.
    // Beri nilai -1 sebagai penanda.
    else {
      lubang[i] = -1;
    }
  }
  
  // Taruh sisa biji ke lubang besar
  lubang[7] = biji1;
  
  // Isi 7 biji pada lubang kecil pemain 2
  for (int i = 14; i >= 8; i--) {
    // Pindahkan 7 biji ke lubang
    if ((biji2 >= 7) && (lubang[i]) >= 0) {
      lubang[i] = 7;
      biji2 -= 7;
    }
    // Jika biji kurang dari 7, lubang tidak diisi namun hangus.
    // Beri nilai -1 sebagai penanda.
    else {
      lubang[i] = -1;
    }
  }
  
  // Taruh sisa biji ke lubang besar
  lubang[15] = biji2;
}

Penulisan for loop dibalik karena biji yang lebih dekat dengan lubang besar harus diisi terlebih dahulu.

Pada program ini kita akan beri nilai -1 pada lumbung yang hangus sebagai penanda. Selain itu, lumbung yang sudah hangus di babak sebelumnya juga tidak boleh diisi lagi oleh biji sekalipun masih ada sisa biji yang cukup di rumah pemain. Oleh sebab itu kita buat pengecekan jika sisa biji ada 7 atau lebih dan lumbung yang akan diisi belum terbakar (nilainya lebih dari -1), baru lumbung diisi, selain itu lumbung hangus.

Sebagai contoh, jika pada akhir babak sebelumnya rumah pemain 1 berisi 40 biji, sedangkan rumah pemain 2 berisi 58 biji, maka penyebarannya menjadi sbb.

7777777
9ABCDEFG5
-1-177777

Mencetak papan dengan lubang yang hangus

Lubang hangus yang bernilai -1 ini tidak ingin kita cetak angkanya. Jadi fungsi cetak_papan() perlu kita ubah sedikit. Jika nilainya -1 kita ganti dengan tanda XX. Perhatikan baris 19-25 di bawah ini.

/**
 * Bersihkan layar dan cetak papan congklak.
 */
void cetak_papan() {
  // Contoh papan dan index masing-masing lubang
  // ______A____B____C____D____E____F____G_____
  //
  //     (14) (13) (12) (11) (10) ( 9) ( 8)    
  // (15)                                  ( 7)
  //     ( 0) ( 1) ( 2) ( 3) ( 4) ( 5) ( 6)
  
  system("clear");
  
  printf("______A____B____C____D____E____F____G_____\n\n");
  
  // Lubang kecil di atas
  printf("    ");
  for (int i = 14; i >= 8; i--) {
    // Jika lubang hangus (-1) cetak tanda XX
    if (lubang[i] == -1) {
      printf("(XX) ");
    }
    else {
      printf("(%2d) ", lubang[i]);
    }
  }
  printf("\n");
  
  // Lubang besar di kiri dan kanan
  printf("(%2d)                                  (%2d)\n", lubang[15], lubang[7]);
  
  // Lubang kecil di bawah
  printf("    ");
  for (int i = 0; i <= 6; i++) {
    // Jika lubang hangus (-1) cetak tanda XX
    if (lubang[i] == -1) {
      printf("(XX) ");
    }
    else {
      printf("(%2d) ", lubang[i]);
    }
  }
  printf("\n\n");
}

Pada program utama, pemanggilan fungsi reset_papan() sekarang harus disertai jumlah biji, yaitu 49. Kita juga bisa mengetes apakah kode kita berfungsi dengan baik. Coba ubah-ubah nilai parameternya dan lihat bagaimana hasilnya.

int main(int argc, char **argv)
{
  reset_papan(49, 49);
  alur_permainan();
  
  // Tentukan pemenangnya
  if (lubang[7] > lubang[15]) {
    printf("PEMAIN 1 MENANG\n");
  }
  else if (lubang[15] > lubang[7]) {
    printf("PEMAIN 2 MENANG\n");
  }
  else {
    printf("SERI\n");
  }
  getchar();
}

Lubang yang hangus tidak boleh diisi

Ingat bahwa peraturan tambahan pada babak selanjutnya adalah bahwa lumbung yang sudah hangus tidak boleh diisi. Oleh karena itu kita perlu mengubah dua fungsi aksi yang kita punya.

Pertama fungsi distribusi_biji(). Sebelumnya kita punya pengecekan jika index jatuh di rumah lawan, makan index kita geser satu. Kini kita tambahkan pengecekan jika index jatuh di lumbung hangus. Selain itu, ada kemungkinan lumbung hangus atau rumah lawan berada berdampingan. Jika demikian kita perlu geser terus sampai menemui lumbung yang boleh diisi.

Untuk itu, ubah if menjadi perulangan while. Lihat baris 25-31 berikut.

/**
 * Mengambil isi lubang dan mendistribusikan biji ke lubang di 
 * sebelahnya berlawanan jarum jam hingga habis.
 * 
 * @param index Indeks lubang yang akan diambil dan didistribusikan.
 * @param pemain Nomor urut pemain
 * @return Indeks lubang di mana biji terakhir ditaruh.
 */
int distribusi_biji(int index, int pemain) {
  // Pindahkan biji ke genggaman
  int genggaman = lubang[index];
  lubang[index] = 0;
  
  // Perbaharui papan dan beri jeda waktu sebelum melanjutkan
  cetak_papan();
  cetak_genggaman(index, genggaman);
  usleep(50000);
  
  // Distribusikan biji berlawanan arah jarum jam
  while (genggaman > 0) {
    // Geser index ke lubang di sampingnya
    index = (index + 1) % 16;
    
    // Lubang yang hangus tidak boleh diisi
    while (
      ((pemain == 1) && (index == 15)) || 
      ((pemain == 2) && (index == 7)) ||
      (lubang[index] == -1)
    ) {
      index = (index + 1) % 16;
    }
    
    // Pindahkan satu biji dari genggaman ke lubang
    lubang[index]++;
    genggaman--;

    // Perbaharui papan dan beri jeda waktu sebelum melanjutkan
    cetak_papan();
    cetak_genggaman(index, genggaman);
    usleep(50000);
  }
  
  return index;
}

Aksi lain yang perlu diubah adalah menembak_biji(). Lubang yang bernilai -1 tidak dapat ditembak.

/**
 * Mengambil isi lubang dan memindahkan semuanya ke rumah milik pemain.
 * 
 * @param index Indeks lubang yang akan diambil.
 * @param pemain Nomor urut pemain.
 */
void menembak_biji(int index, int pemain) {
  // Lubang bernilai -1 tidak dapat ditembak
  if (lubang[index] == -1) {
    return;
  }
  
  // Pindahkan biji ke genggaman
  int genggaman = lubang[index];
  lubang[index] = 0;
  
...dst

Lubang yang hangus tidak boleh dipilih

Pada fungsi pilih_lubang() perlu dibuat pengecekan tambahan, bahwa lubang yang nilainya -1 tidak lolos validasi input. Lihat baris 30 berikut.

/**
 * Meminta pemain memilih lubang A..G dan mengembalikan indeks lubang 
 * yang dipilih.
 * 
 * @param pemain Nomor urut pemain
 * @return Indeks lubang yang dipilih
 */
int pilih_lubang(int pemain) {
  char input;
  int index;
  
  // Minta input dari pemain dan lakukan validasi
  do {
    printf("Pilih lubang [A..G] ");
    scanf("%c", &input);
    flush_input();
  } while (tolower(input) < 'a' || tolower(input) > 'g');
  
  // Pemetaan input huruf menjadi indeks sesuai nomor pemain
  // Huruf   : A   B   C   D   E   F   G
  // Pemain 1: 0   1   2   3   4   5   6
  // Pemain 2: 14  13  12  11  10  9   8
  if (pemain == 1) {
    index = input - 'a';
  } else {
    index = 14 - (input - 'a');
  }
  
  // Jika lubang yang dipilih kosong atau hangus, ulangi permintaan input
  if (lubang[index] <= 0) {
    return pilih_lubang(pemain);
  } else {
    return index;
  }
}

Memulai babak berikutnya

Karena permainan akan dimainkan lebih dari sekali, maka alur permainan ini kita pindahkan ke dalam perulangan. Mari kita buat sebuah fungsi baru untuk mengatur jalannya babak. Fungsi diberi nama alur_babak().

Cara kerja fungsi ini serupa dengan alur_permainan() secara garis besar:

  1. Reset papan
  2. Jalankan alur_permainan()
  3. Periksa apakah berlanjut ke babak berikutnya
    • Jika seluruh lumbung milik salah satu pemain hangus, permainan berakhir
    • Jika pemain masih memiliki lumbung yang bisa diisi 7 biji, lanjut ke babak berikutnya (ulangi tahap 1)
/**
 * Kontrol alur babak. Babak terakhir selesai jika semua lumbung milik 
 * salah satu pemain hangus terbakar (tidak dapat terisi 7 biji).
 */
void alur_babak() {
  int babak = 0;
  
  // Pada awal permainan, beri 49 biji di rumah masing-masing
  lubang[7] = 49;
  lubang[15] = 49;
  
  do {
    // Reset papan dan tampilkan babak
    reset_papan(lubang[7], lubang[15]);
    cetak_papan();
    babak++;
    printf("BABAK %d\n", babak);
    usleep(1000000);
    
    // Kontrol alur permainan
    alur_permainan();
    
    // Beri jeda untuk menampilkan posisi akhir sebelum melanjutkan 
    // babak berikutnya
    usleep(1000000);
  } while (!cek_babak_selesai());
}

Mengecek apakah babak bisa berlanjut atau tidak sangatlah mudah. Jika biji di rumah pemain kurang dari 7, artinya tidak ada satu pun lumbung yang bisa diisi. Berarti permainan selesai.

/**
 * Periksa apakah babak sudah final (selesai) atau masih berlanjut ke 
 * babak berikutnya.
 */
int cek_babak_selesai() {
  // Semua lubang tidak dapat diisi jika biji di rumah kurang dari 7
  if ((lubang[7] < 7) || (lubang[15] < 7)) {
    return 1;
  }
  else {
    return 0;
  }
}

Sekarang ubah sedikit fungsi utama untuk menjalankan permainan melalui alur_babak().

int main(int argc, char **argv)
{
  alur_babak();
  printf("PERMAINAN SELESAI\n");
  
  // Tentukan pemenangnya
  if (lubang[7] > lubang[15]) {
    printf("PEMAIN 1 MENANG\n");
  }
  else if (lubang[15] > lubang[7]) {
    printf("PEMAIN 2 MENANG\n");
  }
  else {
    printf("SERI\n");
  }
  getchar();
  return 0;
}

Siapa mulai duluan?

Giliran tidak selalu dimulai oleh pemain 1. Pada babak berikutnya, pemain yang kalah di babak sebelumnya memulai giliran lebih dulu. Karena itu kita ubah sedikit fungsi alur_permainan() untuk menerima parameter pemain yang memulai pertama kali. Caranya sederhana, pindahkan deklarasi variabel int pemain = 1; menjadi parameter.

/**
 * Kontrol alur permainan. Fungsi ini akan berputar terus menerus hingga 
 * permainan selesai.
 * 
 * @param pemain Nomor urut pemain yang mulai giliran pertama.
 */
void alur_permainan(int pemain) {
  int index = -1;
  
...dst

Pada fungsi alur_babak(), tambahkan bagian kode untuk menentukan pemain mana yang mulai duluan. Lihat baris 7, 22-37 pada kode di bawah ini.

/**
 * Kontrol alur babak. Babak terakhir selesai jika semua lumbung milik 
 * salah satu pemain hangus terbakar (tidak dapat terisi 7 biji).
 */
void alur_babak() {
  int babak = 0;
  int pemain = 1;
  
  // Pada awal permainan, beri 49 biji di rumah masing-masing
  lubang[7] = 49;
  lubang[15] = 49;
  
  do {
    // Reset papan dan tampilkan babak
    reset_papan(lubang[7], lubang[15]);
    cetak_papan();
    babak++;
    printf("BABAK %d\n", babak);
    usleep(1000000);
    
    // Kontrol alur permainan
    alur_permainan(pemain);
    
    // Periksa pemenang babak ini dan berikan giliran pada pemain yang 
    // kalah untuk babak berikutnya
    // Jika pemain 1 menang, berikan ke pemain 2
    if (lubang[7] > lubang[15]) {
      pemain = 2;
    }
    // Jika pemain 2 menang, berikan ke pemain 1
    else if (lubang[15] > lubang[7]) {
      pemain = 1;
    }
    // Jika seri, beri ke pemain yang berbeda dari babak ini
    else {
      pemain = (pemain % 2) + 1;
    }
    
    // Beri jeda untuk menampilkan posisi akhir sebelum melanjutkan 
    // babak berikutnya
    usleep(1000000);
  } while (!cek_babak_selesai());
}

Penyelesaian alur permainan

Saat alur permainan selesai, ada kode untuk mengumpulkan semua biji dari lumbung ke rumah tiap pemain. Kode ini terletak di akhir fungsi alur_permainan(). Baris ini perlu kita ubah untuk tidak mengumpulkan biji dari lumbung hangus. Karena nilainya -1 sehingga bisa mempengaruhi jumlah keseluruhan biji.

/**
 * Kontrol alur permainan. Fungsi ini akan berputar terus menerus hingga 
 * permainan selesai.
 * 
 * @param pemain Nomor urut pemain yang mulai giliran pertama.
 */
void alur_permainan(int pemain) {
  int index = -1;
  
  // Kontrol alur permainan
  do {

........................................................................

  } while (cek_permainan_selesai() == 0);
  
  // Jika permainan selesai, gabungkan semua biji di lubang sisi pemain 
  // ke rumah miliknya
  for (int i = 0; i <= 6; i++) {
    // Jangan proses lumbung hangus
    if (lubang[i] == -1) {
      continue;
    }
    lubang[7] += lubang[i];
    lubang[i] = 0;
  }
  for (int i = 8; i <= 14; i++) {
    // Jangan proses lumbung hangus
    if (lubang[i] == -1) {
      continue;
    }
    lubang[15] += lubang[i];
    lubang[i] = 0;
  }
  
  cetak_papan();
}

Selamat! Permainan congklak kamu sudah jadi. Bagaimana, mudah bukan?

Untuk kamu yang suka tantangan, cobalah buat fitur pemain komputer sehingga permainan congklak bisa dimainkan seorang diri melawan komputer. Pembahasan mengenai pemain komputer akan dilanjutkan pada kesempatan berikutnya.

Unduh kode program di sini.

Tulis Komentar