Bundle of updates

I was planning on following the new format for the release announcements, but since almost every single app has the same changes, it would be quite repetitive. I updated basically every single program to have better installers on Windows and the Mac, and fixed the compile process to not have extra warnings during release builds.

The important thing is that I updated to Qt 5.11, and this means that FocusWriter should be able to save to Dropbox! I say should because the bug report is closed as fixed, but I don’t use Dropbox so I can’t test for myself. Enjoy!

More releases!

I just realized I forgot to announce the releases I made at the beginning of the month! Oops. This poor, neglected blog.

I updated all of my projects, and for the most part it was a very minor release that fixed an installation bug in Linux or updated the translations. Of course, FocusWriter had a few more fixes than the rest, but that is to be expected as it is a much more complicated program. And Tanglet actually had a feature release, thanks to Markus Enzenberger. If you have not yet updated, enjoy!

Releases galore!

Over the past week and a half, I have made releases for all of my projects. Most of them were pretty minor, and just amounted to updating the translations (and fixing an issue where the Qt-supplied translations were not being properly loaded). Packagers will now need to depend on lrelease, because I no longer include the precompiled binary .qm files.

The projects with actual feature releases were CuteMaze, Hexalate, Tanglet, and Tetzle. For the most part, the features added will not be obvious unless you have a 4K monitor, because the biggest thing I added was support for high-DPI displays. I did also finish moving my projects to be Qt 5 only, and to use C++11.

As usual, report any issues you have. Enjoy!

Development version numbers

Now that I am switching to release branches I am finally going to also tackle something that has always bothered me but I’ve never taken the time to solve: the development source code has the same version number as the most recent release. I have always wanted it to be some sort of automated number, but I wasn’t sure what I wanted it to be. And I didn’t want to have to update it myself with every single commit.

At first I had wondered about a number that gets incremented every time you compile the project, but I quickly realized that was a pointless thing to track. I may build a project 5,000 times and have a huge build number, but someone else might download the source and compile it only twice. Same source code, different version number. That’s frankly pretty silly.

Instead, I got inspired by the idea of using the git revision ID. It is obviously unique for each commit, and it identifies the specific source code for everyone. Of course, you can’t embed the revision ID because it is a hash that is created of the source code that you’re trying to embed it in. A hash that contains itself? Impossible! Of course, all you have to do instead is simply ask git for the revision ID, and pass that as a definition to the compiler:

VERSION = $$system(git rev-parse --short HEAD)
DEFINES += VERSIONSTR=\\\"git.$${VERSION}\\\"

The source code also needs to make use of the new compiler definition:

QCoreApplication::setApplicationVersion(VERSIONSTR);

This means that I finally have an automated version number for the development source code. I’ve only updated Kapow so far, but I am going to make this change to all of my projects.

The end of PowerPC support

I should have announced this sooner, but better late than never I suppose. I will no longer be creating new PowerPC builds of my programs. There are many reasons, but the biggest two are that my iBook G3 finally gave up the ghost, and that Qt has dropped support for PowerPC. I know that this is an inconvenience for some of my users, and I am sorry about that. Still, I hung in there as long as I could, but Apple has moved on.

But where to put you?

I’ve been writing programs with Qt since the Qt 3 era, and when I made my first Qt 4 programs available for general use I added cross platform support. One thing that entails is storing user data files in different locations on different platforms. At the time, I couldn’t find any way in Qt to do that, so I ended up writing this in CuteMaze:

QString homeDataPath()
{
#if defined(Q_OS_MAC)
    QString path = QDir::homePath() +
        "/Library/Application Support/GottCode/CuteMaze";
#elif defined(Q_OS_UNIX)
    QString path = qgetenv("XDG_DATA_HOME");
    if (path.isEmpty()) {
        path = QDir::homePath() + "/.local/share";
    }
    path += "/games/cutemaze";
#elif defined(Q_OS_WIN32)
    QString path = QDir::homePath() +
        "/Application Data/GottCode/CuteMaze";
#endif
    return path;
}

I’ve never been happy with that code. For one thing, it uses platform specific code in a platform independent codebase, which limits the number of supported platforms to those I can easily test myself. Thankfully, I will notice any bugs I introduce in one platform but not the rest, since I build my programs on those platforms.

However, I was recently made aware of the fact that CuteMaze would not compile on less common platforms, and that piece of code was one of the reasons. I figured that this was a chance to improve said code, so I went looking to see if there is a better way to write… and there is! After I released CuteMaze, a new class for desktop integration was added to Qt. The above code would be better written like this:

QString homeDataPath()
{
    return QDesktopServices::storageLocation( QDesktopServices::DataLocation ) + "/CuteMaze";
}

The above functionality was added in Qt 4.4. Instead of using it, I have been dragging the messy and inferior code from project to project. Obviously I don’t re-solve every problem I come to, I remember what I have done or what I have read about. It’s only natural I will miss things when reading about new versions of Qt.

Now that I know about this, I am going to use the new code for the platforms not already supported. In future projects I will use only the new code, but I don’t want to go through the headache of moving all of the user data files for all of my current projects.

