Setelah loop berjalan, kita perlu memastikan program tetap hidup ketika pengguna mengetik teks aneh atau angka di luar rentang. Pada bagian ini kita menambahkan dua lapis validasi: lapis format di baca_int (apakah teks dapat diparse sebagai integer?) dan lapis semantik di game_periksa (apakah angka berada di rentang [1, 100]?). Lapis ketiga adalah penanganan EOF — bila pengguna menekan Ctrl-D di tengah game, program harus keluar dengan rapi, bukan loop tak terbatas.
Konsep #
Ada tiga kemungkinan hasil dari pembacaan input via fgets + strtol:
- Berhasil:
fgetsmengembalikan buffer,strtolberhasil memparse angka,endptr != buffer. Kembalikan1dan tulis hasil ke*out. - Tidak terurai:
fgetsmengembalikan buffer, tetapistrtolgagal memparse (endptr == buffer). Kembalikan0. - EOF:
fgetsmengembalikanNULL(mis. pengguna menekan Ctrl-D atau pipe berakhir). Kembalikan-1.
Tanpa pembedaan ketiga kasus ini, program mudah jatuh ke loop tak terbatas. Bila fgets memulangkan NULL dan kita memperlakukannya sebagai “input tidak valid”, loop akan terus memanggil fgets yang selalu memulangkan NULL lagi — perulangan tak berakhir. Maka kita memerlukan kode khusus untuk EOF yang langsung keluar dari loop.
Validasi semantik (rentang) dilakukan di game_periksa. Pemanggil dapat membedakan TEBAK_INVALID dari TEBAK_KECIL/TEBAK_BESAR dan menampilkan pesan yang sesuai. Input invalid tidak mengurangi ronde — kita pakai trik ronde--; continue; agar iterasi itu diabaikan.
Implementasi #
Fungsi baca_int yang menangani tiga kasus:
static int baca_int(const char *prompt, int *out) {
char buffer[64];
printf("%s", prompt);
if (fgets(buffer, sizeof(buffer), stdin) == 0) return -1; /* EOF */
char *sisa = 0;
long nilai = strtol(buffer, &sisa, 10);
if (sisa == buffer) return 0; /* tidak ada angka terbaca */
*out = (int)nilai;
return 1;
}Loop utama yang menangani tiga kode balik:
for (int ronde = 1; ronde <= GAME_MAKS_RONDE; ronde++) {
int tebak = 0;
int hasil_baca = baca_int("Tebakan Anda: ", &tebak);
if (hasil_baca == -1) { /* EOF */
printf("\nInput berakhir (EOF). Berhenti.\n");
break;
}
if (hasil_baca == 0) { /* tidak terurai */
printf("Input tidak valid. Coba lagi.\n");
ronde--;
continue;
}
TebakStatus s = game_periksa(tebak, rahasia, ronde);
printf("Ronde %d: %s\n", ronde, status_pesan(s));
if (s == TEBAK_INVALID) { ronde--; continue; }
if (s == TEBAK_BENAR) {
menang = 1;
ronde_menang = ronde;
break;
}
}Kode Lengkap #
Fungsi baca_int dan status_pesan di main.c:
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.";
}
}Pengujian #
Uji input invalid dan EOF:
gcc -std=c11 -Wall -Wextra -o /tmp/p2b3 main.c game.c
printf 'bukan-angka\n0\n150\n50\n' | /tmp/p2b3Output yang Dihasilkan (angka rahasia berbeda tiap detik):
=== Game Tebak Angka ===
Saya menyimpan angka antara 1 sampai 100.
Anda punya 10 ronde untuk menebak.
Tebakan Anda: Input tidak valid. Coba lagi.
Tebakan Anda: Ronde 1: Tebakan di luar rentang yang sah.
Tebakan Anda: Ronde 2: Tebakan di luar rentang yang sah.
Tebakan Anda: Ronde 3: Tebakan terlalu besar.
Input berakhir (EOF). Berhenti.
Maaf, ronde habis. Angka rahasia: 37Uji dengan EOF lebih awal:
printf '' | /tmp/p2b3Output yang Dihasilkan:
=== Game Tebak Angka ===
Saya menyimpan angka antara 1 sampai 100.
Anda punya 10 ronde untuk menebak.
Tebakan Anda:
Input berakhir (EOF). Berhenti.
Maaf, ronde habis. Angka rahasia: 96Mari kita telaah logika tiga kode balik pada baca_int:
- Baris
if (fgets(...) == 0) return -1;:fgetsmemulangkanNULLsaat EOF atau error. Kode-1memberi sinyal eksplisit ke pemanggil bahwa tidak ada lagi input — berbeda dari0yang berarti “teks ada tapi tidak terurai”. - Baris
long nilai = strtol(buffer, &sisa, 10);: kita pakailongagar dapat menangani angka besar tanpa overflow sebelum cast keint. Base10untuk desimal. - Baris
if (sisa == buffer) return 0;:sisadi-set olehstrtolke alamat karakter pertama yang tidak terparse. Bilasisa == buffer(alamat awal), berarti tidak ada karakter angka sama sekali — input seperti “bukan-angka”. - Baris
*out = (int)nilai;: castlongkeintaman dalam rentang game (1-100). Untuk input di luarINT_MAX, cast ini bisa overflow — pada game kita tidak relevan karenagame_periksaakan menolaknya.
Bagian 4 akan menghitung skor berbasis ronde kemenangan.
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;
}