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
}