Oběšenec v Céčku Vyřešeno

Místo pro dotazy a rady ohledně programovacích jazyků (C++, C#, PHP, ASP, Javascript, VBS..) a tvorby webových stránek

Moderátor: Mods_senior

PsychiKsSs
nováček
Příspěvky: 7
Registrován: duben 11
Pohlaví: Muž
Stav:
Offline

Oběšenec v Céčku  Vyřešeno

Příspěvekod PsychiKsSs » 30 dub 2011 19:52

Dobrý den,
potřeboval bych pomoci s tímto jednoduchým programem. Je to jednoduše naprogramovaná hra "Oběšenec",
která má ale problém, který bych chtěl objasnit. Při zadání krátkého zadání (asi do 10 znaků) program vypíše
všechny pomlčky správně, ale při delším zadání za řetězec pomlček a mezer vypíše nějaké neznámé další znaky.

Oběšenec - exe soubor ke stažení.
Screeny - správné a chybné zobrazení.

Kód: Vybrat vše

    char veta[60], x, tip[60], vet[60];
    int i, pocet, j, pamet, por, tips[10], y, v;
   
    printf("Napis zadani obesence(MAX 50 znaku): ");
    gets(veta);
    system("cls");   
    pocet = strlen(veta);
   
    printf("Kdyz si budes chtit tipnout tajenku stiskni '1'\n\n");
    for(i=0;i<pocet;i++){if(veta[i] >= 'A' && veta[i] <= 'Z')veta[i]+='a'-'A';
                          if(veta[i]==' '){vet[i]=' ';}
                          else{vet[i]='-';}
                        }
                                     
    printf("%s\n", vet);     
   
    for(i=0;i<5;i++){y=0;por=0;
                     for(v=0;v<10;v++){tips[v]=0;}
                       printf("\n\nZadej tipovane pismeno: ");
                        scanf("%c", &x);getchar();
                         while(x=='1'){printf("Napis celou tajenku: ");
                                       gets(tip);
                                       for(v=0;v<pocet;v++){if(tip[v] >= 'A' && tip[v] <= 'Z')tip[v]+='a'-'A';}
                                       por = strcmp(veta, tip);
                                       if(por == 0){printf("Spravne");goto end;}
                                       else{break;}
                                      }
                           if(x<='Z' && x>='A')x+='a'-'A';
                            for(j=0;j<pocet;j++){if(veta[j]==x){tips[y]=j;
                                                                y++;}
                                                }
                     if(y>0){printf("Trefa\n");i--;}
                     else{printf("Spatne (%d. chyba)", i+1);continue;}
                     
                     for(j=0,y=0;j<pocet;j++){if(tips[y]==j){y++;vet[j]=x;continue;}
                                             }
                     printf("%s", vet);
                     for(j=0;j<pocet;j++){if(vet[j]=='-'){por++;}}
                     if(por==0){break;}                                                         
                    }
   
    if(i==5)printf("\n\nVsechny pokusy byly vycerpany, hra konci!");
                           
    end:
        if(por==0 && i!=5){printf("\n\nJsi vyherce!");}

Za případné vyřešení problému a nějaké zlepšující nápady děkuji.

Reklama
Uživatelský avatar
faraon
Master Level 8.5
Master Level 8.5
Příspěvky: 7397
Registrován: prosinec 10
Pohlaví: Muž
Stav:
Offline

Re: Oběšenec v Céčku

Příspěvekod faraon » 01 kvě 2011 10:21

Kdysi jsem někde četl vtip asi v tomto znění:

Dva Céčkové řetězce si zajdou do baru.
Barman se jich ptá co si budou přát.
První si objednává: "Já bych si dal sklenu vína."
Potom druhý: "Já chci jen minerálku.^}{@&#<€"
Barman se udiveně obrátí na první řetězec: "Není kolega nemocen?"
A ten odpoví: "Ale ne, on je jen chybně ukončen."


Máš dva řetězce, veta a vet. První si načteš funkcí gets(), ta do něj vloží celý řádek zadaný na klávesnici a za něj znak \0, který označuje jeho konec.
Potom si podle jeho délky nasázíš do druhého pomlčky a mezery, a za nimi ti zůstane binární bordel. Zapomněl jsi ho ukončit tou nulou!
Funkce printf() ti ho pak vesele vypisuje a vypisuje a vypisuje... A vypisuje dokud nenarazí na nulu, protože to je jediná věc která to vypisování ukončí! Vlastně je div že v tom prvním případě se ti náhodou vypsal správně, měl jsi štěstí že ti na zásobníku nějaké nuly zůstaly po předchozí činnosti, pro delší řetězec to ale nestačilo, moc nechybělo a skončil bys s porušením ochrany paměti.

Prostě za tu kopírovací smyčku, po které ti proměnná i ukazuje na první znak za koncem řetězce, přidej jedno přiřazení:

vet[i]='\0';



Návrhy na zlepšení?

- Funkce gets() nehlídá délku vstupu, doporučuje se použít místo ní fgets(). Když si dělám něco pro sebe, tak jí sice také používám, ale pokud ten program chceš "poslat do světa", musíš ho udělat blbuvzdorný. Věř mi že se vždycky najde někdo, kdo se ti tam pokusí napsat něco delšího, případně přesměrovat celý textový soubor :evil:
- Věty pro hádání můžeš nadefinovat do pole a losovat je náhodně, nebo si je načíst ze souboru.
- Zrovna do té "kopírovací smyčky" by neškodilo přidat kontrolu na překročení délky, když jí nemáš jinak hlídanou, a v případě chyby program ukončit chybovým hlášením dřív než spadne sám. Je na to funkce exit(), která umí i vrátit chybový kód.
- Tu maximální délku (a ostatní konstanty) nezadávej do kódu číslem, ale nadefinuj si jí na začátku jako symbolickou konstantu:
#define MAXDELKA 60
Ušetří ti to práci a chyby při úpravách a opravách programu. Říká se že v samotném kódu se smí vyskytnout jen dvě čísla, 0 a 1, a o té jedničce se vedou spory ;-) Nemluvě o tom že místo nuly je lepší použít NULL, zvlášť že 0 a NULL nemusí být na některých architekturách totožné!
- Použít goto v takhle malém programu je fakt ostuda, tomuhle příkazu se vyhni dokud nebudeš psát programy dlouhé aspoň 10000 řádek! Nemluvě o tom že to odporuje zásadám strukturovaného programování, dokonce té nejdůležitější:
"Blok smí mít jen jeden vstup a jeden výstup!"



