Klasa Runner — druga, lepsza implementacja

Ciąg dalszy wykorzystania mechanizmów obiektowych w przykładzie wykorzystującym klasę Runner. Prosty program prezentujący klasę Runner wyposażoną w konstruktor. Klasa wykorzystuje hermetyzację, jej pola zostały uprywatnione i wyposażone w odpowiednie funkcje dostępowe set/get.

Program stanowi uzupełnienie wykładu z programowania w językach C/C++. Koncepcja klasy Runner została omówiona we wprowadzeniu do programowania obiektowego (pdf).

Do pobrania wersja źródłowa pliku (main02.cpp).

Poprzednia wersja: main02.cpp.

Uwaga! Implementacja tylko dla Windows, obsługa konsoli realizowana za pośrednictwem WinApi. Najlepiej kompilować używając MinGW (środowiska Code::Blocka, DevC++) lub kompilatorów w środowisku C++ Builder. Proszę unikać środowisk VisualC++, CLion, QT, będą stwarzać problemy.

// Runner, wersja 02

#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <ctime>
#include <conio.h>

#if defined(__MINGW32__) || defined(_MSC_VER)
#include <windows.h>
#endif

// Kody klawiszy sterujących kursorem i klawiszy specjalnych
enum KEY_CODES
{
#ifdef __BORLANDC__
	KEY_BLANK = 0x0000,
#elif (defined(__GNUC__) && defined(__MINGW32__)) || defined(_MSC_VER)
	KEY_BLANK = 0x00e0,
#endif
	KEY_UP = 0x4800,
	KEY_DOWN = 0x5000,
	KEY_LEFT = 0x4b00,
	KEY_RIGHT = 0x4d00,
	KEY_ESC = 0x001b
};

// Prototypy funkcji obsługi konsoli
int getKey();
void clearScreen();
void writeCharXY(int x, int y, char c);
void writeStrXY(int x, int y, char s[]);
void writeIntXY(int x, int y, int i);
void writeDoubleXY(int x, int y, double d);
void cursorOff();
void cursorOn();

// Rozmiary ekranu konsoli, nie uwzglêdniają ostatniego wiersza
// ten jest wierszam statusy i zawiera informacje o stanie gry
const int NUM_OF_COLS = 80;
const int NUM_OF_ROWS = 24;

// Klasa Runner z polami prywatnymi. Domyślna pozycja startowa to (1, 1),
// nie pozwalamy na ustawienie pozycji < 1 i odpowiednio > NUM_OF_COLS,
// NUM_OF_ROWS. Próba ustawienia nieprawidłowej parametrów skutkuje
// ustwieniem wartości domyślnych (1, 1, '*').
class Runner
{
  public:
    Runner(int startX = 1, int startY = 1, char startShape = '*');

    // "Ustawiacze"
    void setX(int newX);
    void setY(int newY);
    void setShape(char newShape);

    // "Pobieracze"
    int  getX();
    int  getY();
    char getShape();

    // "Realizatory"
    void show();
    void hide();

    void moveUp();
    void moveDown();
    void moveLeft();
    void moveRight();

  private:
    int  x, y;
    char shape;
    // Pomocnicze funkcje weryfikacji pozycji ekranowej
    bool isXOnScreen(int x);
    bool isYOnScreen(int y);
};

// Definicje funkcji składowych poza deklaracją klasy
Runner::Runner(int startX, int startY, char startShape)
{
  // Wartości x, y i shape mogłyby być ustawione na liście inicjalizacyjnej.
  // Ale wartości paramterów konstruktora mogły być nieprawidłowe, dlatego
  // są ustawione w ciele konstruktora z wykorzystaniem odpowiednich funkcji
  // ustawiających
  setX(startX);
  setY(startY);
  setShape(startShape);
}

//// Trochę inna wersja konstruktora, wykorzystująca listę
//// inicjalizacyjną. Do kontroli poprawności wykorzystywany jest
//// trójargumentowy operator warunkowy. Ta wersja kłóci się
//// z zasadą Single Responsibility (SOLID), weryfikacja i ustalanie
//// wartości domyśłnych jest elementem odpowiednich funkcji set...,
//// i to raczej one powinny być wykorzystywane
//Runner::Runner(int startX, int startY, char startShape)
//: x(isXOnScreen(startX) ? startX : 1),
//  y(isYOnScreen(startY) ? startY : 1),
//  shape((startShape> 32 && startShape<= 127) ? startShape : '*')
//{
//}

void Runner::setX(int newX)
{
   x = isXOnScreen(newX) ? newX : 1;

//  Zamiast:
//  if(isXOnScreen(newX))
//    x = newX;
//  else
//    x = 1;
}

void Runner::setY(int newY)
{
  y = isYOnScreen(newY) ? newY : 1;
}

void Runner::setShape(char newShape)
{
  shape = (newShape > 32 && newShape <= 127)
          ? newShape : '*';
}

void Runner::show()
{
  writeCharXY(x, y, shape);
}

void Runner::hide()
{
  writeCharXY(x, y, ' ');
}

