C
Qt Quick Ultralite chess Example
Demonstrates how to use a C++ singleton object in Qt Quick Ultralite.
Overview
This is a simple example demonstrating how to integrate a C++ object with Qt Quick Ultralite. It implements a simplified game of chess that does not conform to all the rules on purpose. Two players can make moves and capture opposite player's pieces. The game indicates player's turn on the top-right corner and time elapsed since the last move on the bottom-right.
The game logic is implemented in the C++ singleton object to expose a few properties and methods from the singleton to the QML code. The QML code connects mouse events with the singleton to shows current status of a board.
Project structure
CMake project file
Before using properties and methods of the C++ singleton object in the QML files, tell the qmlinterfacegenerator
tool to expose them to QML. This is done using the InterfaceFiles.files property in QmlProject, which runs the tool on the header file.
... InterfaceFiles { files: ["chessmodel.h"] } ...
Singleton object
The ChessModel
class is responsible for managing the chessboard. It inherits from Qul::Singleton to expose the public methods, signals, and properties 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) ... void release(int piece, int col, int row) ...
Application UI
The chess.qml
file defines the chessboard and the chess pieces. It uses two Repeaters
to construct a chessboard using Rectangle items and place the pieces on top:
Reapeator responsible for chessboard
... // The chess board: simply 64 squares of different colors Repeater { model: 64 Rectangle { property int row: Math.floor(index/8); property int col: index % 8; z: 0; x: col * squareSize y: (7 - row) * squareSize height: squareSize width: squareSize color: { var even = ((row + col) % 2) == 0; if (!ChessModel.canDrop(col, row)) return even ? "#d18b47" : "#ffce9e"; if (row == hoverRow && col == hoverCol) return even ? "#d18bff" : "#ffceff"; return even ? "#ff8b47" : "#ffaa88"; } } } ...
Color of a field is bound to the ChessModel
using the canDrop()
method. It highlights the possible moves of a particular piece. Whenever value returned by canDrop()
for a piece changes, color's binding is reevaluated.
Reapeator responsible for the chess peices
It uses ChessModel
to get the position of each piece. It also informs model of any user actions, by calling the setActivePiece()
and release()
methods.
... // The chess pieces: There are 32 chess pieces, the first 16 are white, and the // last 16 are black. Repeater { model: 32 Item { id: pieceText; visible: ChessModel.col(modelData) >= 0; x: squareSize * ChessModel.col(modelData); y: squareSize * (7 - ChessModel.row(modelData)); // Note: with QUL, the item in a repeater might not get the same order relative to // the item, so we use the z order to ensure that pieces are on top z: 1 height: squareSize width: squareSize Text { color: index < 16 ? "#eee" : "#444" text: { var p = index % 16; switch (p) { case 0: return "♚"; case 1: return "♛"; case 2: case 3: return "♜"; case 4: case 5: return "♝"; case 6: case 7: return "♞"; } return "♟"; } x: (pieceTouch.pressed ? pieceTouch.mouseX - pieceTouch.pressedX : 0); y: (pieceTouch.pressed ? pieceTouch.mouseY - pieceTouch.pressedY : 0); height: squareSize width: squareSize horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } MouseArea { id: pieceTouch; anchors.fill: pieceText; property real pressedX: 0 property real pressedY: 0 onPressed: { pressedX = mouse.x pressedY = mouse.y } onPressedChanged: { if (pressed) { ChessModel.setActivePiece(modelData); } else { ChessModel.release(modelData, hoverCol, hoverRow); ChessModel.setActivePiece(-1); } } onMouseXChanged: hoverCol = (pieceText.x + pieceTouch.mouseX - pieceTouch.pressedX + squareSize / 2) / squareSize; onMouseYChanged: hoverRow = 7 - (pieceText.y + pieceTouch.mouseY - pieceTouch.pressedY - squareSize / 3) / squareSize; } } } ...
It also binds the ChessModel
actions to the invalidMove
and validMove
signals:
... ChessModel.onInvalidMove: invalidLabel.visible = true ChessModel.onValidMove: { invalidLabel.visible = false; console.log("valid move ", col, row); }
Files:
Available under certain Qt licenses.
Find out more.