C
Qt Quick Ultralite chess Example
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
#pragma once
#include <qul/property.h>
#include <qul/singleton.h>
#include <qul/timer.h>
#include <qul/signal.h>
/**
* This is a very simple example demonstrating how to integrate a C++ Object
* with Qt Quick Ultralite
*
* The example does not conform to the rules of chess, it has been simplified
* on purpose
*
* The idea is just to expose a few properties and functions from a Singleton.
* By inheriting from Qul::Singleton, we tell the qmlinterfacegenerator tool
* to expose the type to QML. (the qul_target_generate_interfaces macro in CMakeLists.txt
* will make sure that the tool is ran on this file)
*/
struct ChessModel : Qul::Singleton<ChessModel>
{
/// This property is public and will be exposed to QML
Qul::Property<bool> whiteTurn;
Qul::Property<int> secondsSinceMove;
Qul::Signal<void(int col, int row)> validMove;
Qul::Signal<void()> invalidMove;
/// Return the current column position of a specific piece
/// (or -1 if it is not in the chess board).
/// This function is public and can be called from the QML
int col(int piece) { return position[piece].value().first; }
/// Return the current row position of a specific piece
/// (or -1 if it is not in the chess board).
/// This function is public and can be called from the QML
int row(int piece) { return position[piece].value().second; }
/// Return true if it is allowed to drop the piece on thie case
/// This function is public and can be called from the QML
bool canDrop(int col, int row)
{
if (col < 0 || col > 7 || row < 0 || row > 7)
return false;
return squares[col][row].value().canDrop;
}
/// Set the current active piece
/// (if piece is negative, there shall not be any active piece)
/// This function is public and can be called from the QML
void setActivePiece(int piece)
{
if (piece < 0) {
for (int r = 0; r < 8; ++r)
for (int c = 0; c < 8; ++c) {
SquareInfo g = squares[c][r].value();
if (g.canDrop) {
g.canDrop = false;
squares[c][r].setValue(g);
}
}
return;
}
if (whiteTurn.value() ? piece >= 16 : piece < 16)
return; // piece of the wrong color
Coordinates currentPos = position[piece].value();
squares[currentPos.first][currentPos.second].setValue(SquareInfo(piece, true));
switch (piece % 16) {
case 0:
// King
setMove(piece, currentPos.first + 1, currentPos.second);
setMove(piece, currentPos.first - 1, currentPos.second);
setMove(piece, currentPos.first, currentPos.second + 1);
setMove(piece, currentPos.first, currentPos.second - 1);
setMove(piece, currentPos.first + 1, currentPos.second + 1);
setMove(piece, currentPos.first - 1, currentPos.second - 1);
setMove(piece, currentPos.first + 1, currentPos.second - 1);
setMove(piece, currentPos.first - 1, currentPos.second + 1);
break;
case 1:
// Queen
for (int i = 1; setMove(piece, currentPos.first + i, currentPos.second); ++i)
;
for (int i = 1; setMove(piece, currentPos.first - i, currentPos.second); ++i)
;
for (int i = 1; setMove(piece, currentPos.first, currentPos.second + i); ++i)
;
for (int i = 1; setMove(piece, currentPos.first, currentPos.second - i); ++i)
;
for (int i = 1; setMove(piece, currentPos.first + i, currentPos.second + i); ++i)
;
for (int i = 1; setMove(piece, currentPos.first - i, currentPos.second + i); ++i)
;
for (int i = 1; setMove(piece, currentPos.first + i, currentPos.second - i); ++i)
;
for (int i = 1; setMove(piece, currentPos.first - i, currentPos.second - i); ++i)
;
break;
case 2:
case 3:
// Rock
for (int i = 1; setMove(piece, currentPos.first + i, currentPos.second); ++i)
;
for (int i = 1; setMove(piece, currentPos.first - i, currentPos.second); ++i)
;
for (int i = 1; setMove(piece, currentPos.first, currentPos.second + i); ++i)
;
for (int i = 1; setMove(piece, currentPos.first, currentPos.second - i); ++i)
;
break;
case 4:
case 5:
// Bishop
for (int i = 1; setMove(piece, currentPos.first + i, currentPos.second + i); ++i)
;
for (int i = 1; setMove(piece, currentPos.first - i, currentPos.second + i); ++i)
;
for (int i = 1; setMove(piece, currentPos.first + i, currentPos.second - i); ++i)
;
for (int i = 1; setMove(piece, currentPos.first - i, currentPos.second - i); ++i)
;
break;
case 6:
case 7:
// Knight
setMove(piece, currentPos.first + 1, currentPos.second + 2);
setMove(piece, currentPos.first + 1, currentPos.second - 2);
setMove(piece, currentPos.first - 1, currentPos.second + 2);
setMove(piece, currentPos.first - 1, currentPos.second - 2);
setMove(piece, currentPos.first + 2, currentPos.second + 1);
setMove(piece, currentPos.first + 2, currentPos.second - 1);
setMove(piece, currentPos.first - 2, currentPos.second + 1);
setMove(piece, currentPos.first - 2, currentPos.second - 1);
break;
default: // Pawn
if (whiteTurn.value()) {
if (setMove(piece, currentPos.first, currentPos.second + 1, true) && currentPos.second == 1)
setMove(piece, currentPos.first, currentPos.second + 2, true);
pawnTake(currentPos.first + 1, currentPos.second + 1);
pawnTake(currentPos.first - 1, currentPos.second + 1);
} else {
if (setMove(piece, currentPos.first, currentPos.second - 1, true) && currentPos.second == 6)
setMove(piece, currentPos.first, currentPos.second - 2, true);
pawnTake(currentPos.first + 1, currentPos.second - 1);
pawnTake(currentPos.first - 1, currentPos.second - 1);
}
}
}
/// Release the given piece in the given position.
void release(int piece, int col, int row)
{
if (!canDrop(col, row)) {
invalidMove();
return;
}
Coordinates pos = position[piece].value();
if (pos.first == col && pos.second == row)
return;
// remove the piece from its old position
squares[pos.first][pos.second].setValue(SquareInfo());
// set the new position of the piece
position[piece].setValue(std::make_pair(col, row));
// Take the old piece out of the board
int old = squares[col][row].value().piece;
if (old >= 0) {
position[old].setValue(std::make_pair(-1, -1));
}
// set the piece in its new square
squares[col][row].setValue(SquareInfo(piece));
whiteTurn.setValue(!whiteTurn.value());
secondsSinceMove.setValue(0);
moveTimer.start();
validMove(col, row);
}
ChessModel()
: whiteTurn(true)
{
// Set the chessboard to the initial position
for (int c = 0; c <= 1; ++c) {
int r = c == 0 ? 0 : 7;
int o = c == 0 ? 0 : 16;
position[o + 0].setValue(std::make_pair(4, r));
position[o + 1].setValue(std::make_pair(3, r));
position[o + 2].setValue(std::make_pair(0, r));
position[o + 3].setValue(std::make_pair(7, r));
position[o + 4].setValue(std::make_pair(2, r));
position[o + 5].setValue(std::make_pair(5, r));
position[o + 6].setValue(std::make_pair(1, r));
position[o + 7].setValue(std::make_pair(6, r));
r = c == 0 ? 1 : 6;
o += 8;
for (int p = 0; p < 8; ++p) {
position[o + p].setValue(std::make_pair(p, r));
}
}
for (int i = 0; i < 16 * 2; ++i) {
const Coordinates &pp = position[i].value();
squares[pp.first][pp.second].setValue(SquareInfo(i));
}
secondsSinceMove.setValue(-1);
// Set a timer to fire every seconds to update the secondsSinceMove value
IncrementMoveTime incrementer;
incrementer.model = this;
moveTimer.onTimeout(incrementer);
moveTimer.setInterval(1000);
}
private:
// These private properties are not directly accessible from QML.
// But by calling value() or setValue() on them, we still create
// bindings to them.
struct SquareInfo
{
explicit SquareInfo(int piece_ = -1, bool canDrop_ = false)
: piece(piece_)
, canDrop(canDrop_)
{}
int piece;
bool canDrop;
bool operator==(const SquareInfo &other) const { return piece == other.piece && canDrop == other.canDrop; }
};
Qul::Property<SquareInfo> squares[8][8];
typedef std::pair<int, int> Coordinates;
Qul::Property<Coordinates> position[32];
// Example of a timer and its slot (initialized in the constructor)
Qul::Timer moveTimer;
struct IncrementMoveTime
{
ChessModel *model;
void operator()() { model->secondsSinceMove.setValue(model->secondsSinceMove.value() + 1); }
};
/// Helper function to set that the piece can be dropped on the given square
// returns true if the square was empty
bool setMove(int, int col, int row, bool pawn = false)
{
if (col < 0 || col > 7 || row < 0 || row > 7)
return false;
int old = squares[col][row].value().piece;
if (old < 0) {
squares[col][row].setValue(SquareInfo(old, true));
return true;
}
if (pawn) // pawn can't take like normal piece
return false;
// One can only take a piece of the opposite color
if (whiteTurn.value() ? old >= 16 : old < 16) {
squares[col][row].setValue(SquareInfo(old, true));
}
return false;
};
/// Helper function to set that a pawn can be dropped in the given location if
/// it takes another piece
void pawnTake(int col, int row)
{
if (col < 0 || col > 7 || row < 0 || row > 7)
return;
int old = squares[col][row].value().piece;
if (old < 0)
return;
if (whiteTurn.value() ? old >= 16 : old < 16) {
squares[col][row].setValue(SquareInfo(old, true));
}
};
};