# Solving AoC in Watcom C/C++
## Prerequisites
- 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.
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...
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) 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!