Setelah pemain bisa menang, kita perlu sistem skor yang memberi penghargaan pada tebakan cepat. Pada bagian ini kita menambahkan dua fungsi: game_hitung_skor (versi cepat tanpa validasi) dan game_hitung_skor_aman (versi yang menolak input tidak valid). Pemisahan ini mengajarkan prinsip penting: API internal boleh mempercayai pemanggilnya, tetapi API yang menghadap pengguna atau test harus defensif.
Konsep #
Formula skor yang kita pilih sederhana: skor penuh 100 bila menang di ronde 1, lalu berkurang 10 poin per ronde tambahan. Secara matematis: skor = 100 - (ronde_menang - 1) * 10. Menang di ronde 5 menghasilkan 100 - 40 = 60, dan menang di ronde 10 menghasilkan 100 - 90 = 10. Bila ronde menang melebihi 10, skor menjadi negatif — kita potong ke 0 agar tidak absurd.
Terdapat dua pendekatan validasi:
- Versi cepat (
game_hitung_skor): mempercayai pemanggil memberi ronde yang valid (1-10). Cocok untuk pemanggil internal yang sudah diverifikasi. - Versi aman (
game_hitung_skor_aman): memeriksa rentang input dan memulangkan 0 bila di luar. Cocok untuk pemanggil eksternal sepertimain.cyang menerima input dari pengguna.
Pemisahan dua fungsi ini bukan redundansi — ini pengakuan bahwa kontrak pemanggil berbeda. Test runner dapat memverifikasi keduanya: fungsi aman menangani input jahat, fungsi cepat mengasumsikan input sudah baik.
Implementasi #
Fungsi skor di game.c:
int game_hitung_skor(int ronde_menang) {
int pengurangan = (ronde_menang - 1) * 10;
int skor = GAME_SKOR_AWAL - pengurangan;
if (skor < 0) skor = 0;
return skor;
}
int game_hitung_skor_aman(int ronde_menang) {
if (ronde_menang < 1 || ronde_menang > GAME_MAKS_RONDE) {
return 0;
}
return game_hitung_skor(ronde_menang);
}Pemanggilan di main.c:
if (menang) {
int skor = game_hitung_skor_aman(ronde_menang);
printf("\nSelamat! Angka rahasia: %d\n", rahasia);
printf("Anda menang di ronde %d dengan skor %d.\n", ronde_menang, skor);
}Kode Lengkap #
Deklarasi di game.h:
int game_hitung_skor(int ronde_menang);
int game_hitung_skor_aman(int ronde_menang);Implementasi di game.c:
int game_hitung_skor(int ronde_menang) {
int pengurangan = (ronde_menang - 1) * 10;
int skor = GAME_SKOR_AWAL - pengurangan;
if (skor < 0) skor = 0;
return skor;
}
int game_hitung_skor_aman(int ronde_menang) {
if (ronde_menang < 1 || ronde_menang > GAME_MAKS_RONDE) {
return 0;
}
return game_hitung_skor(ronde_menang);
}Pengujian #
Jalankan program dengan urutan tebakan yang berakhir menang. Karena angka rahasia acak, kita perlu percobaan berulang untuk mendapatkan kemenangan:
gcc -std=c11 -Wall -Wextra -o /tmp/p2b4 main.c game.c
printf '50\n25\n12\n6\n3\n1\n2\n4\n5\n7\n' | /tmp/p2b4Output yang Dihasilkan (bila rahasia = 7):
=== Game Tebak Angka ===
Saya menyimpan angka antara 1 sampai 100.
Anda punya 10 ronde untuk menebak.
Tebakan Anda: Ronde 1: Tebakan terlalu besar.
Tebakan Anda: Ronde 2: Tebakan terlalu besar.
Tebakan Anda: Ronde 3: Tebakan terlalu besar.
Tebakan Anda: Ronde 4: Tebakan terlalu kecil.
Tebakan Anda: Ronde 5: Tebakan terlalu kecil.
Tebakan Anda: Ronde 6: Tebakan terlalu kecil.
Tebakan Anda: Ronde 7: Tebakan terlalu kecil.
Tebakan Anda: Ronde 8: Tebakan terlalu kecil.
Tebakan Anda: Ronde 9: Tebakan terlalu kecil.
Tebakan Anda: Ronde 10: Tebakan benar!
Selamat! Angka rahasia: 7
Anda menang di ronde 10 dengan skor 10.Verifikasi formula skor dengan cepat via test runner (akan ditulis penuh di Bagian 5):
cat > /tmp/skor_test.c << 'EOF'
#include "game.h"
#include <stdio.h>
#include <assert.h>
int main(void) {
assert(game_hitung_skor_aman(1) == 100);
assert(game_hitung_skor_aman(5) == 60);
assert(game_hitung_skor_aman(10) == 10);
assert(game_hitung_skor_aman(0) == 0);
assert(game_hitung_skor_aman(99) == 0);
printf("Semua assertion skor lulus.\n");
return 0;
}
EOF
gcc -std=c11 -Wall -Wextra -I/home/z/my-project/content/c/proyek-c/proyek-02-game-tebak-angka/ -o /tmp/skor_test /tmp/skor_test.c /home/z/my-project/content/c/proyek-c/proyek-02-game-tebak-angka/game.c && /tmp/skor_testOutput yang Dihasilkan:
Semua assertion skor lulus.Mari kita telaah baris-baris kunci pada game_hitung_skor_aman:
- Baris
if (ronde_menang < 1 || ronde_menang > GAME_MAKS_RONDE): cek rentang dilakukan di awal. Bila input tidak valid, fungsi langsung memulangkan0tanpa menjalankan logika skor. - Baris
return game_hitung_skor(ronde_menang);: setelah validasi, kita mendelegasikan ke versi cepat. Pola ini disebut thin wrapper — fungsi aman membungkus fungsi cepat tanpa duplikasi logika. - Baris
int pengurangan = (ronde_menang - 1) * 10;digame_hitung_skor:ronde_menang - 1karena ronde 1 tidak mengurangi poin. Hasilnya berupa skor menurun secara linear. - Baris
if (skor < 0) skor = 0;: clamp ke 0 untuk mencegah skor negatif yang tidak masuk akal. Tanpa clamp,game_hitung_skor(15)akan menghasilkan-40. - Baris
assert(...)di test: macroassertdari<assert.h>membatalkan program bila kondisi salah. Cocok untuk invariant internal; untuk validasi input pengguna, gunakan cek eksplisit dengan return code.
Bagian 5 akan menyusun ulang seluruh kode menjadi modul final dengan test runner.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
game.c
Unduh berkas/* game.c -- implementasi logika game tebak angka. */
#include "game.h"
#include <stdlib.h>
#include <time.h>
void game_init(void) {
/* Seed generator dengan waktu saat ini. */
srand((unsigned int)time(NULL));
}
int game_angka_rahasia(void) {
/* rand() % N menghasilkan nilai [0, N-1]; kita geser ke rentang yang diinginkan. */
int rentang = GAME_TEBAK_MAKS - GAME_TEBAK_MIN + 1;
return GAME_TEBAK_MIN + (rand() % rentang);
}
TebakStatus game_periksa(int tebak, int rahasia, int ronde_kini) {
if (ronde_kini > GAME_MAKS_RONDE) {
return TEBAK_HABIS;
}
if (tebak < GAME_TEBAK_MIN || tebak > GAME_TEBAK_MAKS) {
return TEBAK_INVALID;
}
if (tebak == rahasia) {
return TEBAK_BENAR;
}
if (tebak < rahasia) {
return TEBAK_KECIL;
}
return TEBAK_BESAR;
}
int game_hitung_skor(int ronde_menang) {
/* Setiap ronde setelah ronde 1 mengurangi 10 poin. */
int pengurangan = (ronde_menang - 1) * 10;
int skor = GAME_SKOR_AWAL - pengurangan;
if (skor < 0) skor = 0;
return skor;
}
int game_hitung_skor_aman(int ronde_menang) {
/* Versi yang menolak input tidak valid. */
if (ronde_menang < 1 || ronde_menang > GAME_MAKS_RONDE) {
return 0;
}
return game_hitung_skor(ronde_menang);
}
const char *game_rentang_teks(void) {
static char buffer[32];
snprintf(buffer, sizeof(buffer), "%d sampai %d",
GAME_TEBAK_MIN, GAME_TEBAK_MAKS);
return buffer;
}
main.c
Unduh berkas/* main.c -- titik masuk game tebak angka.
*
* Memisahkan logika (game.c) dari I/O (main.c) supaya game.c dapat diuji
* tanpa menyimulasikan keyboard.
*/
#include "game.h"
#include <stdio.h>
#include <stdlib.h>
/* Membaca satu integer dari stdin.
* Mengembalikan 1 bila berhasil, 0 bila input tidak terurai, -1 bila EOF.
*/
static int baca_int(const char *prompt, int *out) {
char buffer[64];
printf("%s", prompt);
if (fgets(buffer, sizeof(buffer), stdin) == 0) return -1;
char *sisa = 0;
long nilai = strtol(buffer, &sisa, 10);
if (sisa == buffer) return 0;
*out = (int)nilai;
return 1;
}
static const char *status_pesan(TebakStatus s) {
switch (s) {
case TEBAK_BENAR: return "Tebakan benar!";
case TEBAK_KECIL: return "Tebakan terlalu kecil.";
case TEBAK_BESAR: return "Tebakan terlalu besar.";
case TEBAK_INVALID:return "Tebakan di luar rentang yang sah.";
case TEBAK_HABIS: return "Ronde sudah habis.";
default: return "Status tidak dikenal.";
}
}
int main(void) {
game_init();
int rahasia = game_angka_rahasia();
printf("=== Game Tebak Angka ===\n");
printf("Saya menyimpan angka antara %s.\n", game_rentang_teks());
printf("Anda punya %d ronde untuk menebak.\n\n", GAME_MAKS_RONDE);
int menang = 0;
int ronde_menang = 0;
for (int ronde = 1; ronde <= GAME_MAKS_RONDE; ronde++) {
int tebak = 0;
int hasil_baca = baca_int("Tebakan Anda: ", &tebak);
if (hasil_baca == -1) {
printf("\nInput berakhir (EOF). Berhenti.\n");
break;
}
if (hasil_baca == 0) {
printf("Input tidak valid. Coba lagi.\n");
ronde--; /* jangan pakai ronde untuk input invalid */
continue;
}
TebakStatus s = game_periksa(tebak, rahasia, ronde);
printf("Ronde %d: %s\n", ronde, status_pesan(s));
if (s == TEBAK_INVALID) {
ronde--; /* input invalid tidak mengurangi ronde */
continue;
}
if (s == TEBAK_BENAR) {
menang = 1;
ronde_menang = ronde;
break;
}
}
if (menang) {
int skor = game_hitung_skor_aman(ronde_menang);
printf("\nSelamat! Angka rahasia: %d\n", rahasia);
printf("Anda menang di ronde %d dengan skor %d.\n", ronde_menang, skor);
} else {
printf("\nMaaf, ronde habis. Angka rahasia: %d\n", rahasia);
}
return 0;
}
tests.c
Unduh berkas/* tests.c -- rangkaian test untuk modul game. */
#include "game.h"
#include <stdio.h>
static int lulus = 0;
static int gagal = 0;
#define PERIKSA(kondisi, nama) \
do { \
if (kondisi) { lulus++; printf("[LULUS] %s\n", nama); } \
else { gagal++; printf("[GAGAL] %s\n", nama); } \
} while (0)
static void test_rentang_rahasia(void) {
/* Karena angka acak, kita tes sebanyak 1000 kali untuk memastikan
* seluruh hasil berada dalam rentang sah.
*/
game_init();
int semua_sah = 1;
for (int i = 0; i < 1000; i++) {
int r = game_angka_rahasia();
if (r < GAME_TEBAK_MIN || r > GAME_TEBAK_MAKS) {
semua_sah = 0;
break;
}
}
PERIKSA(semua_sah, "Angka rahasia selalu di rentang sah");
}
static void test_periksa_benar(void) {
TebakStatus s = game_periksa(50, 50, 1);
PERIKSA(s == TEBAK_BENAR, "Tebakan 50 dengan rahasia 50 -> BENAR");
}
static void test_periksa_kecil(void) {
TebakStatus s = game_periksa(30, 50, 1);
PERIKSA(s == TEBAK_KECIL, "Tebakan 30 dengan rahasia 50 -> KECIL");
}
static void test_periksa_besar(void) {
TebakStatus s = game_periksa(70, 50, 1);
PERIKSA(s == TEBAK_BESAR, "Tebakan 70 dengan rahasia 50 -> BESAR");
}
static void test_periksa_invalid(void) {
TebakStatus s = game_periksa(0, 50, 1);
PERIKSA(s == TEBAK_INVALID, "Tebakan 0 di luar rentang -> INVALID");
}
static void test_periksa_habis(void) {
TebakStatus s = game_periksa(50, 50, GAME_MAKS_RONDE + 1);
PERIKSA(s == TEBAK_HABIS, "Ronde > MAKs -> HABIS");
}
static void test_skor_ronde1(void) {
int skor = game_hitung_skor_aman(1);
PERIKSA(skor == GAME_SKOR_AWAL, "Menang ronde 1 -> skor penuh");
}
static void test_skor_ronde5(void) {
int skor = game_hitung_skor_aman(5);
PERIKSA(skor == 60, "Menang ronde 5 -> skor 60");
}
static void test_skor_invalid(void) {
int skor = game_hitung_skor_aman(0);
PERIKSA(skor == 0, "Ronde 0 -> skor 0");
skor = game_hitung_skor_aman(GAME_MAKS_RONDE + 1);
PERIKSA(skor == 0, "Ronde > MAKs -> skor 0");
}
int main(void) {
test_rentang_rahasia();
test_periksa_benar();
test_periksa_kecil();
test_periksa_besar();
test_periksa_invalid();
test_periksa_habis();
test_skor_ronde1();
test_skor_ronde5();
test_skor_invalid();
printf("\nRingkasan: %d lulus, %d gagal\n", lulus, gagal);
return gagal == 0 ? 0 : 1;
}