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 qul_target_generate_interfaces CMake macro, which runs the tool on the header file.

...
qul_target_generate_interfaces(chess 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.