You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdeknighttour/src/gameboard.cpp

434 lines
7.0 KiB
C++

// Author: Denis Kozadaev - (c) 2025
#include <sys/time.h>
#include <cstdlib>
#include <tdeapplication.h>
#include <tdelocale.h>
#include <tqpainter.h>
#include <tqdatetime.h>
#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"