• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Gra w zgadywanie liczb pseudolosowych - nie mogę znaleźć błędu

VPS Starter Arubacloud
+1 głos
955 wizyt
pytanie zadane 31 października 2020 w C i C++ przez rain.deer Początkujący (430 p.)

Cześć,

napotkałam nowy problem w swoich początkach z c++. Napisałam prostą grę, która ma pozwalać na zgadywanie liczby wylosowanej z zakresu 1 - 1000. Program (zgodnie z instrukcjami, które są zapisane w pierwszych linijkach) powinien być zabezpieczony przed możliwością wprowadzenia błędnych wartości liczbowych. Wprowadziłam więc instrukcję: "bool bCzyBlad = cin.fail();", która w przypadku wprowadzenia złej wartości (np. liter) powinna to wychwycić. Dalej zrobiłam pętlę if, która w przypadku takiej złej wartości powinna wypisywać na ekran: "Wpisałeś wartość spoza wyznaczonego zakresu" i przerywać działanie programu. Ale.. najpierw pojawił się problem, że nawet jak wpisana liczba była złożona z samych cyfr pojawiał się komunikat "Wpisałeś wartość spoza wyznaczonego zakresu". A gdy próbowałam jakoś naprawić program i zrobić tak, żeby w przypadku, gdy liczba jest poprawna, program działał i pozwalał na dalsze zgadywanie, całkiem się wysypał. Nie wiem co jest nie tak. sad

/* Twoim zadaniem domowym jest napisanie prostej gry, która ma działać następująco :
1. Program losuje liczbę z przedziału od 1 do 1000.
2. Użytkownik zgaduje liczbę, która została wylosowana.
3. Jeżeli podana liczba jest za duża(za mała) gra wypisuje stosowny komunikat i powraca do kroku 2.
4. Jeżeli gracz trafi liczbę wylosowaną to progam kończy działanie, wypisując na ekran wylosowaną liczbę oraz liczbę 'strzałów', które oddał gracz.
Gra ma być zabezpieczona przed możliwością wprowadzenia błędnych wartości liczbowych.*/
#include <iostream>
#include <time.h>
#include <cstdlib>
using namespace std;
int main()
{
	cout << "Losowanie" << endl;
	cout << "Program wylosuje teraz liczbe z przedzialu 1 - 1000. Zgadnij liczbe." << endl;
	srand(time(NULL));
	int wytypowana_liczba;
	int wylosowana_liczba = (rand() % 999) + 1;
	cout << wylosowana_liczba << endl;		
	cout << "Zgadnij wylosowana liczbe " << endl;
	cin >> wytypowana_liczba;
	bool bCzyBlad = cin.fail();
	
	if (cin.fail() != 0)
	{
	cout << "Wpisales wartosc spoza wytyczonego zakresu." << endl;
	_exit(0);
	}
	
	else ( do {
	if (wylosowana_liczba > wytypowana_liczba)
	{
	cout << "Moja liczba jest wieksza" << endl;
	cout << "Podaj inna liczbe: " << endl;
	cin >> wytypowana_liczba;
	}
	else if (wylosowana_liczba < wytypowana_liczba) {

	cout << "Moja liczba jest mniejsza" << endl;
	cout << "Podaj inna liczbe: " << endl;
	cin >> wytypowana_liczba;
	}

	} while (wylosowana_liczba != wytypowana_liczba);
	if (wylosowana_liczba == wytypowana_liczba)
	{
	cout << "Zgadles." << endl;
	}
	)
	return 0;
}


	

 

1
komentarz 2 listopada 2020 przez Whiskey_Taster Pasjonat (15,610 p.)
edycja 2 listopada 2020 przez Whiskey_Taster

Zobacz na linię nr 29. Cóż to za twór? 