EDIT: Tak jsem konečně dorazil domů ke svému počítači a zkusil to zkompilovat, takže ještě pár drobností:

- Zpráva "Jsi vyherce!" na konci není ukončená \n, počítej s tím že ten program může někdo spustit z konzole a po jeho skončení se vypíše prompt, v tomhle případě ale na stejný řádek hned za tu zprávu.
- Pro napsání celé tajenky použij jiný znak než 1, co když se náhodou vyskytne v hledaném textu? Zkus třeba mezeru, to je znak který se v žádném slově celkem zaručeně nevyskytne :-D
- Zapracuj na formátování, musel jsem to asi půl hodiny předělávat, abych se v tom vůbec vyznal :-D Používej třeba jednotné odsazování o čtyři sloupce (pascalské dva jsou pro C málo), a závorky { } dávej vždy pod sebe, aby otevírací i uzavírací byla ve stejném sloupci. Mnohem lépe se pak hledá kde cyklus začíná nebo končí, zvlášť když ho máš přes padesát řádků...
"Král Lávra má dlouhé oslí uši, král je ušatec!

(pravil K. H. Borovský o cenzuře internetu)

PsychiKsSs
nováček
Příspěvky: 7
Registrován: duben 11
Pohlaví: Muž
Stav:
Offline

Re: Oběšenec v Céčku

Příspěvekod PsychiKsSs » 01 kvě 2011 20:03

Tak v prvním případě ti faraone děkuju za radu. :wink:

Vtip byl velice dobrý a při jeho dočítání jsem si už uvědomoval svou chybu v kodu.

Zlepšení:
-Maximální délku jsem si definoval jak jsi napsal pres #define MAX 60.
-Příkaz goto jsem úplně odstranil a nahradil jsem to efektivnějším způsobem.
-Také jsem přidal kontrolu délky řetězce (pro moje potřeby nepotřebuju vstup ze souboru, ani generátor náhodných zadání).
-Za zprávou "Jsi výherce" jsem měl ještě jeden rádek s \n, který jsem tady nezkopíroval-moje chyba(znám známou větu: "Pokračujte stisknutím libovolné klávesy...").
-Tlačítko pro napsání celé tajenky jsem změnil na mezeru-je to mnohem lepsi :wink:
-Také jsem místo smyčky while dal příkaz if(nevím proč jsem tam dával ten while).
-Upravil jsem formátování textu-je to mnohem přehlednější xD

