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.
434 lines
7.0 KiB
C++
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"
|