Prerequisites
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.
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…
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.