Rada:
-Chtěl bych se zeptat ještě na příkaz fgets(), jestli by jsi mi ho mohl nějak jednoduše vysvětlit(stačí 2 řádky-bude se ho snažit pochopit).
-Také jsem zkoušel příkaz exit() a psal mi chybu, nevíš proč? - Nemá se něco psát do té závorky?

Nakonec ještě přidávám upravený kod:

Kód: Vybrat vše

    char veta[MAX], x, tip[MAX], vet[MAX];
    int i, pocet, j, pamet, por, tips[10], y, v;
   
    do{
       printf("Napis zadani obesence(MAX 60 znaku): ");
       gets(veta);
       pocet = strlen(veta);
       }while(pocet>59);
       
    system("cls");
   
    printf("Kdyz si budes chtit tipnout tajenku stiskni ' ' (mezeru)\n\n");
    for(i=0;i<pocet;i++){if(veta[i] >= 'A' && veta[i] <= 'Z')veta[i]+='a'-'A';
                         if(veta[i]==' '){vet[i]=' ';}
                         else{vet[i]='-';}
                        }
    vet[i]='\0';
                                     
    printf("%s\n", vet);     
   
    for(i=0;i<5;i++){y=0;por=0;
                      for(v=0;v<10;v++){tips[v]=0;
                                       }
                       printf("\n\nZadej tipovane pismeno: ");
                        scanf("%c", &x);getchar();
                         if(x==' '){printf("Napis celou tajenku: ");
                                    gets(tip);
                                    for(v=0;v<pocet;v++){if(tip[v] >= 'A' && tip[v] <= 'Z')tip[v]+='a'-'A';
                                                        }
                                    pamet = strcmp(veta, tip);
                                   }
                          if(pamet == 0){printf("Spravne");
                                         break;
                                        }
                           if(x<='Z' && x>='A')x+='a'-'A';
                            for(j=0;j<pocet;j++){if(veta[j]==x){tips[y]=j;
                                                                y++;
                                                               }
                                                }
                          if(y>0){printf("Trefa\n");
                                  i--;
                                 }
                          else{printf("Spatne (%d. chyba)", i+1);
                               continue;
                              }
                     
                         for(j=0,y=0;j<pocet;j++){if(tips[y]==j){y++;
                                                                 vet[j]=x;
                                                                 continue;
                                                                }
                                                 }
                        printf("%s", vet);
                       for(j=0;j<pocet;j++){if(vet[j]=='-'){por++;
                                                           }
                                           }
                      if(por==0){break;
                                }                                                         
                    }
   
    if(i==5)printf("\n\nVsechny pokusy byly vycerpany, hra konci!");
                           
    if(por==0 && i!=5)printf("\n\nJsi vyherce!");
   
    printf("\n\n");

Ještě mám jedén problém-při tipování písmen, když se zadá více znaků než jeden, potom už při dalším tipování to píše i při zprávném písmenu chybu-nedalo by se to vyřešit vyprázdnění bufferu?

Uživatelský avatar
faraon
Master Level 8.5
Master Level 8.5
Příspěvky: 7397
Registrován: prosinec 10
Pohlaví: Muž
Stav:
Offline

Re: Oběšenec v Céčku

Příspěvekod faraon » 01 kvě 2011 21:34

