I updated my Qt version to 6.8.1. Here is what you need to do.
Go to https://www.qt.io/
Make a Qt account if you don't have one.
Under Products look under More and click Licensing
You will see a box called Qt Community Edition
Click Learn More
You will come to Qt for Open Source Development
Scroll down and under Looking for Qt Binaries? click Download the Qt Online Installer.
You get to a page called Download Qt for open source use.
Click Linux x64 (or what platform you want).
The name of the online installer is qt-online-installer-linux-x64-4.8.1.run
You may need to set the permission of this file to Execute.
After this write in console: sudo ./qt-online-installer-linux-x64-4.8.1.run
You now get up a page where you need to log in to your Qt account.
Then click through the next pages. Set installation directory and choose Custom Installation (you want to avoid unnecessary installs
of designer tools etc).
...
You can also download the code itself. Instructions to download, configure and build are given here:
https://wiki.qt.io/Building_Qt_6_from_Git
I now use a build command like this:
g++ -Wall -o main main.cpp counter.cpp io.cpp settings.cpp -L. -l:library.a -I/home/me/luau/VM/include -I/home/me/luau/Compiler/include -I/home/me/Qt/6.8.1/gcc_64/include/QtWidgets -Wl,--copy-dt-needed-entries -L/home/me/Qt/6.8.1/gcc_64/lib -lQt6Core -lQt6Widgets -L/home/me/luau -lLuau.VM -lLuau.Compiler -lLuau.Ast -lisocline
All the classes I am not working with is saved in a static library called library.a.
Saving a game is more complicated that you would think. Closely related to saving a game is the concept of ownership of counters. Let us start with a few useful statements.
1. A saved game is independent. This means you can be offline opening a game. You need nothing else than the file itself to open a saved game in the engine. This may seems obvious but read on.
2. A saved game is a binary. Saving a game as a binary means that it can not easily be inspected or tampered with. I say easily, because it is always possible to inspect/tamper a binary unless it is encrypted, but this would collide with statement 1 (a saved game is independent). It is possible to obfuscate a binary pretty well but then the source code can not be open.
3. A saved game is not just the saved state of all paying pieces, but also the rights you set for your own paying pieces. Having own playing pieces is what ownership is about.
4. A saved game must always be able to be loaded by a later version of a module. If this is not the case I consider the design to be fundamentally flawed. There are ways to ensure that old saved games can always be loaded if you follow certain rules.
I will not go into security issues in my prototypes. Neither will I make network code, transfer protocols and server code. The whole online part is relatively independent of the engine itself.
Ownership of counters means you have certain rights over your counters. For now I define nine such rights (the list can be expanded/modified). In Edit->Settings there is now a new tab called Ownership. Each of the nine rights has a check box.
The restrictions your opponent has with counters owned by you:
Hidden counters are invisible.
Concealed counters can not be seen.
Concealed counters can not be revealed.
No right-click context menu.
Counters can not be deleted.
Counters can not be flipped.
Counters can be moved or rotated.
A stack can not be opened.
A stack can not be inspected (no hoover window).
Checking a box will activate the right and disallow your opponent doing that action.
Note: in general when you play a game there should be a level of trust between players. Therefore it is not necessarily a good idea to impose a lot of restrictions on what your opponent can do with your counters. Also, ‘hidden’ actions will not be possible because all actions are logged. A wrong actions can be undone.
These rights are saved when a game is saved. They must follow the game.
Ownership applies to counters (playing pieces). Counters are owned by a specific side. It is useful to think of ‘side’ as a trait. In traits.luau I defined o.Side for German counters to be:
German = {}
function German:new()
local o = Piece:new ()
setmetatable(self, {__index = Piece})
self.__index = self
o.Side = "German"
o.Overlays = Overlays:new ()
o.Overlays:add(Mask:new("German/Mask", 46, 44), (o.Side ~= PlayerSide))
o.Overlays:add(Label:new("7", "times", 28, "white", 45, 63), (o.Side ~= PlayerSide))
return o
end
The module developer defines certain counters to be German (i.e. to have o.Side set to “German”). In repository.luau:
Repository:addTraits("German/12R-51R", German:new())
Repository:addTraits("German/38-94", German:new())
Some counters are markers and have no side trait.
In VASSAL when you start a new game you get to choose sides. In this engine I will for the time being simulate this choice by setting a global variable in module.luau to a side (one of the possible values for o.Side: “French” or “German”):
--- set side
PlayerSide = "German"
The C++ engine must also have a knowledge of sides. In settings.cpp:
std::string Settings::playerSide = "German";
These two variables must have the same value, either “German” or “French”.
Ownership is related to side. Ownership is determined by an ownership field in each counter. This field is the product of two primes, the product of the ownership key and another (random) prime number. The ownership key is unique for a user and stored in a user profile. In io.h the ownership key is defined like this:
private:
unsigned long long int _ownershipKey; // holds a 32-bit prime number
The ownership field is defined in counter.h:
private:
unsigned long long int _ownershipField; // holds a 64-bit number
Note: In reality the ownershipKey should be a much larger prime, maybe 1024 bits. But then it’s definition and implementation will be very different as no fundamental data type in C++ can hold such a large number. Instead one must use so-called arbitrary-precision arithmetic software, like the GNU Multiple Precision Arithmetic Library (GMP). Code can be found on the net that can do such arbitrary-precision arithmetic.
Note: the ownershipKey will be stored in a profile associated with a given user and loaded into the engine at startup. For the time being I will just hard-code the key. Prime 32-bit numbers are easy to generated in Linux with openSSL (openssl prime -generate -bits 32 -hex
).
Why use the product of two primes to determine ownership? Because it is very hard to factorize two large primes. This means that the ownership key is a handy way of determining ownership. If your key divides the ownership field of a counter, you know that the counter is owned by you.
Note: using the product of two prime numbers is a well known crypto-technology. It comes from the difficulty of factoring large numbers. Therefore is difficult (impossible with large primes) to find the ownershipKey that divides the ownershipField. If you have the key it’s easy to determine ownership, if you don’t it’s basically impossible.
Note: In VASSAL there is a password that does something similar to this. The problem with a password is that each time a game is saved the password must also be stored in the saved file and can in theory be read by others. In this engine only the ownership field is saved, and given how hard it is to factorize the product of two large primes, the password (the ownership key) can not be read.
The ownershipKey generates the ownershipField. The ownershipField is what gives the player ownership over counters belonging to their side. Side itself need not by saved because side is determined by looking at the Side trait of a counter that has an ownershipField that can be divided by the ownership key. In the code it looks like this (io.cpp):
std::string IO::findSide()
{
std::string side = "";
for (auto obj = Counter::counters.begin(); obj != Counter::counters.end(); ++obj)
{
Counter *counter = obj->second;
Counter::Table table = obj->second->table;
if (table.find("Side") != table.end())
{
unsigned long long field = counter->getOwnershipField();
if (field != 0)
if (field % getKey() == 0)
{
side = std::get<std::string>(table["Side"]);
return side;
}
}
}
return side;
}
Currently, all check boxes in the settings table for rights (the restriction table) are disabled except the ‘No right-click context menu’. I have left this check box open so that it’s possible to test how restriction works. Do this: have the box checked and make sure PlayerSide = "German"
in module.luau, std::string Settings::playerSide = "German"
in settings.cpp and _ownershipKey = 0xc2158b49;
in io.cpp. Drag a German counter onto the map. An ownershipField based on your ownershipKey is now computed and stored with the counter. Save the game and close the game. To simulate playing the opposite side go into io.cpp and comment out/ in like this:
// NOTE!! _ownershipKey must be loaded from a user profile, not set like this
//_ownershipKey = 0xc2158b49; // 32-bit prime
// openssl prime -generate -bits 32 -hex
_ownershipKey = 0xddb49da1;
This will simulate that you are now the opposite side. Recompile, start and load the game you saved. When you now right-click the German counter nothing happens! The file you loaded was made with a different ownershipKey. The German counter is now not your own and can not be right-clicked !
Ownership in implicit. Ownership of new counters is determined automatically. This is done for ease of play. But note that if you are German and drag a French counter to the board it will not get an ownershipField. The German player has no access to the French ownershipKey. Keys are never public. They are never put online.
A counter with no ownership field (field set to 0) will get an ownership field next time it is saved by a player having a side equal to the counter’s side. In io.cpp:
// case where opponent has dragged your counter on board
// set your getOwnershipField and rights
if (f == 0)
if (table.find("Side") != table.end())
if (std::get<std::string>(table["Side"]) == Settings::playerSide)
{
counter->setOwnershipField(IO::getKey() * 0xef06eea1);
f = counter->getOwnershipField();
r = Settings::myOwnershipRights;
}
fs.write(reinterpret_cast<const char*>(&f), sizeof f);
Saving-loading games itself is about reading the Counter[id]
state and saving it a format that later can be read and loaded back into a new Counter[id]
state. I will not go into the technical details of this. It is important to make sure that non-saveable fields like function pointers get a new value based on the new run time.
The total code is in my opinion still small.
Interested can study the difference between prototype5 and prototype6 on my GitHub.
Next up are logfiles.