X11 五子棋
#include <X11/Xlib.h> #include <stdlib.h> #include <X11/keysym.h> #include <cstring> #include <iostream> using namespace std; enum CELL_STATE { CELL_EMPTY, CELL_O, CELL_X }; enum GAME_STATE { X_TURN, O_TURN, X_WON, O_WON, DRAW }; class Game { public: Game(); CELL_STATE getCellState(int x, int y); GAME_STATE getGameState(); bool makeMove(int x, int y); bool makeRandomMove(); void restart(); private: CELL_STATE _boardState[3][3]; GAME_STATE _gameState; int _turnsPassed; void _checkForEnd(); }; inline CELL_STATE Game::getCellState(int x, int y) { return _boardState[x][y]; } inline GAME_STATE Game::getGameState() { return _gameState; } Game::Game() { restart(); } void Game::restart() { for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { _boardState[i][j] = CELL_EMPTY; } } _gameState = X_TURN; _turnsPassed = 0; } bool Game::makeMove(int x, int y) { GAME_STATE nextState; CELL_STATE curPlayerCell; if(_gameState == O_TURN) { curPlayerCell = CELL_O; nextState = X_TURN; } else if(_gameState == X_TURN) { curPlayerCell = CELL_X; nextState = O_TURN; } else { return false; } if(_boardState[x][y] != CELL_EMPTY) { return false; } _boardState[x][y] = curPlayerCell; _turnsPassed++; _gameState = nextState; _checkForEnd(); return true; } bool Game::makeRandomMove() { GAME_STATE nextState; CELL_STATE curPlayerCell; if(_gameState == O_TURN) { curPlayerCell = CELL_O; nextState = X_TURN; } else if(_gameState == X_TURN) { curPlayerCell = CELL_X; nextState = O_TURN; } else { return false; } int nFreeCells = 0; int freeCells[9][2]; for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { if(_boardState[i][j] == CELL_EMPTY) { freeCells[nFreeCells][0] = i; freeCells[nFreeCells][1] = j; nFreeCells++; } } } if(nFreeCells == 0) { return false; } int choice = rand() % nFreeCells; int x = freeCells[choice][0]; int y = freeCells[choice][1]; _boardState[x][y] = curPlayerCell; _turnsPassed++; _gameState = nextState; _checkForEnd(); return true; } void Game::_checkForEnd() { if(_turnsPassed == 9) { _gameState = DRAW; } CELL_STATE winningCell = CELL_EMPTY; for(int i = 0; i < 3; i++) { if((_boardState[i][0] == _boardState[i][1]) && (_boardState[i][1] == _boardState[i][2]) && (_boardState[i][0] != CELL_EMPTY)) { winningCell = _boardState[i][0]; } } for(int i = 0; i < 3; i++) { if((_boardState[0][i] == _boardState[1][i]) && (_boardState[1][i] == _boardState[2][i]) && (_boardState[0][1] != CELL_EMPTY)) { winningCell = _boardState[0][i]; } } if((_boardState[0][0] == _boardState[1][1]) && (_boardState[1][1] == _boardState[2][2]) && (_boardState[0][0] != CELL_EMPTY)) { winningCell = _boardState[0][0]; } if((_boardState[2][0] == _boardState[1][1]) && (_boardState[1][1] == _boardState[0][2]) && (_boardState[2][0] != CELL_EMPTY)) { winningCell = _boardState[2][0]; } if(winningCell == CELL_X) { _gameState = X_WON; } else if(winningCell == CELL_O) { _gameState = O_WON; } } class HelloWorld { public: HelloWorld(Display* display); Window getWindow(); void draw(); void map(); void handleKeyPress(const XKeyEvent& event); void handleMousePress(const XButtonEvent& event); void restartGame(); private: const static int MIN_CELL_SIZE; const static int STRING_HEIGHT; const static char WINDOW_FONT[]; Display* _display; Screen* _screen; Window _window; Font _font; unsigned long _blackColor; unsigned long _whiteColor; Game _game; void _drawO(const GC& gc, int x, int y, int w, int h); void _drawX(const GC& gc, int x, int y, int w, int h); void _drawString(const GC& gc, const char* str, int x, int y); void _drawStringCentered(const GC& gc, const char* str, int x, int y, int w, int h); }; inline Window HelloWorld::getWindow() { return _window; } const int HelloWorld::MIN_CELL_SIZE = 50; const int HelloWorld::STRING_HEIGHT = 20; const char HelloWorld::WINDOW_FONT[] = "-*-*-*-*-*-*-12-*-*-*-*-*-*-*"; HelloWorld::HelloWorld(Display* display) : _game() { _display = display; _screen = XDefaultScreenOfDisplay(display); _blackColor = BlackPixelOfScreen(_screen); _whiteColor = WhitePixelOfScreen(_screen); _font = XLoadFont(_display, WINDOW_FONT); int screenWidth = XWidthOfScreen(_screen); int screenHeight = XHeightOfScreen(_screen); int windowWidth = MIN_CELL_SIZE * 3; int windowHeight = MIN_CELL_SIZE * 3 + STRING_HEIGHT * 3; int windowX = (screenWidth + windowWidth) / 2; int windowY = (screenHeight + windowHeight) / 2; _window = XCreateSimpleWindow(_display, XRootWindowOfScreen(_screen), windowX, windowY, windowWidth, windowHeight, 1, _blackColor, _whiteColor); long eventMask = ButtonPressMask | ExposureMask | KeyPressMask; XSelectInput(_display, _window, eventMask); draw(); } void HelloWorld::draw() { // Getting window dimensions. Window rootWindow; int x, y; unsigned int width, height, borderWidth, bitDepth; XGetGeometry(_display, _window, &rootWindow, &x, &y, &width, &height, &borderWidth, &bitDepth); // Setting up the GC. GC gc = XDefaultGCOfScreen(_screen); XSetBackground(_display, gc, _whiteColor); XSetFont(_display, gc, _font); XSetForeground(_display, gc, _blackColor); // Clearing the window. XClearArea(_display, _window, 0, 0, width, height, false); // Is the window large enough for us? if((width < MIN_CELL_SIZE * 3) || (height < (MIN_CELL_SIZE * 3 + STRING_HEIGHT * 3))) { _drawStringCentered(gc, "Window too small.", 0, 0, width, height); return; } // Calculating grid cell sizes. int xStepSize = width / 3; int yStepSize = (height - STRING_HEIGHT * 3) / 3; // Drawing glyphs. for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { switch(_game.getCellState(i, j)) { case CELL_O: _drawO(gc, xStepSize * i, STRING_HEIGHT * 2 + yStepSize * j, xStepSize, yStepSize); break; case CELL_X: _drawX(gc, xStepSize * i, STRING_HEIGHT * 2 + yStepSize * j, xStepSize, yStepSize); break; default: break; } } } XSetBackground(_display, gc, _whiteColor); XSetForeground(_display, gc, _blackColor); // Drawing the grid lines. for(int i = 0; i <= 3; i++) { int yValue = STRING_HEIGHT * 2 + yStepSize * i; XDrawLine(_display, _window, gc, 0, yValue, width, yValue); } for(int i = 1; i <= 2; i++) { int xValue = xStepSize * i; XDrawLine(_display, _window, gc, xValue, STRING_HEIGHT * 2, xValue, height - STRING_HEIGHT); } // Drawing the strings. _drawStringCentered(gc, "Hello, World!", 0, 0, width, STRING_HEIGHT); _drawStringCentered(gc, "[R]estart", 0, height - STRING_HEIGHT, width, STRING_HEIGHT); switch(_game.getGameState()) { case X_TURN: _drawStringCentered(gc, "It is your turn to play.", 0, STRING_HEIGHT, width, STRING_HEIGHT); break; case O_TURN: _drawStringCentered(gc, "Please wait, thinking...", 0, STRING_HEIGHT, width, STRING_HEIGHT); break; case X_WON: _drawStringCentered(gc, "You have won.", 0, STRING_HEIGHT, width, STRING_HEIGHT); break; case O_WON: _drawStringCentered(gc, "You have lost.", 0, STRING_HEIGHT, width, STRING_HEIGHT); break; case DRAW: _drawStringCentered(gc, "It is a draw.", 0, STRING_HEIGHT, width, STRING_HEIGHT); break; default: break; } } void HelloWorld::map() { XMapWindow(_display, _window); } void HelloWorld::handleKeyPress(const XKeyEvent& event) { unsigned int keyCode_r = XKeysymToKeycode(_display, XK_r); unsigned int keyCode_q = XKeysymToKeycode(_display, XK_q); if(event.keycode == keyCode_r) { _game.restart(); draw(); } else if(event.keycode == keyCode_q) { // Quit, somehow } } void HelloWorld::handleMousePress(const XButtonEvent& event) { if(event.button == 1) { Window rootWindow; int winX, winY; unsigned int width, height, borderWidth, bitDepth; XGetGeometry(_display, _window, &rootWindow, &winX, &winY, &width, &height, &borderWidth, &bitDepth); if((event.y >= 2 * STRING_HEIGHT) && (event.y < (height - STRING_HEIGHT))) { int xStepSize = width / 3; int yStepSize = (height - STRING_HEIGHT * 3) / 3; int cellX = event.x / xStepSize; int cellY = (event.y - STRING_HEIGHT * 2) / yStepSize; if(_game.makeMove(cellX, cellY)) { if(_game.getGameState() == O_TURN) { _game.makeRandomMove(); } draw(); } } } } void HelloWorld::restartGame() { _game.restart(); draw(); } void HelloWorld::_drawO(const GC& gc, int x, int y, int w, int h) { XSetBackground(_display, gc, _whiteColor); XSetForeground(_display, gc, _blackColor); XFillArc(_display, _window, gc, x + w/10, y + h/10, (w*4)/5, (h*4)/5, 0, 360*64); XSetForeground(_display, gc, _whiteColor); XFillArc(_display, _window, gc, x + w/5, y + h/5, (w*3)/5, (h*3)/5, 0, 360*64); } void HelloWorld::_drawX(const GC& gc, int x, int y, int w, int h) { static const int POINT_COUNT = 12; static const XPoint RAW_CROSS[] = { {1, 2}, {2, 1}, {5, 4}, {8, 1}, {9, 2}, {6, 5}, {9, 8}, {8, 9}, {5, 6}, {2, 9}, {1, 8}, {4, 5} }; XPoint scaledCross[POINT_COUNT]; for(int i = 0; i < POINT_COUNT; i++) { scaledCross[i].x = (RAW_CROSS[i].x * w) / 10 + x; scaledCross[i].y = (RAW_CROSS[i].y * h) / 10 + y; } XSetBackground(_display, gc, _whiteColor); XSetForeground(_display, gc, _blackColor); XFillPolygon(_display, _window, gc, scaledCross, POINT_COUNT, Nonconvex, CoordModeOrigin); } void HelloWorld::_drawString(const GC& gc, const char* str, int x, int y) { XDrawString(_display, _window, gc, x, y, str, strlen(str)); } void HelloWorld::_drawStringCentered(const GC& gc, const char* str, int x, int y, int w, int h) { int direction, ascent, descent; XCharStruct strDimensions; XTextExtents(XQueryFont(_display, XGContextFromGC(gc)), str, strlen(str), &direction, &ascent, &descent, &strDimensions); int newX = x + (w - strDimensions.width) / 2; int newY = y + (h + strDimensions.ascent - strDimensions.descent) / 2; _drawString(gc, str, newX, newY); } int main() { Display* display = XOpenDisplay(NULL); if(!display) { cerr << "Unable to connect to X server." << endl; exit(1); } HelloWorld mainWindow(display); mainWindow.map(); XFlush(display); XEvent event; while(true) { XNextEvent(display, &event); switch(event.type) { case ButtonPress: mainWindow.handleMousePress((XButtonEvent)event.xbutton); case Expose: if(event.xexpose.count == 0) { mainWindow.draw(); } break; case KeyPress: mainWindow.handleKeyPress((XKeyEvent)event.xkey); default: break; } } XCloseDisplay(display); return 0; }