else ( do {

Jeśli chcesz coś zawrzeć wewnątrz else, to używasz nawiasów {...}, a nie zwykłych. Ponadto formatowanie pozostawia wiele do życzenia, przez powyższy zabieg, to jest wpisanie instrukcji do ... while powinno zawierać jakieś wcięcia, aby łatwiej się czytało.

W dodatku taka uwaga - ten ostatni if jest w ogóle niepotrzebny. Skoro wyszliśmy z pętli, to znaczy, że liczba podana przez użytkownika oraz liczba wybrana są identyczne. Wobec tego sprawdzanie warunku równości tych liczb po wyjściu z pętli (które to wyjście następuje, gdy obie liczby są równe) jest zbędne. 

2 odpowiedzi

0 głosów
odpowiedź 2 listopada 2020 przez Piotr Batko Stary wyjadacz (13,190 p.)

Ale.. najpierw pojawił się problem, że nawet jak wpisana liczba była złożona z samych cyfr pojawiał się komunikat "Wpisałeś wartość spoza wyznaczonego zakresu".

Nie powinno się tak dziać. Spróbuj tego przykładu:

#include <iostream>

int main()
{
    int value;
    std::cin >> value;

    if (std::cin.fail())
    {
        std::cout << "Fail\n";
    }
    else
    {
        std::cout << "Good\n";
    }

    return 0;
}

(...) gdy próbowałam jakoś naprawić program i zrobić tak, żeby w przypadku, gdy liczba jest poprawna, program działał i pozwalał na dalsze zgadywanie, całkiem się wysypał.

Załóżmy, że próbujesz wczytać tekst do inta (czyli prosisz użytkownika o wpisanie liczby, a on wpisuje "kopytko"). Z tego przykładu powyżej wiesz już, że w takiej sytuacji std::cin będzie miał ustawioną flagę failbit. Musisz jeszcze wiedzieć, że std::cin przechowuje u siebie cały wprowadzony przez użytkownika tekst i gdy używasz operatora >>, wyciągasz z niego kolejne porcje. Przykład:

int a, b, c;
std::cin >> a;
std::cin >> b;
std::cin >> c;
std::cout << "Finished\n";

Gdy uruchomisz program i wpiszesz 1<spacja>2<spacja>3<enter> to natychmiast się zakończy. Sprawdź sobie, uruchom powyższy kod. Dobra, teraz dlaczego tak się dzieje?

W trakcie pierwszej instrukcji odczytu std::cin miał w swoim buforze "1<spacja>2<spacja>3<enter>". Każemy mu wczytać a. Ok, wyciąga z bufora liczbę do pierwszej nie-cyfry (jak będzie "123abc", to wyciągnie 123, a abc zostawi), w tym przypadku 1. Od teraz a jest równe 1, a w buforze zostało "<spacja>2<spacja>3<enter>".

Druga instrukcja odczytu: std::cin wyrzuca białe znaki z początku strumienia (bufor: "2<spacja>3<enter>") i wczytuje do pierwszej nie-cyfry. Więc b jest równe 2 i bufor ma w sobie "<spacja>3<enter>".

Z wczytywaniem c nie ma żadnych fajerwerków, ale zauważ co zostanie w buforze. Zostanie "<enter>". Zapamiętaj na przyszłość, że tak się dzieje, to nie będziesz mieć problemów z używaniem std::getline w przyszłości, ale tym się teraz nie przejmuj :)

Dobra, wracam do rozwiązania Twojego problemu, bo się trochę rozpisałem o std::cin-ie. Co się dzieje jak wczytujesz tekst do inta. Po linijce std::cin >> value; oprócz failbit-a, w buforze std::cin-a pozostaje "kopytko". Kiedy ponownie program napotka linijkę z wczytywaniem inta (jak u Ciebie w pętli?) nawet się nie zatrzyma. Poleci dalej, a failbit i bufor pozostaną w takim stanie jak były. Czyli po tym, jak wykryjesz, że std::cin się zepsuł (std::cin.fail()), musisz go naprawić i możesz z niego korzystać dalej :)

Koniec wstępu teoretycznego, praktyka:

