Skip to content

Instantly share code, notes, and snippets.

@niuniulla
Created March 18, 2024 19:36
Show Gist options
  • Save niuniulla/2af219b9ee749985f3a475041098f472 to your computer and use it in GitHub Desktop.
Save niuniulla/2af219b9ee749985f3a475041098f472 to your computer and use it in GitHub Desktop.
A simple game example using X11.
/*
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