Andrzej unjello Lichnerowicz

Solving AoC in Watcom C/C++

2025-01-12T23:54:15+02:00

Prerequisites

  1. Watcom C/C++ 11.0 Installer

Installation

This time, we have a CD, folks – 650MiB of data. Some serious business indeed. And only this, as I never really found any proper manual for WATCOM. Only Programmers Guide that skims over basic compilation syntax for all the combinations of 16/32 bit and different pmode extenders.

Screenshot of DosBox-X with Watcom C/C++ 11.0 installation in progress.

Watcom C/C++ 11 installation

Screenshot by author.

The installer modifies CONFIG.SYS and AUTOEXEC.BAT. The former doesn’t really affect us – CONFIG.SYS is managed by DosBox anyway. As for AUTOEXEC.BAT, we’re pulling its contents into a copy of a Makefile from the previous day:

$(EXE): $(CPP_SRC)
	@$(DOSBOX_CMD) -silent -nolog -nogui \
	    -c "MOUNT C $(ROOT_DIR)" \
        -c "MOUNT $(TOOLS_MOUNT) $(TOOLS_INSTALL_PATH)" \
        -c "PATH $(TOOLS_MOUNT):\WATC11\BINW;%path%" \
        -c "SET INCLUDE=$(TOOLS_MOUNT):\WATC11\H" \
        -c "SET WATCOM=$(TOOLS_MOUNT):\WATC11" \
        -c "SET EDPATH=$(TOOLS_MOUNT):\WATC11\EDDAT" \
        -c "C:" \
        -c "WCL DAY02.CPP > WCL.TXT" \
        -c "exit" 2>/dev/null

A quick side note: Watcom is really dear to my heart. I did my first serious software with it – first demoscene code and first OSS tools. I don’t remember what happened to my very first Watcom version (10.0), but I still have the 11.0 CD after multiple apartment changes and other adventures in adulthood. I got the CD from the only place I could back then, the Russian flea market, because – let’s face it – there were hardly any official software channels in the mid-90s. I keep that CD as a lucky charm. I still remember being shocked to discover the turtle graphic wasn’t an official Sybase cover…

A photo of CD case and a disk with Watcom C++ 11

Russian edition of Watcom C/C++ 11

Photo by author.

Now that the tools are installed, let’s do a quick sanity-check compilation:

#include <stdio.h>
#include <iostream>

int main(int argc, char** argv) {
    if (argc < 1) {
        std::cout << "Usage: day02 <file>\n";
        return 1;
    }
    return 0;
}

and…

Watcom C/C++16 Compile and Link Utility Version 11.0
Copyright by Sybase, Inc., and its subsidiaries, 1988, 1997.
All rights reserved.  Watcom is a trademark of Sybase, Inc.
       wpp DAY02.CPP 
Watcom C++16 Optimizing Compiler  Version 11.0 
Copyright by Sybase, Inc., and its subsidiaries, 1989, 1996.
All rights reserved.  Watcom is a trademark of Sybase, Inc.
DAY02.CPP(6): Error! E241: col(14) 'class std' has not been declared 
DAY02.CPP: 10 lines, included 1922, no warnings, 1 error
Error: Compiler returned a bad status compiling 'DAY02.CPP'

Oops… guess who forgot that Watcom 11.0 was released in 1997 – a whole year before the standard got, well, standardized. The STL already existed (created around 1994 and released to the public domain by Hewlett Packard about a year later), but there was no std namespace in Watcom 11.0. Muscle memory got me. After some thought, I decided to avoid the STL altogether. I’ve never really been a fan anyway (except maybe std::vector, which is nice, and std::optional, though that’s mostly because it was authored by a a friend I admire).

The Puzzle

And now, the puzzle. The first part looks pretty straightforward: just a simple loop. fscanf consumes all the whitespace, and for the example data, it’s just regular 5-digit columns.

bool is_safe_level(int* v, size_t len) {
    if (len < 2) return false;
    int sign = get_sign(v[0], v[1]);
    for (int i = 0; i < len - 1; i++) {
        const int a = v[i];
        const int b = v[i+1];
        const int diff = abs(a - b);
        const int s = get_sign(a, b);
        if (s != sign || diff > 3 || diff < 1) return false;
    }
    return true;
}

int main(int argc, char** argv) {
    // [...]
    int c;
    int v[5];
    do {
        for (i = 0; i < sizeof(v)/sizeof(v[0]); i++) c=fscanf(fp, "%d", &v[i]);
        if (c == EOF) break;
    
        if (is_safe_level(v, sizeof(v)/sizeof(v[0]))) part1_count++;
    } while(!feof(fp));
    // [...]
}

It works flawlessly with the example. But when I tried it on the real puzzle data, I got the wrong number. After closer inspection, the total records came out to 1315 when it should have been around 1000. It turned out that production data doesn’t always have 5 columns; the records vary in length. I decided to read the whole line first, then use sscanf on that string.

There is one catch: I need to parse digits until EOF, but I don’t know how many there will be. That means I have to do it one by one. Like the rest of its family, sscanf returns the number of records parsed, so sscanf(buf, "%d", &number) will return 1. The question is, how do I advance the string pointer after reading one integer? It turns out %n returns the number of characters read so far. That’s exactly what I need:

int c;
int v[30];
char line[1024];
char newline;

int part1_count = 0, total = 0;
while(fgets(line, sizeof line, fp) != NULL) {
    int count = 0, characters_read = 0;
    char *buf = line;
    while ((c = sscanf(buf, "%d%n", &v[count], &characters_read)) != EOF && c > 0) {
        buf += characters_read;
        count++;
    }

    if (is_safe_level(v, count)) part1_count++;

    memset(line, sizeof line, 0);
    memset(v, sizeof v, 0);
    total++;
}

Okay, now it works. On to the second part of the puzzle, which is a little trickier. I tried to be clever by just summing a sign function: sign = a == b ? 0 : ((a < b) ? -1 : 1), expecting that if I got one less than the total number of elements in the array, that would reveal the solution. So if for a 5-element array I get -5, for the array with one element removed I should get -4 if it’s safe… Turns out that didn’t match up. Since the number of columns is small, I ended up using an \(O(N^2)\) approach: test all permutations by removing one element at a time.

bool is_safe_level_dampener(int *v, size_t len) {
    if (is_safe_level(v, len)) return true;

    int modified_v[30];
    for (int ex = 0; ex < len; ex++) {
        for (int i=0, j=0; i < len; i++) {
            if (i != ex) modified_v[j++] = v[i];
        }
        if (is_safe_level(modified_v, len-1)) return true;
        memset(v, sizeof v, 0);
    }

    return false;
}

And that’s it – correct answer in hand. Can’t wait for next week… which happens to be tomorrow!

Comments

Discussion powered by , hop in. if you want.