このページでは

C

Qt Quick ウルトラライトチェスの例

Qt Quick UltraliteでC++シングルトン・オブジェクトを使う方法を示す。

概要

これはC++オブジェクトをQt Quick Ultraliteに統合する方法を示す簡単な例です。これは、意図的にすべてのルールに準拠していない簡略化されたチェスゲームを実装しています。2人のプレーヤーが手を打ち、反対側のプレーヤーの駒を捕獲することができます。ゲームは、右上にプレイヤーの手番、右下に最後の手からの経過時間を表示する。

ゲームロジックはC++のシングルトンオブジェクトに実装され、いくつかのプロパティとメソッドがシングルトンからQMLコードに公開されます。QMLコードは、マウスイベントをシングルトンに接続し、碁盤の現在の状態を表示する。

プロジェクトの構成

CMakeプロジェクトファイル

C++シングルトン・オブジェクトのプロパティやメソッドをQMLファイルで使う前に、qmlinterfacegenerator 。これはQmlProjectのInterfaceFiles.filesプロパティを使い、ヘッダーファイル上でツールを実行します。

...
        InterfaceFiles {
                files: ["chessmodel.h"]
        }
...
シングルトンオブジェクト

ChessModel クラスはチェスボードの管理を担当します。Qul::Singleton を継承し、publicメソッド、シグナル、プロパティを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)
    ...
アプリケーションUI

chess.qml ファイルはチェス盤とチェスの駒を定義します。2つのRepeaters を使って、Rectangle アイテムを使ってチェス盤を作り、駒を配置します:

チェスボードを担当するReapeator
    ...
    // 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";
            }
        }
    }
    ...

フィールドの色は、canDrop() メソッドを使ってChessModel にバインドされる。これは、特定の駒の可能な動きをハイライトする。ある駒についてcanDrop() が返す値が変わるたびに、色のバインディングが再評価される。

チェスの駒を担当するReapeator

ChessModel を使って各駒の位置を取得します。また、setActivePiece()release() メソッドを呼び出して、ユーザーのアクションをモデルに知らせます。

    ...
    // 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;
            }
        }
    }
    ...

また、ChessModel のアクションをinvalidMovevalidMove のシグナルにバインドします:

    ...
    ChessModel.onInvalidMove: invalidLabel.visible = true
    ChessModel.onValidMove: {
        invalidLabel.visible = false;
        console.log("valid move ", col, row);
    }

ファイル


詳細はこちらをご覧ください。