// Author: Denis Kozadaev - (c) 2025 #include #include #include #include #include #include #include "gameboard.h" #define IS_SET(flags, flag) (((flags) & (flag)) == (flag)) typedef enum { CFG_TIPS = 0x01, CFG_KNIGHT = 0x02, CFG_RANDOM = 0x04 } ConfigBits; GameBoard::GameBoard(TQWidget *parent, const char *name) :TQWidget(parent, name), paperColor(0xF0, 0xF0, 0xED) { #include "knight_icon.inc" struct timeval tv; knight = new TQImage(); knight->loadFromData(knight_icon, sizeof(knight_icon)); map = nullptr; xpm = kxpm = nullptr; map_size = 0; config = CFG_RANDOM; gettimeofday(&tv, NULL); srand(tv.tv_sec * 1000000 + tv.tv_usec); } GameBoard::~GameBoard() { if (map != nullptr) delete []map; if (xpm != nullptr) delete xpm; if (kxpm != nullptr) delete kxpm; delete knight; } void GameBoard::resizeEvent(TQResizeEvent *e) { TQWidget::resizeEvent(e); if (xpm == nullptr) { xpm = new TQPixmap(); xpm->resize(e->size()); redrawMap(); } } /* * Initializes new game map to the given size * (e.g. size X size) */ void GameBoard::initMap(int size) { size_t mem; if (map != nullptr) delete []map; if ((size != map_size) && (kxpm != nullptr)) { delete kxpm; kxpm = nullptr; } map_size = size; mem = map_size * map_size; map = new int[mem]; memset(map, 0, mem * sizeof(*map)); kpos = -1; curr = 0; } /* * Draw the map into the pixmap */ void GameBoard::redrawMap() { TQPainter *p; int w, h, x, y, dx, dy, ox, oy, pos, i, size; TQFont fnt; if (xpm == nullptr) return; w = xpm->width(); h = xpm->height(); adjustDelta(w, h, dx, dy, ox, oy); p = new TQPainter(xpm); fnt = p->font(); fnt.setPointSize(dy * 4 / 11); fnt.setItalic(true); p->setFont(fnt); p->setBrush(TQBrush(paperColor)); p->setPen(TQt::black); h -= oy * 3; w -= ox * 3; if ((kpos >= 0) && (kxpm == nullptr)) kxpm = new TQPixmap(knight->scale(dx - 3, dy - 3)); for (pos = 0, y = oy; y < h; y += dy) { for (x = ox; x < w; x += dx) { p->drawRect(x, y, dx, dy); if (isEnabled() && (kpos >= 0) && (kpos == pos) && IS_SET(config, CFG_KNIGHT)) { /* draw the knight */ p->drawPixmap(x, y, *kxpm); } if (map[pos] > 0) { if (!isEnabled() && (kpos == pos)) p->setPen(TQt::red); p->drawText(x, y, dx, dy, TQt::AlignCenter, TQString::number(map[pos])); if (!isEnabled() && (kpos == pos)) p->setPen(TQt::black); } pos++; } } if (kpos < 0) { fnt.setPointSize(dy * 3 / 5); fnt.setBold(true); p->setFont(fnt); p->setPen(TQColor(0, 0xB3, 0)); p->drawText(0, 0, w, h, TQt::AlignCenter, i18n("Choose a cell")); } else { predictMoves(); fromLinear(kpos, x, y); if (vmove.size() == 0) { /* game over */ setEnabled(false); y = y * dy + 1; x = x * dx + 1; p->setBrush(TQBrush(TQt::red)); p->drawRect(x + ox, y + oy, dx - 2, dy - 2); p->setPen(TQt::black); p->drawText(x + ox, y + oy, dx, dy, TQt::AlignCenter, TQString::number(map[kpos])); } if (isEnabled()) { if (IS_SET(config, CFG_TIPS)) { ValidMoves::const_iterator it; p->setPen(TQPen(TQt::green, 3)); for (it = vmove.constBegin() ; it != vmove.constEnd(); ++it) { fromLinear(*it, x, y); y = y * dy + 1; x = x * dx + 1; p->drawRect(x + ox, y + oy, dx - 2, dy - 2); } } } else { fnt.setPointSize(dy * 3 / 5); fnt.setBold(true); p->setFont(fnt); p->setPen(TQColor(0, 0xB3, 0)); size = map_size * map_size; p->drawText(0, 0, w, h, TQt::AlignCenter, (map[kpos] < size)?i18n("Game over") :i18n("You passed\nthe tour")); } } delete p; } /* * Computes deltas by the given width and height of the widget */ void GameBoard::adjustDelta(int w, int h, int &dx, int &dy, int &ox, int &oy) { dx = w / map_size; dy = h / map_size; ox = (w - dx * map_size) / 2; oy = (h - dy * map_size) / 2; } /* * Fills the vector by valide moves from the current position */ void GameBoard::predictMoves() { int x, y; vmove.clear(); fromLinear(kpos, x, y); ifAppend(vmove, x - 1, y - 2); ifAppend(vmove, x + 1, y - 2); ifAppend(vmove, x - 2, y -1); ifAppend(vmove, x + 2, y - 1); ifAppend(vmove, x - 2, y + 1); ifAppend(vmove, x + 2, y + 1); ifAppend(vmove, x - 1, y + 2); ifAppend(vmove, x + 1, y + 2); } /* * Compute the given arguments are a valid knight's move on the map */ void GameBoard::ifAppend(ValidMoves &vm, int px, int py) { if ((px >= 0) && (px < map_size) && (py >= 0) && (py < map_size)) { int pos = toLinear(px, py); if (map[pos] == 0) vm.append(pos); } } /* * Converts position to the x and y coordinates on the map */ void GameBoard::fromLinear(int pos, int &x, int &y) { x = pos % map_size; y = pos / map_size; } /* * Generate a starting cell of the knight */ void GameBoard::generateCell() { int msize = map_size * map_size; kpos = rand() % msize; if (kpos >= msize) kpos -= 2; curr = 1; map[kpos] = curr; } /* * Callback to handle mouse button */ void GameBoard::mousePressEvent(TQMouseEvent *e) { int x = e->x(), y = e->y(), i; i = toMap(x, y); if ((kpos < 0) || (mayMove(i) != 0)) { curr++; kpos = i; map[kpos] = curr; redrawMap(); update(); } } void GameBoard::paintEvent(TQPaintEvent *e) { TQWidget::paintEvent(e); if (xpm != nullptr) { TQPainter *p; p = new TQPainter(this); p->drawPixmap(0, 0, *xpm); delete p; } } /* * Returns non-zero if the given cell is allowed to move the knight * and 0 otherwise */ int GameBoard::mayMove(int n) { int res = 0; for (ValidMoves::const_iterator it = vmove.constBegin(); it != vmove.constEnd(); ++it) if (n == *it) { res++; break; } return (res); } /* * Returns an index map by the given x and y from the widget */ int GameBoard::toMap(int x, int y) { int ret = -1; int w, h, dx, dy, ox, oy; w = xpm->width(); h = xpm->height(); adjustDelta(w, h, dx, dy, ox, oy); oy = (y - oy) / dy; if (oy >= map_size) oy = map_size - 1; ox = (x - ox) / dx; if (ox >= map_size) ox = map_size - 1; ret = oy * map_size + ox; return (ret); } /* * Returns linear position by the given coordinates on the map */ int GameBoard::toLinear(int x, int y) { return (y * map_size + x); } /* * Request a new game for the given board size */ void GameBoard::newGame(int size) { setEnabled(true); if (xpm != nullptr) { xpm->fill(paperColor); update(); } initMap(size); if (IS_SET(config, CFG_RANDOM)) generateCell(); redrawMap(); update(); } /* * Toggle highlight tips to move the knight */ void GameBoard::setTips(bool ena) { if (ena) config |= CFG_TIPS; else config &= ~CFG_TIPS; redrawMap(); update(); } /* * Toggle the knight visibility */ void GameBoard::setKnight(bool ena) { if (ena) config |= CFG_KNIGHT; else config &= ~CFG_KNIGHT; redrawMap(); update(); } /* * Toggle random starting cell of the knight */ void GameBoard::setRandomCell(bool ena) { if (ena) config |= CFG_RANDOM; else config &= ~CFG_RANDOM; if (curr == 0) { generateCell(); redrawMap(); update(); } } #include "gameboard.moc"