_______._ _ _ _.___/ | | | | | █ ▀▀▀▀▀▀███ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ █▀▀▀▀▀▀███ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█ █ █▀▀▀▀▀███ █▀▀▀▀▀███ █▀▀▀▀▀███ █▀▀▀▀▀▀▀▀▀ █ angelo! █▀▀▀▀▀▀███ ▄ █ █████ ███ █████ ███ █████ ███ ██████ ███ ██████ ███ ██████ ███ █ ▄ ▀▀▀▀▀ ▀▀▀ ▀▀▀▀▀ ███ ▀▀▀▀▀ ███ ▀▀▀▀▀▀ ▀▀▀ ▀▀▀▀▀▀ ▀▀▀ ▀▀▀▀▀▀ ▀▀▀ █ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀ ▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ √m⌡ : | - -----andrzej-_ ____\_____|----_|_____________'-lichnerowicz-------- - : | . p e r s o n a l ! | #1 | wEBPAGE . . _ - --------- | - -- - | ------- - _ . -:-\-\-------------------|- - - -|-----------------------------------\-\-: | _|___________|_ | | : : |
|//_)-> 2 0 2 5 . 0 1 . 1 2 <--------------------------------------------(_\|
| |
| |
| |
# Solving AoC in Watcom C/C++
## 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.

Screenshot by author.

Watcom C/C++ 11 installation

The installer modifies and . The former doesn’t really affect us -- is managed by DosBox anyway. As for , 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

Photo by author.

Russian edition of Watcom C/C++ 11

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 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 , which is nice, and , 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. 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 when it should have been around . 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 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, returns the number of records parsed, so will return . The question is, how do I advance the string pointer after reading one integer? It turns out 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: , 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 , for the array with one element removed I should get if it's safe... Turns out that didn't match up. Since the number of columns is small, I ended up using an O(N2)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!

| |
| |
| |
\__ --> andrzej.lichnerowicz.pl <-- __/ // \\ // ------------------------ ---------------------- \\ '~~~~~~~~~~~~~~~~~~~~~~~~~// ------ ------- \~~~~~~~~~~~~~~~~~~~~~~` '~~~~~~~// \~~~~~~~` // ---------- \ '~~~~~~~~~~~~~~~`