#include <iostream>
#include <limits>

int main()
{
    int guess;
    std::cout << "Enter number from 1 to 1000: ";
    std::cin >> guess;

    while (std::cin.fail())
    {
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

        std::cout << "Incorrect value, please enter again: ";
        std::cin >> guess;
    }

    // Correct input was entered, we can use it now :)
}

Linijka 12. -- std::cin.clear() -- czyści flagi, a więc failbit też. Kolejna linia wyciąga z bufora wszystkie znaki aż do entera. Od tego momentu std::cin jest już zdrowy i można z niego ponownie czytać :)

Trochę długa wyszła ta odpowiedź... :) A tak w ogóle to widać, że dbasz o porządek w kodzie i dobrze nazywasz zmienne. To bardzo ważne w programowaniu. Tak trzymaj, powodzenia ;)

komentarz 2 listopada 2020 przez rain.deer Początkujący (430 p.)

Dokonałam poprawek i program na pozór działa. Jeśli przy pierwszym wpisywaniu liczby wpiszę np "5g0" program poprawnie wypisze, że jest to niepoprawna wartość i każe wpisać inną liczbę, gdy kolejne liczby nie zawierają już liter wszystko jest ok. Ale - gdy najpierw wpisuję wartość liczbową, a np. w drugim, czy trzecim kroku cyfry i litery, program wariuje - w nieskończoność wypisuje "Moja liczba jest większa / mniejsza. Wprowadź inną liczbę". Jak uniknąć tego problemu i co go wywołuje? Stan kodu na chwilę obecną jest następujący:
 

#include <iostream>
#include <time.h>
#include <limits>

int main()
{
	std::cout << "Losowanie" << std::endl;
	std::cout << "Program wylosuje teraz liczbe z przedzialu 1 - 1000. Zgadnij liczbe." << std::endl;
	std::srand(time(NULL));
	int wytypowana_liczba;
	int wylosowana_liczba = (rand() % 999) + 1;
	
	std::cout << wylosowana_liczba << std::endl;		
	std::cout << "Zgadnij wylosowana liczbe " << std::endl;
	std::cin >> wytypowana_liczba;

	while (std::cin.fail())
	{
		std::cin.clear();
		std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
		std::cout << "Wpisales wartosc spoza wytyczonego zakresu.";
		std::cout << "Podaj inna liczbe: " << std::endl;
		std::cin >> wytypowana_liczba;
	}
	while (wylosowana_liczba != wytypowana_liczba) {
		if (wylosowana_liczba > wytypowana_liczba)
		{
			std::cout << "Moja liczba jest wieksza" << std::endl;
			std::cout << "Podaj inna liczbe: " << std::endl;
			std::cin >> wytypowana_liczba;
		}
		else if (wylosowana_liczba < wytypowana_liczba) {

			std::cout << "Moja liczba jest mniejsza" << std::endl;
			std::cout << "Podaj inna liczbe: " << std::endl;
			std::cin >> wytypowana_liczba;
		}
	}
		std::cout << "Zgadles." << std::endl;
	
	return 0;
}

Zależy mi na tym, żeby zgadywanie było kontynuowane aż do czasu, gdy padnie prawidłowa odpowiedź.

komentarz 2 listopada 2020 przez Piotr Batko Stary wyjadacz (13,190 p.)

Zrobiłaś bezpieczne wczytywanie pierwszej wartości (linia 15), ale nie zabezpieczyłaś pozostałych wczytywań (linijki: 30 i 36). Wystarczy, że zabezpieczysz tamte odczyty w bardzo podobny sposób i naprawione :)

Aha, no i mam nadzieję, że już wiesz, że gdy użytkownik wprowadzi "5g0", to 5 zostanie odczytane poprawnie (czyta wszystko do pierwszej nie-cyfry, pamiętasz?), a dopiero kolejny odczyt się nie uda.

