-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.ts
More file actions
196 lines (167 loc) · 5.37 KB
/
index.ts
File metadata and controls
196 lines (167 loc) · 5.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/**
* ___ __ ___
* | \ __ _ _ _ / \| __|
* | |) / _` | || | | () |__ \
* |___/\__,_|\_, | \__/|___/
* |__/
*
* "Supply Stacks"
*
* Given a little ASCII art of stacked letters atop numbers, followed by a list
* of movements, determine the contents of veritical stacks, then execute the
* movements in order (individually). Finally, report the top item in each stack
* after the transofrmation.
*
* WARNING: Input file has hidden spaces at the end of each line... watch out for that...
*
* ALSO WARNING: Object properties have to be strings, so the stack designations
* will always be strings. (They're integers.)
*/
import * as fs from 'fs';
import { join } from 'path';
import chalk from 'chalk';
const FILENAME = 'input.txt';
const MODE: number = 9001; // For part one, use 9000. For part two, use 9001.
type cargoStack = string[];
interface cargoArea {
[key: string]: cargoStack;
}
interface movement {
count: number;
from: number;
to: number;
}
type instructions = movement[];
let maxDisplayHeight = 0;
/**
* Pretty print with colors.
*
* @param cargo (cargoArea) Current cargo stack area
*/
const visualizeCargo = (cargo: cargoArea): void => {
const cols = Object.keys(cargo).length;
maxDisplayHeight = Object.keys(cargo).reduce((max, key) => Math.max(max, cargo[key].length) , maxDisplayHeight);
const output = [];
// Header Row
output.push('');
output.push((' ' + Object.keys(cargo).join(' ')));
output.push(chalk.gray('-'.repeat(output[1].length + 1)));
// Contents
for (let i = 0; i < maxDisplayHeight; i++) {
const row = [];
for (const stack in cargo) {
// Assume empty
let display = ' ';
// If it's populated and the top of the stack
if (cargo[stack][i] && i < cargo[stack].length - 1) {
display = `${chalk.gray("[")}${chalk.blueBright(cargo[stack][i])}${chalk.gray("]")}`;
}
// If it's populated but not on top:
else if (cargo[stack][i]) {
display = `${chalk.gray("[")}${chalk.yellowBright(cargo[stack][i])}${chalk.gray("]")}`;
}
row.push(display);
}
output.push(row.join(' '));
}
output.push('');
console.log(output.reverse().join("\n"));
};
/**
* Execute a step 1 or more times on the cargo field.
*
* @param move (movement) what to do
* @param cargo (cargoArea) the working cargo space
*/
const doStep = (move: movement, cargo: cargoArea, mode: number = 9000): void => {
// Part One
if (mode === 9000) {
for (let i = 0; i < move.count; i++) {
const pickup = cargo[move.from.toString()].pop();
if (pickup) {
cargo[move.to.toString()].push(pickup);
}
}
}
// Part Two
else if (mode === 9001) {
const pickup = cargo[move.from.toString()].splice(-move.count);
if (pickup) {
cargo[move.to.toString()].push(...pickup);
}
}
}
/**
* Wait on the main thread, for visualization purposes
*
* @param ms (number) miliseconds delay
* @returns
*/
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const report = (cargo: cargoArea): string => {
return Object.keys(cargo)
.reduce((report, stack) => report + cargo[stack][cargo[stack].length - 1], "");
}
// MAIN:
const [stacksArt, stepsRaw] =
fs.readFileSync(join(process.cwd(), `lib/${FILENAME}`), 'utf8')
.split("\n\n");
const stacksRaw = stacksArt.split("\n").reverse();
// Split the "header" row into the column numbers we'll have. Extra spaces don't
// matter, and after clean/split, it's just digits.
const stackAddresses: string[] = stacksRaw
.shift()?.trim().replace(/\s+/g, ' ').split(' ') ?? [];
// Make a column stack for each address we got.
const cargo: cargoArea =
Object.assign({}, ...Array.from(stackAddresses, (a) => ({[a]: []})))
// Make addresses we can use when we split out the map.
const addresses: number[] = Object.keys(cargo).map(s => parseInt(s));
// Unpack the artwork into
// {
// [COLUMN]: [BOTTOM ... TOP]
// }
stacksRaw.forEach((row) => {
addresses.forEach((n) => {
// Funky addressing but this works.
const textPostion = (n * 4) - 3;
const container = row.slice(textPostion, textPostion + 1);
if (container && container != ' ') {
cargo[n.toString()].push(container);
}
});
});
// Unpack the instructions raw text into
// [{
// count: How many to move
// from: Source stack
// to: Destination stack
// }, {}, ...]
const steps: instructions = stepsRaw.trim().split("\n").flatMap((row) => {
const numbers = row.match(/\d+/g)?.map(s => parseInt(s));
if (numbers) {
return {
count: numbers[0],
from: numbers[1],
to: numbers[2],
}
} else {
// Mostly to make type checking happy, but if a row fails the regex, flatMap
// will make bad rows go away.
return [];
}
});
// Run the instructions on the cargo area global
for(const step in steps) {
console.clear();
console.log(`#${step}: Move ${steps[step].count} from ${steps[step].from} -> ${steps[step].to}`);
visualizeCargo(cargo);
doStep(steps[step], cargo, MODE);
await delay(100);
}
if (MODE === 9000) {
// Part One: The top crates are TDCHVHJTG
console.log(`The top crates are ${chalk.yellowBright(report(cargo))}\n`);
} else if (MODE === 9001) {
// Part Two: With the NEW AND IMPROVED CrateMover 9001, the top crates are NGCMPJLHV
console.log(`With the NEW AND IMPROVED CrateMover 9001, the top crates are ${chalk.yellowBright(report(cargo))}\n`);
}