Ajťáci mají velmi svérázný smysl pro humor, klasickým příkladem je třeba legendární přepínač MAGIC/MORE MAGIC (http://www.catb.org/jargon/html/magic-story.html) :lol:

Do té závorky u exit() se má napsat nějaká hodnota int, stejně jako u příkazu return, v podstatě to dělá téměř to samé. Dokonce return je jediný případ, kde se toleruje porušení toho pravidla o jednom výstupu, když budeš mít v proceduře nebo programu těch returnů víc, tak se to bere podobně jako break u cyklů a ne jako chyba, protože v obou případech je jasné co přesně se stane, na rozdíl od toho goto. A zrovna v tomhle případě bude možná ještě lepší nezatěžovat se exitem a rovnou tam dát zprávu a return 0;

Funkce fgets se používá pro načtení řádku ze souboru, a protože pro vstup a výstup máš k dispozici tři automaticky otevřené soubory: stdin, stdout a stderr, můžeš s nimi pracovat bez jakýchkoliv omezení stejně jako se soubory na disku (ale ty by sis musel otevřít sám), například funkce getchar() je ve skutečnosti jenom makro, které se při překladu nahrazuje funkcí getc(stdin):

Kód: Vybrat vše

#include <stdio.h>

#define MAXDELKA 10

int main(void)
    {
    char string[MAXDELKA];

    printf("Zadej řetězec: ");
    fgets(string,MAXDELKA,stdin);
    printf("%s",string);

    return 0;
    }


Do řetězce string[] se načte MAXDELKA-1 nebo méně znaků ze souboru stdin, tedy standardního vstupu. Ty zadáš kolik maximálně bytů se do toho pole vejde, takže si fgets() automaticky nechá místo i na ukončující znak \0.
Akorát bacha na jednu věc, fgets na rozdíl od gets načítá i znak \n na konci řádku! A protože ho v té proměnné máš (pokud nebyl vstup delší!), tak se ti také bude vypisovat v printf(), takže pokud ho tam nechceš, musíš ho sám odstranit, třeba takhle:

Kód: Vybrat vše

for (i=0;(i<MAXDELKA)&(string[i]!='\n');++i);
if (i<MAXDELKA)
   string[i]='\0';


To načítání více znaků je problém, navíc když zadáš celý řetězec, tak ti to načítá každý druhý znak :-D Jo, vyprázdnit buffer by to fakt chtělo. A také tě program nijak neupozorní na opakované zadání znaku který už je odkrytý.

Teď už to formátování je trochu přehlednější, ale věci jako
for(j=0,y=0;j<pocet;j++){if(tips[y]==j){y++;
mi fakt nedělají dobře kolem žaludku :shock: Je lepší mít každý příkaz na samostatném řádku, i když se tím kód může na výšku dost protáhnout.



EDIT: Ještě mě napadlo, když hlídáš překročení délky toho zadávaného řetezce, bylo by dobré uživatele upozornit že nastala chyba, aby věděl co zvoral a jak má pokračovat. Není nic děsivějšího než prázdná obrazovka s blikajícím kurzorem, čekající na nějaký vstup:

Kód: Vybrat vše

for (;;)
    {
    printf("Napis zadani obesence(MAX %d znaku): ",MAX);
    gets(veta);
    pocet = strlen(veta);
    if (pocet>MAX)
       printf("\aZadany text je prilis dlouhy!\n");
    else
       break;
    }


Kdysi dávno vyšla knížka Dialog s počítačem od autorů Becka a Vejmoly, v té je tahle problematika dokonale rozebraná. S ohledem na dobu vzniku jsou v ní sice příklady v BASICu, ale ty rady platí všeobecně, nejen pro konzolové programy, ale i pro ty komunikující přes GUI: http://www.knihzdar.cz/opacsqlok/zaznam.php?detail_num=23743&vers=1&lang=eng

A když už tam máš konstantu MAX, použil jsem jí i v tom printf().
"Král Lávra má dlouhé oslí uši, král je ušatec!

(pravil K. H. Borovský o cenzuře internetu)

PsychiKsSs
nováček
Příspěvky: 7
Registrován: duben 11
Pohlaví: Muž
Stav:
Offline

Re: Oběšenec v Céčku

Příspěvekod PsychiKsSs » 01 čer 2011 10:19

Tak ještě jednou díky za další rady ;)

Omlouvám se, že jsem neodpověděl dříve, ale potřeboval jsem všechny tvé rady vztřebat :D
Ale teď už umím používat skoro vše co jsi mi poradil a taky jsem se naučil využívat některé rady na úpravu kodu.
Teď jsem dopsal zavěrečnou práci do školy a nebýt každého příkazu na novém řádku, tak by se asi špatně upravoval..
Program má jen 600 řádku, ale dokázal mi zabrat docela dost času xD

Učený z nebe nespadl, proto oceňuji všechny užitečné rady ;)
Hodně zdaru a dalších užitečných rad nejen mě ti přeje PsychiksSs *THUMBS UP*


Zpět na “Programování a tvorba webu”

Kdo je online

Uživatelé prohlížející si toto fórum: Žádní registrovaní uživatelé a 3 hosti