Zanim jeszcze się zabierzesz do naprawiania, zauważ, że można ten kodzik odrobinkę uprościć (to Ci trochę ułatwi naprawianie). Jeżeli masz kod w postaci:

if (...)
{
  // Zrob czynnosc A
  // Zrob czynnosc C
}
else
{
  // Zrob czynnosc B
  // Zrob czynnosc C
}

To możesz to zapisać prościej jako:

if (...)
{
  // Zrob czynnosc A
}
else
{
  // Zrob czynnosc B
}

// Zrob czynnosc C

A teraz zerknij do swojej pętli ;)

komentarz 3 listopada 2020 przez rain.deer Początkujący (430 p.)

ale nie zabezpieczyłaś pozostałych wczytywań (linijki: 30 i 36). Wystarczy, że zabezpieczysz tamte odczyty w bardzo podobny sposób i naprawione :)

W jaki sposób należy to zrobić?

Nieco uprościłam kod, ale nie wpłynęło to w żaden sposób na sprawność programu. Nie wiem, co jest w nim nie tak. sad Kod wygląda teraz tak: 

#include <iostream>
#include <time.h>
#include <limits>
#include <cstdlib>

int main()
{
	std::cout << "Losowanie" << std::endl;
	std::cout << "Program wylosuje teraz liczbe z przedzialu 1 - 1000. " << std::endl;
	std::srand(time(NULL));
	int wytypowana_liczba;
	int wylosowana_liczba = (rand() % 999) + 1;

	
	std::cout << "Zgadnij wylosowana liczbe " << std::endl;
	std::cin >> wytypowana_liczba;
    		
	
		while (std::cin.fail() || wytypowana_liczba > 1000 || wytypowana_liczba < 1)
	{
		std::cin.clear();
		std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
		std::cout << "Wpisales wartosc spoza wytyczonego zakresu. ";
		std::cout << "Podaj inna liczbe: " << std::endl;
		std::cin >> wytypowana_liczba;
	}
	   

		while (wylosowana_liczba != wytypowana_liczba) 
	{
		if (wylosowana_liczba > wytypowana_liczba)
		{
		std::cout << "Moja liczba jest wieksza" << std::endl;
		}
		else if (wylosowana_liczba < wytypowana_liczba) 
		{
		std::cout << "Moja liczba jest mniejsza" << std::endl;
	    }
		std::cout << "Podaj inna liczbe: " << std::endl;
		std::cin >> wytypowana_liczba;
		if (wylosowana_liczba == wytypowana_liczba)
        std::cout << "Zgadles." << std::endl;
		
		}
	
	return 0;
}

 

komentarz 3 listopada 2020 przez Piotr Batko Stary wyjadacz (13,190 p.)
No skopiować zabezpieczenie z linijek 19-26 pod niezabezpieczone wczytywanie w linijce 40.
komentarz 3 listopada 2020 przez rain.deer Początkujący (430 p.)

Zadziałało, nareszcie! Bardzo dziękuję za pomoc, sama bym nie wpadła na to. blush

–1 głos
odpowiedź 31 października 2020 przez wizarddos Nałogowiec (25,130 p.)
Czemu nie użyjesz zamiast cin.fail() funkcji biblioteki cctype
komentarz 1 listopada 2020 przez rain.deer Początkujący (430 p.)

Po takim zastosowaniu tej funkcji wyskakuje mi błąd "za mało argumentów w wywołaniu funkcji", czyli coś musi się znaleźć w nawiasie. Tylko nie wiem co. sad

 

komentarz 1 listopada 2020 przez rain.deer Początkujący (430 p.)

@wizadkol123, zmieniłam trochę program, ale wpadł w wieczną pętlę. Gdy wpisuję wartości, które są liczbami, działa dobrze. Ale gdy wpiszę np. ciąg liter, program w nieskończoność wyświetla "Moja liczba jest większa/mniejsza. Podaj inną liczbę". 