int Runner::getX()
{
  return x;
}

int Runner::getY()
{
  return y;
}

char Runner::getShape()
{
  return shape;
}

void Runner::moveUp()
{
  hide();
  if(y > 1)
    --y;
  show();
}

void Runner::moveDown()
{
  hide();
  if(y < NUM_OF_ROWS)
    ++y;
  show();
}

void Runner::moveLeft()
{
  hide();
  if(x > 1)
    --x;
  show();
}

void Runner::moveRight()
{
  hide();
  if(x < NUM_OF_COLS)
    ++x;
  show();
}

bool Runner::isXOnScreen(int newX)
{
  return (newX > 0 && newX <= NUM_OF_COLS);
}

bool Runner::isYOnScreen(int newY)
{
  return (newY > 0 && newY <= NUM_OF_ROWS);
}

int main()
{
  int    key;
  Runner R;

  R.setX(40);
  R.setY(12);
  R.setShape('*');

  cursorOff();
  clearScreen();
  R.show();

  do
  {
    switch( key = getKey() )
    {
      case KEY_UP    : R.moveUp();
                       break;
      case KEY_DOWN  : R.moveDown();
                       break;
      case KEY_LEFT  : R.moveLeft();
                       break;
      case KEY_RIGHT : R.moveRight();
                     break;
    }//switch
  }
  while( key != KEY_ESC );
  clearScreen();
  cursorOn();

  return EXIT_SUCCESS;
}

// Funkcje obsługi konsoli, sprawa techniczna, w sensie merytorycznym nieistotna
int getKey()
{
	int key = _getch();
	return (key == KEY_BLANK) ? _getch() << 8 : key;
}

void clearScreen()
{
#ifdef __BORLANDC__
	clrscr();
#elif (defined(__GNUC__) && defined(__MINGW32__)) || defined(_MSC_VER)
	COORD leftTop = { 0, 0 };
	CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
	int numOfCells = 80 * 25;
	DWORD writtenItems;
	HANDLE consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
	if (GetConsoleScreenBufferInfo(consoleHandle, &consoleInfo))
		numOfCells = consoleInfo.dwSize.X * consoleInfo.dwSize.Y;

	FillConsoleOutputAttribute(consoleHandle, 0xf,
		numOfCells, leftTop, &writtenItems);
	FillConsoleOutputCharacter(consoleHandle, ' ',
		numOfCells, leftTop, &writtenItems);
#else
#error "Nieobslugiwana platforma"
#endif
}

void writeCharXY(int x, int y, char c) {
#ifdef __BORLANDC__
	gotoxy(x, y);
	putch(c);
#elif (defined(__GNUC__) && defined(__MINGW32__)) || defined(_MSC_VER)
	COORD cursorPos;
	DWORD written;

	cursorPos.X = x - 1;
	cursorPos.Y = y - 1;

	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), cursorPos);
	WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), &c, 1, &written, 0);
#else
#error "Nieobslugiwana platforma"
#endif
}

void writeStrXY(int x, int y, char s[])
{
#ifdef __BORLANDC__
	gotoxy(x, y);
	cputs(s);
#elif (defined(__GNUC__) && defined(__MINGW32__)) || defined(_MSC_VER)
	COORD cursorPos;
	DWORD written;

	cursorPos.X = x - 1;
	cursorPos.Y = y - 1;

	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), cursorPos);
	WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), s, strlen(s), &written, 0);
#else
#error "Nieobslugiwana platforma"
#endif
}

void writeIntXY(int x, int y, int i)
{
	char s[80];
#if defined(_MSC_VER)
	sprintf_s(s, 80, "%d", i);
#else
	sprintf(s, "%d", i);
#endif
	writeStrXY(x, y, s);
}

void writeDoubleXY(int x, int y, double d)
{
	char s[80];
#if defined(_MSC_VER)
	sprintf_s(s, 80, "%g", d);
#else
	sprintf(s, "%g", d);
#endif
	writeStrXY(x, y, s);
}

void cursorOff()
{
#ifdef __BORLANDC__
	_setcursortype(_NOCURSOR);
#elif (defined(__GNUC__) && defined(__MINGW32__)) || defined(_MSC_VER)
	CONSOLE_CURSOR_INFO cursorInfo;
	HANDLE consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
	GetConsoleCursorInfo(consoleHandle, &cursorInfo);
	cursorInfo.bVisible = false;
	SetConsoleCursorInfo(consoleHandle, &cursorInfo);
#else
#error "Nieobslugiwana platforma"
#endif
}

void cursorOn()
{
#ifdef __BORLANDC__
	_setcursortype(_NORMALCURSOR);
#elif (defined(__GNUC__) && defined(__MINGW32__)) || defined(_MSC_VER)
	CONSOLE_CURSOR_INFO cursorInfo;
	HANDLE consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
	GetConsoleCursorInfo(consoleHandle, &cursorInfo);
	cursorInfo.bVisible = true;
	SetConsoleCursorInfo(consoleHandle, &cursorInfo);
#else
#error "Nieobslugiwana platforma"
#endif
}