Witam.
Otóż mam oto taki programik "Kółko i krzyżyk"
main.cpp
#include <iostream>
#include <conio.h>
#include "game.h"
void main()
{
// rozpoczynamy grę
StartGry();
// pętla nieskończona - częsty element w grach
// wyjdziemy z niej przy pomocy break w odpowiednim momencie
for(;; )
{
// rysujemy planszę
RysujPlansze();
// sprawdzamy stan gry i czynimy odpowiednie działania
if( g_StanGry == GS_MOVE )
{
// ruch któregoś gracza
// pobieramy go, więc i wywołujemy funkcję Ruch()
unsigned uNumerPola;
std::cin >> uNumerPola;
Ruch( uNumerPola );
}
else if( g_StanGry == GS_WON || g_StanGry == GS_DRAW )
// koniec gry (wygraną lub remisem)
// wtedy przerywamy pętlę
break;
}
// czekamy na dowolny klawisz
getch();
}
game.h
// Plik nagłówkowy właściwego kodu gry
// zabezpieczenie przez wielokrotnym dołączeniem
#pragma once
```cpp
// typy
//-----
// znak - czyli kółko lub krzyżyk :)
enum SIGN { SGN_CIRCLE = 'O', SGN_CROSS = 'X' };
// pojedyncze pole (może być puste, mieć kółko lub krzyżyk)
enum FIELD { FLD_EMPTY = 0, // puste
FLD_CIRCLE = SGN_CIRCLE, // kółko
FLD_CROSS = SGN_CROSS }; // krzyżyk
// stan gry
enum GAMESTATE { GS_NOTSTARTED, // nie rozpoczęta
GS_MOVE, // ruch gracza
GS_WON, // wygrana gracza
GS_DRAW }; // remis
// deklaracje zmiennych
//---------------------
// zmienna określająca stan gry musi być widoczna także na zewnątrz
// modułu game.cpp; poniższa deklaracja czyni zadość tej potrzebie
// stan gry
extern GAMESTATE g_StanGry;
// prototypy funkcji
//------------------
// rozpoczęcie gry
bool StartGry();
// narysowanie planszy
bool RysujPlansze();
// wykonanie ruchu
bool Ruch( unsigned );
i game.cpp
```cpp
// Właściwy kod gry
#include <iostream>
#include <ctime>
#include "game.h"
// zmienne
//--------
// plansza w postaci tablicy 3x3
FIELD g_aPlansza[ 3 ][ 3 ] = { { FLD_EMPTY, FLD_EMPTY, FLD_EMPTY },
{ FLD_EMPTY, FLD_EMPTY, FLD_EMPTY },
{ FLD_EMPTY, FLD_EMPTY, FLD_EMPTY } };
// stan gry (nie rozpoczęta, ruch gracza, wygrana lub remis)
GAMESTATE g_StanGry = GS_NOTSTARTED;
// znak aktualnego gracza (kółko lub krzyżyk)
SIGN g_AktualnyGracz;
//----------------------------------------------------------------------------------------
// funkcje
//--------
// funkcja wywoływana na starcie gry
bool StartGry()
{
// najpierw sprawdzamy, czy gra już nie trwa;
// jeżeli tak, to funkcja kończy się porażką
if( g_StanGry != GS_NOTSTARTED ) return false;
// losujemy gracza, który będzie zaczynał
srand( static_cast < unsigned >( time( NULL ) ) );
g_AktualnyGracz =( rand() % 2 == 0 ? SGN_CIRCLE
: SGN_CROSS );
// ustawiamy stan gry na ruch graczy
g_StanGry = GS_MOVE;
// wszystko się udało, zatem zwracamy true
return true;
}
// rysowanie (albo raczej wypisywanie) pola gry
bool RysujPlansze()
{
// także sprawdzamy, czy aby na pewno gra rozpoczęłas ię
if( g_StanGry == GS_NOTSTARTED ) return false;
// czyścimy okienko konsoli przy pomocy polecenia systemowego CLS
system( "cls" );
// pokazujemy szyld tytułowy
std::cout << " KOLKO I KRZYZYK " << std::endl;
std::cout << "---------------------" << std::endl;
std::cout << std::endl;
// następnie wypisujemy właściwą planszę
// oczywiście, czynimy to przy pomocy dwóch pętli for
std::cout << " -----" << std::endl;
for( int i = 0; i < 3; ++i )
{
// lewa część ramki
std::cout << " |";
// wiersz
for( int j = 0; j < 3; ++j )
{
// sprawdzamy, czy dane pole jest puste
if( g_aPlansza[ i ][ j ] == FLD_EMPTY )
// wtedy wyświetlamy jego numerek
// tak, żeby użytkownik wiedział, jaką liczbę wpisać, gdy będzie
// chciał postawić na nim kółko lub krzyżyk :)
std::cout << i * 3 + j + 1;
else
// jeżeli natomiast pole jest zajęte,
// wyświetlamy odpowiadający mu znak;
// korzystamy tu ze sztuczki z odpowiednimi wartościami stałych typu
// FIELD; dzięki niej unikamy dodatkowego if'a (tudzież operatora ?:)
std::cout << static_cast < char >( g_aPlansza[ i ][ j ] );
}
// prawa część ramki
std::cout << "|" << std::endl;
}
std::cout << " -----" << std::endl;
std::cout << std::endl;
// wreszcie, pokazujemy dolny komunikat
// jest on albo prośbą o podanie ruchu, albo informacją o stanie gry
// wszystko zależy o tegoż stanu, a zatem stosujemy instrukcję switch
switch( g_StanGry )
{
case GS_MOVE:
// ruch gracza, a więc gra trwa
// musimy więc poprosić o ruch
std::cout << "Podaj numer pola, w ktorym" << std::endl;
std::cout << "chcesz postawic ";
std::cout <<( g_AktualnyGracz == SGN_CIRCLE ? "kolko": "krzyzyk" ) << ": ";
break;
case GS_WON:
// gra zakończona, ktoś wygrał :)
// wyświetlamy odpowiednią informację
std::cout << "Wygral gracz stawiajacy ";
std::cout <<( g_AktualnyGracz == SGN_CIRCLE ? "kolka": "krzyzyki" ) << "!";
break;
case GS_DRAW:
// może też się zdarzyć remis
// także pokazujemy wiadomość
std::cout << "Remis!";
break;
}
// wszystko poszło OK, zatem powiadamiamy o tym
return true;
}
// wykonanie ruchu i sprawdzenie jego rezultatu
bool Ruch( unsigned uNumerPola )
{
// znowuż musimy sprawdzić stan gry
if( g_StanGry != GS_MOVE ) return false;
// następnie sprawdzamy, czy numer pola mieści się w przedziale <1; 9>
if( !( uNumerPola >= 1 && uNumerPola <= 9 ) ) return false;
// jeśli doszliśmy dotąd, to wszystko jest w porządku i możemy wykonać ruch;
// przeliczamy więc numer pola na indeksy tablicy i przypisujemy znak gracza;
// (tylko wtedy, gdy pole jest puste)
// po raz któryś-tam z kolei używamy przy tym sztuczki z wartościami enumów
unsigned uY =( uNumerPola - 1 ) / 3;
unsigned uX =( uNumerPola - 1 ) % 3;
if( g_aPlansza[ uY ][ uX ] == FLD_EMPTY )
g_aPlansza[ uY ][ uX ] = static_cast < FIELD >( g_AktualnyGracz );
else
// jeżeli próbowano dwukrotnie postawić znak w tym samym miejscu,
// to niestety nie możemy na to pozwolić :)
return false;
int LINIE[][ 3 ][ 2 ] = { { { 0, 0 }, { 0, 1 }, { 0, 2 } }, // górna pozioma
{ { 1, 0 }, { 1, 1 }, { 1, 2 } }, // środkowa pozioma
{ { 2, 0 }, { 2, 1 }, { 2, 2 } }, // dolna pozioma
{ { 0, 0 }, { 1, 0 }, { 2, 0 } }, // lewa pionowa
{ { 0, 1 }, { 1, 1 }, { 2, 1 } }, // środkowa pionowa
{ { 0, 2 }, { 1, 2 }, { 2, 2 } }, // prawa pionowa
{ { 0, 0 }, { 1, 1 }, { 2, 2 } }, // przekątna backslashowa
{ { 2, 0 }, { 1, 1 }, { 0, 2 } } }; // przekątna slashowa
FIELD Pole, ZgodnePole;
unsigned uLiczbaZgodnychPol;
for( int i = 0; i < 8; ++i )
{
// i przebiega po kolejnych możliwych liniach (jest ich osiem)
// zerujemy zmienne pomocnicze
Pole = ZgodnePole = FLD_EMPTY; // obie zmienne zostaną ustawione na FLD_EMPTY
uLiczbaZgodnychPol = 0;
for( int j = 0; j < 3; ++j )
{
// j przebiega po trzech polach w każdej linii
// pobieramy rzeczone pole
// to zdecydowanie najbardziej pogmatwane wyrażenie :)
Pole = g_aPlansza[ LINIE[ i ][ j ][ 0 ] ][ LINIE[ i ][ j ][ 1 ] ];
// jeżeli sprawdzane pole różni się od tego, które ma się zgadzać...
if( Pole != ZgodnePole )
{
// to zmieniamy zgadzane pole na to aktualne
ZgodnePole = Pole;
uLiczbaZgodnychPol = 1;
}
else
// jeśli natomiast oba pola się zgadzają, no to inkrementujemy
// licznik takich zgodnych pól
++uLiczbaZgodnychPol;
}
// teraz sprawdzamy, czy udało nam się zgodzić linię
if( uLiczbaZgodnychPol == 3 && ZgodnePole != FLD_EMPTY )
{
// jeżeli tak, no to ustawiamy stan gry na wygraną
g_StanGry = GS_WON;
// przerywamy pętlę i funkcję
return true;
}
}
// istnieje jeszcze możliwość, że nastąpił remis
// wtedy funkcja dojdzie tutaj i cała plansza będzie pokryta krzyżykami i kółkami
// sprawdzamy to przy pomocy odpowiedniej pętli, zliczającej zapełnione pola
// jeżeli jest ich tyle, ile wszystkich, no to kończymy remisem
unsigned uLiczbaZapelnionychPol = 0;
for( int i = 0; i < 3; ++i )
for( int j = 0; j < 3; ++j )
if( g_aPlansza[ i ][ j ] != FLD_EMPTY )
++uLiczbaZapelnionychPol;
if( uLiczbaZapelnionychPol == 3 * 3 )
{
// ustawiamy stan gry na remis
g_StanGry = GS_DRAW;
// kończymy funkcję
return true;
}
// oczywiście, może się zdarzyć, iż gra po prostu toczy się dalej
// w takim wypadku zmieniamy jedynie aktualnego gracza
g_AktualnyGracz =( g_AktualnyGracz == SGN_CIRCLE ? SGN_CROSS: SGN_CIRCLE );
// zwracamy true
return true;
}
I zadanie do niego brzmi:"W naszej grze w kółko i krzyżyk jest ukryta pewna usterka. Objawia się
wtedy, gdy gracz wpisze coś innego niż liczbę jako numer pola. Spróbuj naprawić
ten błąd; niech program reaguje tak samo, jak na wartość spoza przedziału
<1; 9>"
Pierwszą myślą było rzutowanie, ale jakoś mnie nie przekonało. W internecie wyczytałem o ciekawej funkcji atoi(), która konwertuje wartość zapisaną w łańcuchu napisów w liczbe całkowitą. Czy będzie to poprawne rozwiązanie? Ktoś może ma inny pomysł?