/* Twoim zadaniem domowym jest napisanie prostej gry, która ma działać następująco :
1. Program losuje liczbę z przedziału od 1 do 1000.
2. Użytkownik zgaduje liczbę, która została wylosowana.
3. Jeżeli podana liczba jest za duża(za mała) gra wypisuje stosowny komunikat i powraca do kroku 2.
4. Jeżeli gracz trafi liczbę wylosowaną to progam kończy działanie, wypisując na ekran wylosowaną liczbę oraz liczbę 'strzałów', które oddał gracz.
Gra ma być zabezpieczona przed możliwością wprowadzenia błędnych wartości liczbowych.*/
#include <iostream>
#include <time.h>
#include <cstdlib>
#include <cctype>
#include <math.h>

int main()
{
	std::cout << "Losowanie" << std::endl;
	std::cout << "Program wylosuje teraz liczbe z przedzialu 1 - 1000. Zgadnij liczbe." << std::endl;
	std::srand(time(NULL));
	int wytypowana_liczba;
	int wylosowana_liczba = (rand() % 999) + 1;
	int i = 'i';
	wytypowana_liczba = i;
	std::cout << wylosowana_liczba << std::endl;		
	std::cout << "Zgadnij wylosowana liczbe " << std::endl;
	std::cin >> wytypowana_liczba;
	int isdigit(int ch);
	do {
		if (wylosowana_liczba > wytypowana_liczba)
		{
			std::cout << "Moja liczba jest wieksza" << std::endl;
			std::cout << "Podaj inna liczbe: " << std::endl;
			std::cin >> wytypowana_liczba;
		}
		else if (wylosowana_liczba < wytypowana_liczba) {

			std::cout << "Moja liczba jest mniejsza" << std::endl;
			std::cout << "Podaj inna liczbe: " << std::endl;
			std::cin >> wytypowana_liczba;
		}

	} while (wylosowana_liczba != wytypowana_liczba);

	if (wylosowana_liczba == wytypowana_liczba)
	{
		std::cout << "Zgadles." << std::endl;
		_exit(0);
	}
	if (isdigit(i) == false || wytypowana_liczba > 1000 || wytypowana_liczba < 1)
	{
	std::cout << "Wpisales wartosc spoza wytyczonego zakresu." << std::endl;
	_exit(0);
	}
	

	return 0;
}


	

 

komentarz 1 listopada 2020 przez rain.deer Początkujący (430 p.)

Nareszcie udało mi się poprawić kod laugh I wszystko działa jak należy. Dziękuję za pomoc :)



 

#include <iostream>
#include <time.h>
#include <cstdlib>
#include <cctype>
#include <math.h>

int main()
{
	std::cout << "Losowanie" << std::endl;
	std::cout << "Program wylosuje teraz liczbe z przedzialu 1 - 1000. Zgadnij liczbe." << std::endl;
	std::srand(time(NULL));
	int wytypowana_liczba;
	int wylosowana_liczba = (rand() % 999) + 1;
	int i = 'i';
	wytypowana_liczba = i;
	std::cout << wylosowana_liczba << std::endl;		
	std::cout << "Zgadnij wylosowana liczbe " << std::endl;
	std::cin >> wytypowana_liczba;
	int isdigit(int ch);
	while (wylosowana_liczba != wytypowana_liczba && wytypowana_liczba >= 1 && wytypowana_liczba <= 1000)
	{
		if (wylosowana_liczba > wytypowana_liczba)
		{
			std::cout << "Moja liczba jest wieksza" << std::endl;
			std::cout << "Podaj inna liczbe: " << std::endl;
			std::cin >> wytypowana_liczba;
		}
		else if (wylosowana_liczba < wytypowana_liczba) {

			std::cout << "Moja liczba jest mniejsza" << std::endl;
			std::cout << "Podaj inna liczbe: " << std::endl;
			std::cin >> wytypowana_liczba;
		}

	} 

	if (wylosowana_liczba == wytypowana_liczba)
	{
		std::cout << "Zgadles." << std::endl;
		_exit(0);
	}
	if (isdigit(i) == false || wytypowana_liczba > 1000 || wytypowana_liczba < 1)
	{
	std::cout << "Wpisales wartosc spoza wytyczonego zakresu." << std::endl;
	_exit(0);
	}
	

	return 0;
}

 

