This post participates in the competition "Smart phones for smart posts" .Friday, as you know, is the end of the work week, the best time for fun and games. And the best games are those that are fascinating, in which you need to think a little, and in which you can play with friends. I decided to write one of these games.
This post is not another translation or free presentation of various QML Howto and Quick Start. Rather, it is a description of the pitfalls that can be encountered when writing a real application.
')

When Qt Quick / QML was only announced, the words were heard from Nokia that “in the future, not only the user interface will be written in Qt Quick, but all the logic of simple applications will be written in JavaScript, programmers will not need to write a single line of code on the pluses.” The statement was even more provocative than my headline, and I was immediately interested: I decided to try to write a simple game without a single line of code on the pluses.
To stir up interest, add that:
- I usually write the code just on the pros
- I know little JS
- I do not know how and hate to do interfaces
- I once tried to do the same game on honest Qt, but broke down, unable to cope with QGraphicsScene and other interesting classes
- the result of my work can not only be downloaded, but also played on the network
- All sources can be downloaded from me from bazaar or tarballball .
We learn about the rest under a cat.
Version one, or war with drag and drop
As a starting point, the game
Zaar was chosen - a board logic game for two players. I sincerely hope that my insidious implementation of this game will not cause indignation of the right holders, but nevertheless I recommend those who are interested to buy it - it is much more interesting in a live form than in my performance.
And we return to the development. First of all, for ten minutes I was contemplating the playing field (in the picture above): in all decent games, as you know, it is rectangular. After ten minutes I said: “Aha!”, I added a picture with nodes so that a rectangular grid was formed, after which I wrote a function to check if the node actually exists.
The second problem was the pictures. As I said, I hate making interfaces, and as I draw, it’s better not to think at all. To generate a picture in runtime seemed to me even more stupid. I sighed, opened inkscape and tried to carefully draw the playing field with straight lines. On the third line, I gave up, opened the resulting .svg-file in the Vime and, by simple calculations and manipulations with macros, straightened the available lines and drew the missing ones. Similarly, six pictures with images of chips were created. That is why they are so crooked and unlike the original.
I describe my existential throwings in such detail and give so little code to add right now: these are the biggest problems I encountered in the process. It really is. Position the field on the form, generate and arrange the chips, make homemade buttons and display the status - no problem! Animate moving chips - 2 lines! Allowing and prohibiting the player to move various chips depending on the phase of the move - all this is done in QML so elementary that I can only suggest reading the official manual with examples. By the end of the work, the
js-file with all the main logic, including empty lines and comments, occupied as many as 173 lines or 6 functions.
Oh no, I guess I remembered one moment that astounded me and forced me to write a crutch. This moment is called:
drag'n'drop . Yes, it sounds weird, but a drag-and-drop in a little less than a fully graphic tulkite is bad. “Dragging” can be, it turns out, not an element, but the MouseArea, which lies on it. The only thing we can do is determine which buttons can be clicked on, and what restrictions we have on coordinates. It is impossible, as when working with the system, to handle the event “they threw something at me that it is”, you cannot allow the element to be thrown only at certain objects. You can only handle the events of pressed and released. Spin further as you want. And in the examples, if my memory serves me, they do such nonsense in general only with all sorts of Grid and List, no arbitrarily positioned elements to you. Apparently because of this, by the way, to say to the element “I didn’t like the way you were thrown, go back to the place”, is also impossible. I'm telling you, the developers only thought about parsing RSS.
Therefore I had to do the following. The property of an element, obviously, is not its coordinates on the screen -
x and
y - but the position on the board. Coordinates are calculated based on the position. At the events of pressed and released, we remember the initial position and calculate which new one we tried to throw an element into. After that we call the
function responsible for moving the element. If the function tells us that moving is not possible, we need to do with the element what? Right, return to the original position. Watch carefully for the hands:
if ( (oX == nX && oY == nY)
See this assignment minus one? Yeah. The fact is that in QML, if we assign a property the value that already lies in it (oX and oY), then the engine considers that the property has not changed and does not recalculate everything that is connected with it, in our case these are absolute coordinates on the screen. We have to assign some obviously different value, and only then the original one.
The implementation of drag-n-drop itself looks like
this :
MouseArea { ... acceptedButtons: Qt.LeftButton
And that's where all the problems really ended. It was possible to play the game, win for whites, win for blacks. But - on one computer. It was boring, I was eager to play online with friends. And here comes the second iteration of the development, much more interesting.
Version two, or the network strikes back.
Where at this moment I began to think about the third problem, but so far it has not yet reached, we will talk about the network.
As I discovered, QML has very poor and sad support for working with the network. We have Ajax requests from a javascript engine (and only asynchronous), and we have an active XmlListModel in our examples. I would not like to believe that all QML was created solely for easy parsing of RSS feeds.
Anyway, looking ahead, I will say that the most vivid illustration of the poverty of working with the network in QML is the following line:
Component.onDestruction: disconnect();
In short, when I close the game, I would like to send a message to the server that I have disconnected and the session can be killed. The problem is that when a signal arrives, I create
an asynchronous ajax request, “send” it, and then ... and then our event loop successfully continues and runs the program regularly - the signal was caused by a cross in the corner window. Voila! The request
never really has time to reach the server. Never. But I try, I believe in the best.
In the meantime, this is all in the future, now I’m still looking at two ways to communicate with the network and, of course, discard the XmlListModel. If the installation was not to use only QML, of course, it would be possible to simply open the socket, but I decided to squeeze everything you need from javascript.
On the one hand, without this, the application probably would not have become as interesting as it is now. On the other - I had a bunch of problems. About this and tell.
When I decided to use XMLHttpRequest, I immediately realized that you can forget about direct connections between clients, I need a server. To write the server itself, willy-nilly, I had to forget about QML / JS, but it was for the best - the server can hardly be called an “uncomplicated” application. I wrote it on the rock, and the only reason for such a ridiculous act was the fact that it was the Ocsigen server that I trust in the world at the 80th port. The logic of the server is quite simple and is described by the following facts:
- the server accepts requests from anonymous users, generates session id for them and either selects a partner from those who are waiting (ideally, there can be no more than one), or puts a partner in a queue
- when the game starts, it is the server that now generates the initial placement of chips and sends it to both players.
- the player sends a message about his move the chips, and it is sent to his partner
- once a second each player requests information about fresh actions from the server
- if the player has not received any requests within 3 seconds, he is considered to be missing and the game breaks off
- an error is sent to the player for any possible error (incorrect data, missing opponent, etc.), after which the client breaks the connection and displays an ugly message “Some error occured”
- The client sends all the data in the GET-fields (query string), and the answer comes in binary form
While those who wish to watch
the server
code , I will draw your attention to the last point. In order not to be entertained on the server with JSON generation, and in order not to drive customers with bad Internet into melancholy, I sent all the data through a completely idiotic protocol invented by me. It was my big mistake, try not to make it. The first 16 bytes of the response always contained the session id (ordinary ascii characters), and then the code of each byte was the corresponding value. That is, the characters \ 0, \ 1 and the like were regularly encountered in the response. If I knew javascript better, I would guess not to do that, but I’ve done something stupid. As it turned out, javascript does not know how to work with strings in strings, only with characters. Non-printing characters he digested without thinking. Zero byte swallowed, not even choking. The trouble began when sending the generated board - at some point the cell index exceeded 0x80 and smart javascript considered this byte a piece of the next character.
I rummaged through the Internet, I discovered the instruction - forcibly changing the response encoding to x-custom-charset - I discovered that the js-engine in Qt does not allow this, and changed the encoding directly on the server. After that, I downloaded the ready-made library BinaryReader.js, which asserted that it was able to read the text in bytes and tried to read the result also to her. Everything was in vain - JS stubbornly gave me 0xff instead of my byte. Fortunately, I found that the indices of all cells allowed for chips are even. I began to divide them in half during the transfer, and this made it possible to read the data as it should be. As a result, the code has become one more
component for 170 lines, which interacted with the server and allowed to enjoy a full-fledged network game, and I approached the last problem - about it immediately after the advertisement.

Version three, or where to shove?
At this moment, finally, the thought that had long overwhelmed me, finally took shape. “Dude,” she said, “how would we compile the code?” The question was relevant - I ran all the code for the test via qmlviewer from qtcreator, but of course, this is not the way out. I wanted to make the application available to more mundane people - who don’t have qmlviewer. And it was here that I received a blow from my breath. It turned out that despite the availability of QML projects in qtcreator, it is impossible to compile QML. No You need to write a viewer in C ++, and load the main QML file from it. This is, generally speaking, a big trouble, in my opinion. I remember once, when Flash was still far from all machines, the developers from Macromedia did a very tricky and interesting thing: in Flash Player with an open swf file, you could press a button and “compile” a self-contained exe file that started on any machine. Developers from Nokia would not hurt to borrow this great idea, adjusted for different architectures and platforms.
Well, okay, I thought, in the end, all the code works without pluses, so be it, let the player be on the pros. I created another project in qtcreator, this time from the category “C ++ and QML”, and started to automatically see how it compiles using the automatically generated example. According to the results of the inspection revealed an interesting fact. In the example project, the QML files were added to the
/ qml / tzaar / directory . And in main.cpp there was a line:
viewer.setMainQmlFile(QLatin1String("qml/tzaar/main.qml"));
But in the .pro file there were interesting lines:
# Add more folders to ship with the application, here folder_01.source = qml/tzaar folder_01.target = qml DEPLOYMENTFOLDERS = folder_01
They meant neither more nor less, but the fact that the contents of
/ qml / tzaar / when installing the program is copied to
/ qml / . Catch? The example code was valid exactly until I wanted to install it somewhere, after installation the player would not have found the files anymore. Moreover, editing the .pro file did not help - any attempt to put the same values ​​in source and target led to the fact that qmake went crazy and said that I was trying to copy the file to myself. This alignment obviously did not suit me. I tried to put all the resources in QRC - if they are in the same file, they simply cannot get lost. It turned out that the trick here too - the class qmlapplicationviewer automatically applied to the new project spoiled all qrc: / links. I was already mentally ready to correct this mistake, and all resources, including the html-page with a description of the game, moved to qrc. Everything worked, but now every qtcreator launch tells me that my qmlapplicationviewer is different from it, and do I want to overwrite all changes?
Corrections for the standard qmlapplicationviewer to make it correct can be taken
here , but for now I’ll remind you of a few simple rules for those who want to follow my example:
- if the file was at qml / Tzaar.qml relative to the project root, then in qrc it should be looked up as qrc: /qml/Tzaar.qml
- if the QML files are all side by side, they can be loaded directly along a relative path, for example: “Piece.qml” - that is, you will not have to make changes
- if we want to load a file from QML that was in a different directory (say, field.js, which was in the root of the project, and not in the qml folder), then we just need to write "/field.js" - that is, add a slash at the beginning of the address, make the path absolute
By following this simple technique, you, of course, lose the notorious "flexibility" and the ability to replace QML without recompiling - but is it necessary for the game? But you will never suffer from the fact that in different distributions and systems shared data lie at different addresses. From alternative solutions, you can: rewrite the entire build system on CMake or get inside the .pri file from QmlApplicationViewer, which is even worse. For some reason it seems to me that generating a resource file and fixing several paths is a much simpler solution.
Total
Interfaces in QML are really designed very easily and quickly, even by interface-haters like me.
On QML, you can write a game or application that works with an online service. If you gently go around the hidden rake, then the simplicity and convenience will very pleasantly surprise you.
With a bit of desire, it is possible to make one pleasant binary with all the included resources from the entire code, which can be distributed
Work with the network is still lame. It seems to me that developers of games for mobile devices (especially in the light of NFC PR and the like) would be happy if they could properly establish a connection between devices without getting to the C ++ level
The qtcreator sparking example is broken. This is a terrible minus. When the helloworld attached to the IDE does not work correctly, it throws a terrible shadow over the entire library. In this case, this is hitting the direction of the QtCreator developers, and not the technology itself. However, keep in mind. Perhaps in versions later than my 2.2.1, this problem was fixed.
Judging by the sawed-off synchronous mode in XMLHttpRequest and the curve of work with byte-reading, I got the feeling that the JS engine in Qt is defective in places. Be careful.
Those who want to play this great game can read the rules (perhaps
their Russian version from Game Expert will be clearer), compile the game with the qmake && make command, and play - just remember that you need a partner who also connects to the server.
Users of the 32-bit Ubuntu 11.10 (maybe I
’m not promising any other systems) can easily download the archive:
sorokdva.net/tzaar/tzaar.tar.gz and run the already built binary. To work, you need the libqtwebkit-qmlwebkitplugin package.
And if it suddenly seemed to someone that I was strongly scolding QML, then I will remind you of two points. First: I wrote a game in QML in my spare time for a total of
40 hours maximum (actually less). The second: on traditional Qt and with his work with graphics, I
could not write the game. And this should already speak for itself. And I, in general, not that neosilator.