A new CuteMaze release!

It has been over a year since I last updated CuteMaze. I have worked on it since then, but my other projects distracted me so much I didn’t think about it. A few months ago I added zooming support, but moving was pretty slow when zoomed out. I finally got around to looking at it a few days ago, and I discovered that I could noticeably speed up rendering by caching the background. I don’t know why I didn’t do that from the start, actually.

Once I had done that, I decided that it was time to polish up CuteMaze and make a release. I don’t want the new features to sit for too long without people getting to use them, and a year is pretty long! Along with zooming, I also added support for hints. Other than that it is mostly code cleanup. I had intended to rewrite the maze generation algorithms, but I lost interest in that and I have pushed that off to a future release.

For anybody who has made themes, this new release changes the format from being a collection of SVG files to being a single SVG file. You need to put a transparent rectangle behind smaller elements to make them render at the appropriate sizes. It is a fairly easy change to make, and you can look at the provided themes for examples.

Hint support in CuteMaze

A week ago I wrote a simple maze solver for CuteMaze based on the principle of “dead end filling”, for the purpose of giving the player hints. Unfortunately, I had a pretty busy week and I was delayed in adding it. Some things were avoidable, some things were not, but suffice it to say that I’m glad that it’s done.

The cool thing about dead end filling is that it can solve any of CuteMaze’s mazes very quickly. It is actually a remarkably easy way to solve a maze. You start by iterating over every cell in the maze. Each time you find a dead end, you “fill” the maze from that point until you find a junction. If you treat the start and stop cells as junctions, you will end up with a maze entirely filled except for the path between the start and the stop cells. Once you are done filling in all of the dead ends, you track a fill from the start to the stop to find the cells for the solution.

I guess I’m trying to see just how many projects I can work on at the exact same time. I’m trying to recreate a reported issue with Kapow on the Mac. I’m still playing around with the interface of Simsu. I’m finishing up Peg-E so that it can be reviewed by the KDE developers. Plus, to top it off, I’m also working on adding themes and goals to FocusWriter. I should probably slow down and focus on one project at a time so that they get done faster, but that’s not as much fun!

Dusting off CuteMaze

Sometimes I look at old code and say, “What in the world was I thinking?”. I had such an incident today. For an unrelated reason I found myself looking at the source code of CuteMaze. One piece of code in particular stood out: the placement of targets on the maze. This is the code in the current version:

// Add player
m_player.setX(rand() % (columns - 1));
m_player.setY(rand() % (rows - 1));
m_start = m_player;

// Add targets
QList locations;
if (columns * rows > m_total_targets * 2) {
    locations.append(m_start);
    QPoint target;
    for (int i = 0; i < m_total_targets; ++i) {
        do {
            target.setX(rand() % (columns - 1));
            target.setY(rand() % (rows - 1));
        } while (locations.contains(target));
        locations.append(target);
        m_targets.append(target);
    }
// Handle if targets cover half or more of the maze
} else {
    for (int c = 0; c < columns; ++c) {
        for (int r = 0; r < rows; ++r) {
            locations.append(QPoint(c, r));
        }
    }
    locations.removeAll(m_player);
    int pos;
    for (int i = 0; i < m_total_targets; ++i) {
        pos = rand() % locations.size();
        m_targets.append(locations.takeAt(pos));
        for (int y = 0; y < size; ++y) {
            for (int x = 0; x < size; ++x) {
                locations.append(QPoint(x,y));
        }
    }
}

Now that is some pretty strange code. And its major flaw is that as the number of targets increases it gets a lot slower, which apparently I tried to paper over. I randomly selected a location and tried to place a target, unless the number of targets was over half of the maze, in which case I selected random locations from a list. Riiight. Instead, here’s the code that will be in the next version:

QList locations;
for (int y = 0; y < size; ++y) {
    for (int x = 0; x < size; ++x) {
        locations.append(QPoint(x,y));
    }
}
std::random_shuffle(locations.begin(), locations.end());
m_player = m_start = locations.first();
m_targets = locations.mid(1, m_total_targets);

Now I make a list of all locations in the maze, shuffle it, and use the first location for the player and the next group of locations for the targets. Simple, fast, and scales all of the way up to having targets in every single cell of a 10×10 maze (Whee! It’s Pac-Man! :-P).

Of course, changing the target placement code means that old save games won’t work. I seem to be on a roll of breaking compatibility lately. If I’m going to do that, I might as well see if any of the rest of the code makes me say, “What in the world?”.

No install for you!

While testing things yesterday, I discovered that none of my projects install the executables when compiled from source. Oops. I don’t usually try to install them, so I’m not surprised I missed it. I guess nobody else does either, otherwise I expect I would’ve heard about this already.

I am going to be making releases of Gottet, NovProg2, and Tetzle today. I am also planning on making a release of CuteMaze, but that is being delayed while I work out the last few details of porting it to Qtopia4 — thanks go to Alessandro Briosi for porting CuteMaze and Gottet to Qtopia4.