komentarz 1 listopada 2020 przez Piotr Batko Stary wyjadacz (13,190 p.)

Program działa przypadkiem. Przeanalizujmy co się dzieje, jak ktoś na pytanie o liczbę wpisze "kopytko". No cóż, nie da się z tego odczytać liczby, więc w std::cin ustawiona zostaje flaga failbit, a do zmiennej wytypowana_liczba zostaje wpisane 0. Wchodzimy do pętli, w której sprawdzasz, czy liczba mieści się w zakresie od 1 do 1000. Nie mieści się, więc wychodzimy.

W 37. linijce sprawdzasz czy to przypadkowe zero to wylosowana liczba. To się nigdy nie zgodzi, bo losujesz liczby od 1 do 999 (nie do 1000, bo jak by się nawet wylosowało 999, to 999 % 999 daje 0, zobacz do linijki 13.).

W linijce 42. są bardzo dziwne rzeczy. Rozumiem jaki miałaś zamiar, ale pozwól, że powiem Ci co napisałaś :)
isdigit(i) == false || wytypowana_liczba > 1000 || wytypowana_liczba < 1
Argument funkcji isdigit -- i -- ma wartość 'i' (ustawioną w linijce 14.). Wywołanie funkcji isdigit('i'), czyli sprawdzenie, czy litera i to cyfra, oczywiście mówi, że nie jest cyfrą. Zwraca false. Zawsze zwraca false! :D Czyli kod w tym ifie zawsze się wykona. Ten warunek powyżej mógłby zostać kompletnie usunięty i niczego to nie zmieni. Wyświetla się, że została wprowadzona niepoprawna wartość i gitara, wszyscy myślą, że program działa, ale to seria przypadków. Spróbuj zmienić zakres z 1-1000, na 0-1000, a zobaczysz jakie się cuda będą dziać.

Funkcja isdigit kompletnie nie nadaje się do tego zadania. Dobrze zaczęłaś ze sprawdzeniem std::cin.fail(). Tyle wystarczy jeżeli chodzi o komentarz do rozwiązania z cctype. Za chwilę napiszę Ci odpowiedź jak poprawnie użyć do sprawdzenia poprawności danych wejściowych std::cin-a.

komentarz 2 listopada 2020 przez rain.deer Początkujący (430 p.)
Dziękuję za odpowiedź. Przeczytam ją dokładnie i przemodeluję program. Wiem, że pierwotne zastosowanie std::cin.fail() było właściwym wyborem, ale potem wszystko się posypało, nie umiałam też właściwie wykorzystać wyniku jaki daje ta instrukcja. No ale jestem jeszcze początkująca i uczę się programowania (i nie tylko tego) po godzinach pracy, nie mając nigdy wcześniej z czymś takim do czynienia. Może to jakoś tłumaczy moje błędy w tym, przecież prostym, programie.

Podobne pytania

–2 głosów
1 odpowiedź 325 wizyt
pytanie zadane 6 lutego 2017 w C i C++ przez Sajmi Nowicjusz (150 p.)
+1 głos
3 odpowiedzi 881 wizyt

92,452 zapytań

141,262 odpowiedzi

319,085 komentarzy

61,854 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Akademia Sekuraka 2024 zapewnia dostęp do minimum 15 szkoleń online z bezpieczeństwa IT oraz dostęp także do materiałów z edycji Sekurak Academy z roku 2023!

Przy zakupie możecie skorzystać z kodu: pasja-akademia - użyjcie go w koszyku, a uzyskacie rabat -30% na bilety w wersji "Standard"! Więcej informacji na temat akademii 2024 znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...