Created
March 18, 2024 19:36
-
-
Save niuniulla/2af219b9ee749985f3a475041098f472 to your computer and use it in GitHub Desktop.
A simple game example using X11.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Simple game example using X11. | |
*/ | |
#include <X11/Xlib.h> | |
#include <X11/Xutil.h> | |
#include <X11/Xos.h> | |
#include <iostream> | |
#include <unistd.h> | |
#include <stdexcept> | |
#include <string_view> | |
#include <cmath> | |
#include <stdint.h> | |
#include <vector> | |
#include <algorithm> | |
enum keyName | |
{ | |
KEY_ENTER = 36, | |
KEY_UP = 111, | |
KEY_DOWN = 116, | |
KEY_LEFT = 113, | |
KEY_RIGHT = 114 | |
}; | |
template<typename... Args> | |
static void print(std::string_view msg, Args... args) | |
{ | |
std::cout << msg; | |
(std::cout << ... << args) << std::endl; | |
} | |
struct coord2d | |
{ | |
int x=0, y=0; | |
coord2d() = default; | |
coord2d(int x, int y) : x(x), y(y) {}; | |
}; | |
/* class to handle display */ | |
class game_window | |
{ | |
public: | |
game_window(); | |
game_window(int, int); | |
~game_window(); | |
void refresh(bool); | |
void clear(); | |
Display *const getDisplay(); | |
coord2d getCursorPosition(); | |
XEvent & getEvent() {return m_event;} | |
keyName getKeyPressed(); | |
int getWindowWidth() { return m_nScreenWidth;} | |
int getWindowHeight() { return m_nScreenHeight;} | |
void drawRect(XRectangle, uint32_t); | |
void drawRect(XRectangle*, int, uint32_t); | |
void showText(const char*, coord2d, uint32_t); | |
bool isInScreen(coord2d, int, int); | |
bool isInScreen(XRectangle); | |
// color definitions | |
uint32_t m_black; | |
uint32_t m_white; | |
uint32_t m_red; | |
uint32_t m_blue; | |
private: | |
Window m_window; | |
Display *m_display; | |
GC m_gc; | |
int m_screen; | |
int m_nScreenWidth = 800; // Console Screen Size X (columns) | |
int m_nScreenHeight = 600; | |
XEvent m_event; | |
}; | |
game_window::game_window() : | |
m_nScreenWidth (800), m_nScreenHeight(600) | |
{ | |
m_display = XOpenDisplay(0); //create display | |
if (m_display == nullptr) | |
throw std::runtime_error("Display not valid."); | |
m_screen = DefaultScreen(m_display); | |
m_black = BlackPixel(m_display, m_screen); | |
m_white = WhitePixel(m_display, m_screen); | |
m_red = 255 * 65536 + 0 * 256 + 0; | |
m_blue = 0 * 65536 + 0 * 256 + 255; | |
m_window = XCreateSimpleWindow(m_display, DefaultRootWindow(m_display), 0, 0, | |
m_nScreenWidth, m_nScreenHeight, 100, m_white, m_black); | |
m_gc = XCreateGC(m_display, m_window, 0, 0); | |
XSetStandardProperties(m_display, m_window, "Game Window", "Hi", None, NULL, 0, NULL); | |
XSelectInput(m_display, m_window, KeyPressMask | // key press | |
StructureNotifyMask); // such as mapnotify | |
XMapRaised(m_display, m_window); | |
for (;;) // wait for the mapnotify from x server | |
{ | |
XNextEvent(m_display, &m_event); | |
if (m_event.type == MapNotify) | |
break; | |
} | |
} | |
game_window::game_window(int screenW, int screenH) : | |
m_nScreenWidth (screenW), m_nScreenHeight(screenH) | |
{ | |
game_window(); | |
} | |
game_window::~game_window() | |
{ | |
XDestroyWindow(m_display, m_window); | |
} | |
void game_window::refresh(bool clear) | |
{ | |
if (clear) this->clear(); | |
//print("ready for display", ""); | |
XFlush(m_display); | |
//print("display done", ""); | |
} | |
void game_window::clear() | |
{ | |
XClearWindow(m_display, m_window); | |
} | |
Display *const game_window::getDisplay() | |
{ | |
return m_display; | |
} | |
coord2d game_window::getCursorPosition() | |
{ | |
return coord2d(); | |
} | |
void game_window::drawRect(XRectangle rec, uint32_t color) | |
{ | |
XSetForeground(m_display, m_gc, color); | |
XFillRectangle(m_display, m_window, m_gc, rec.x, rec.y, rec.width, rec.height); | |
refresh(false); | |
} | |
void game_window::drawRect(XRectangle *recs, int num, uint32_t color) | |
{ | |
XSetForeground(m_display, m_gc, color); | |
XFillRectangles(m_display, m_window, m_gc, recs, num); | |
refresh(false); | |
} | |
keyName game_window::getKeyPressed() | |
{ | |
return static_cast<keyName>(m_event.xkey.keycode); | |
} | |
void game_window::showText(const char* s, coord2d pos, uint32_t color) | |
{ | |
XSetForeground(m_display, m_gc, color); | |
XDrawString(m_display, m_window, m_gc, pos.x, pos.y, s, 200); | |
refresh(false); | |
} | |
bool game_window::isInScreen(coord2d pos, int sizex, int sizey) | |
{ | |
// the origin is ar the bottom left of the corner | |
if (pos.x + sizex < m_nScreenWidth && pos.x > 0 && | |
pos.y + sizey < m_nScreenHeight && pos.y > 0 ) | |
return 1; | |
return 0; | |
} | |
bool game_window::isInScreen(XRectangle rec) | |
{ | |
// the origin is at the bottom left of the corner | |
if (rec.x + rec.width < m_nScreenWidth && rec.x > 0 && | |
rec.y + rec.height < m_nScreenHeight && rec.y > 0 ) | |
return 1; | |
return 0; | |
} | |
/* class of pion in game */ | |
class Slab | |
{ | |
public: | |
Slab() = default; | |
Slab(XRectangle rec, int id, uint32_t color) : | |
m_rectangle(rec), | |
m_id(id), | |
m_color(color), | |
m_isActive(true) | |
{}; | |
~Slab() = default; | |
Slab(const Slab &s); // copy constructor | |
void deactivate() {m_isActive = false;} | |
bool isCollided(const XRectangle); | |
bool isCollided(Slab&, bool); | |
bool isCollided(std::vector<Slab>, bool); | |
inline XRectangle getRectangle() {return m_rectangle;}; | |
inline uint32_t getColor() {return m_color;}; | |
inline const bool isValid (){return m_isActive;}; | |
inline void setID(int id) {m_id = id;}; | |
inline int getID() {return m_id;}; | |
inline void setValid(bool v) {m_isActive = v;}; | |
protected: | |
XRectangle m_rectangle; | |
uint32_t m_color; | |
bool m_isActive = true; | |
int m_id; | |
}; | |
Slab::Slab(const Slab &s) | |
{ | |
m_rectangle = s.m_rectangle; | |
m_color = s.m_color; | |
m_id = s.m_id; | |
m_isActive = s.m_isActive; | |
} | |
bool Slab::isCollided(const XRectangle rec) | |
{ | |
int dx = std::abs(m_rectangle.x - rec.x); | |
int dy = std::abs(m_rectangle.y - rec.y); | |
return (dx < m_rectangle.width && dy < m_rectangle.height); | |
} | |
bool Slab::isCollided(Slab &s, bool deactivate=false) | |
{ | |
if (!isCollided(s.m_rectangle)) | |
return false; | |
else | |
if (deactivate) | |
s.deactivate(); | |
return true; | |
} | |
bool Slab::isCollided(std::vector<Slab> slabs, bool deactivate) | |
{ | |
int numCollision = 0; | |
for (auto s : slabs) | |
{ | |
if (s.isCollided(m_rectangle)) | |
{ | |
++numCollision; | |
if (deactivate) s.m_isActive = false; | |
} | |
} | |
return bool(numCollision); | |
} | |
/* Class to handle game stuff */ | |
class Gamer : public Slab | |
{ | |
public: | |
Gamer() = default; | |
Gamer(XRectangle rec, int id, uint32_t color, int life) : Slab(rec, id, color), m_life(life) {}; | |
Gamer(const Gamer &g): Slab(g) {m_life = g.m_life;}; // cop constructor | |
inline void move(int dx, int dy) {m_rectangle.x += dx; m_rectangle.y += dy;}; | |
inline void updateScore(int s) {m_life += s;}; | |
inline bool isAlive() {return m_life > 0;}; | |
inline int getLife() {return m_life;}; | |
inline void setLife(const int life) {m_life = life;}; | |
private: | |
int m_life; | |
}; | |
/* Class of stone */ | |
class Stone : public Slab | |
{ | |
public: | |
Stone() = default; | |
Stone(XRectangle rec, int id, uint32_t color) : Slab(rec, id, color) {}; | |
}; | |
/* Class of food for slab*/ | |
class Food : public Slab | |
{ | |
public: | |
Food() = default; | |
Food(XRectangle rec, int id, uint32_t color) : Slab(rec, id, color) {}; | |
Food(const Food &f) : Slab(f) { }; //copy constructor | |
void deactivate(); | |
bool isEaten() {return m_isActive;}; | |
}; | |
void Food::deactivate() | |
{ | |
m_isActive = false; | |
m_rectangle.x = -100; | |
m_rectangle.y = -100; | |
} | |
/* Class to handle game stuff */ | |
class Game | |
{ | |
public: | |
Game(); | |
~Game(); | |
void init(); | |
void run(); | |
void getEvent(); | |
void handleEvent(); | |
void redraw(); | |
void updateGamerPosition(int, int); | |
XRectangle getRondomRectangle(); | |
private: | |
game_window m_gw; //X11 | |
Gamer m_gamer; | |
std::vector<Stone> m_stones; | |
std::vector<Food> m_foods; | |
int m_numStones = 20; | |
int m_numFoods = 20; | |
int m_slabSize = 10; | |
int m_running = 1; | |
int m_velocity = 4; | |
keyName m_key; | |
}; | |
Game::Game() {}; | |
Game::~Game() {}; | |
void Game::init() | |
{ | |
m_gw.clear(); // clear the scene | |
m_stones.clear(); | |
m_foods.clear(); | |
int m_numStones = 10; | |
int m_numFoods = 10; | |
srand((unsigned) time(NULL)); // seed for random numbers | |
// generate the gamer | |
XRectangle rec; | |
for (;;) | |
{ | |
rec = getRondomRectangle(); | |
if (m_gw.isInScreen(coord2d(rec.x, rec.y), rec.width, rec.height)) break; | |
} | |
m_gamer = Gamer(rec, 0, m_gw.m_red, 100); // id is not used, set life to 100 | |
// generate the obstacles | |
m_stones.reserve(m_numStones); | |
for (int i = 0; i < m_numStones; ++i) | |
{ | |
for (;;) | |
{ | |
rec = getRondomRectangle(); | |
if (m_gw.isInScreen(coord2d(rec.x, rec.y), rec.width, rec.height) | |
&& !m_gamer.isCollided(rec)) | |
break; | |
} | |
m_stones.push_back(Stone(rec, i+1, m_gw.m_white)); // use copy constructor | |
} | |
// generate the food | |
m_foods.reserve(m_numFoods); | |
for (int i = 0; i < m_numFoods; ++i) | |
{ | |
for (;;) | |
{ | |
rec = getRondomRectangle(); | |
if (m_gw.isInScreen(coord2d(rec.x, rec.y), rec.width, rec.height) | |
&& !m_gamer.isCollided(rec) | |
&& !std::any_of(m_stones.begin(), m_stones.end(), [rec](Stone s){return s.isCollided(rec);}) | |
) | |
break; | |
} | |
m_foods.push_back(Food(rec, i+1, m_gw.m_blue)); | |
} | |
} | |
void Game::updateGamerPosition(int direction, int step) | |
{ | |
(direction == 0) ? m_gamer.move(step * m_velocity, 0) : m_gamer.move(0, step * m_velocity); | |
} | |
void Game::redraw() | |
{ | |
m_gw.clear(); | |
m_gw.drawRect(m_gamer.getRectangle(), m_gamer.getColor()); | |
std::for_each(m_stones.begin(), m_stones.end(), [this](Stone s){m_gw.drawRect(s.getRectangle(), s.getColor());}); | |
std::for_each(m_foods.begin(), m_foods.end(), [this](Food s){s.isValid()? m_gw.drawRect(s.getRectangle(), s.getColor()) : void();}); | |
} | |
void Game::run() | |
{ | |
while (true) | |
{ | |
m_gw.refresh(true); | |
m_gw.showText("press enter key to start", coord2d(10, 100), m_gw.m_white); | |
for (;;) | |
{ | |
getEvent(); | |
if (m_key == KEY_ENTER) | |
{ | |
print("game started", ""); | |
break; | |
} | |
} | |
init(); | |
m_running = 1; | |
while (m_running) | |
{ | |
redraw(); | |
getEvent(); | |
handleEvent(); | |
} | |
print("Score: ", m_gamer.getLife()); | |
} | |
} | |
void Game::getEvent() | |
{ | |
XNextEvent(m_gw.getDisplay(), &m_gw.getEvent()); | |
m_key = m_gw.getKeyPressed(); | |
//std::cout << "the key code is: " << m_key << std::endl; | |
} | |
void Game::handleEvent() | |
{ | |
switch (m_key) | |
{ | |
case KEY_UP: | |
updateGamerPosition(1, -1); | |
break; | |
case KEY_DOWN: | |
updateGamerPosition(1, 1); | |
break; | |
case KEY_LEFT: | |
updateGamerPosition(0, -1); | |
break; | |
case KEY_RIGHT: | |
updateGamerPosition(0, 1); | |
break; | |
default: | |
break; | |
} | |
if (std::any_of(m_stones.begin(), m_stones.end(), [this](Stone s){return m_gamer.isCollided(s);}) | |
|| !m_gw.isInScreen(coord2d(m_gamer.getRectangle().x, m_gamer.getRectangle().y), m_slabSize, m_slabSize) | |
|| m_gamer.getLife() <= 0) | |
{ | |
print("game over!", " "); | |
m_running = 0; // restart | |
} | |
else if (auto it = std::find_if(m_foods.begin(), m_foods.end(), [this](Food s){return m_gamer.isCollided(s);}); | |
it != std::end(m_foods) | |
) | |
{ | |
//print("ate food: ", (*it).getID()); | |
m_foods[(*it).getID()-1].deactivate(); | |
m_gamer.updateScore(100); | |
if (std::none_of(m_foods.begin(), m_foods.end(), [this](Food s){return s.isValid();})) | |
{ | |
print("You win!"); | |
m_running = 0; // restart game | |
} | |
} | |
else | |
{ | |
m_gamer.updateScore(-1); | |
} | |
} | |
XRectangle Game::getRondomRectangle() | |
{ | |
short randnX = rand() % m_gw.getWindowWidth(); | |
short randnY = rand() % m_gw.getWindowHeight(); | |
XRectangle rect = {randnX, randnY, (unsigned short)m_slabSize, (unsigned short)m_slabSize}; | |
return rect; | |
} | |
int main() | |
{ | |
Game game; | |
game.run(); | |
return 0; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment