const fs = require('fs'); const readline = require('readline'); function Rope(len) { this.tokens = []; this.visited = {'0x0': true}; this.visitedCoord = [[0, 0]]; this.boundaries = [[0, 0], [0, 0]]; this.len = len; for (let i =0; i < len +1; ++i) this.tokens.push([0, 0]); } Rope.prototype.applyMove = function(moveArr) { this.tokens[0][0] += moveArr[0]; this.tokens[0][1] += moveArr[1]; this.boundaries = [ [ Math.min(this.boundaries[0][0], this.tokens[0][0]), Math.max(this.boundaries[0][1], this.tokens[0][0]) ], [ Math.min(this.boundaries[1][0], this.tokens[0][1]), Math.max(this.boundaries[1][1], this.tokens[0][1]) ], ]; for (let i =0; i < this.tokens.length -1; ++i) this.checkAndMove(i); this.visited[this.tokens[this.tokens.length -1][0] + 'x' +this.tokens[this.tokens.length -1][1]] = true; this.visitedCoord.push([this.tokens[this.tokens.length -1][0], this.tokens[this.tokens.length -1][1]]); } Rope.prototype.checkAndMove = function(headPos) { if (Math.abs(this.tokens[headPos][0] - this.tokens[headPos +1][0]) <= 1 && Math.abs(this.tokens[headPos][1] - this.tokens[headPos+1][1]) <= 1) return; if (this.tokens[headPos +1][1] !== this.tokens[headPos][1]) this.tokens[headPos +1][1] += (this.tokens[headPos +1][1] < this.tokens[headPos][1]) ? 1 : -1; if (this.tokens[headPos +1][0] !== this.tokens[headPos][0]) this.tokens[headPos +1][0] += (this.tokens[headPos +1][0] < this.tokens[headPos][0]) ? 1 : -1; } const COLORS = [ '\033[39m', '\033[31m', '\033[32m', '\033[33m', '\033[34m', '\033[35m', '\033[36m' ] function showRope(arenaSize, ropeItems) { // clear screen for (let i =0; i < 5; ++i) console.log(); // /clear screen console.log('\x1Bc'); let item = ropeItems.shift(); for (let py =0; py < arenaSize[1]; ++py) { let line = ''; for (let px =0; px < arenaSize[0]; ++px) { if (item && item.x === px && item.y === py) { line += COLORS[item.color] + item.c + COLORS[0]; item = ropeItems.shift(); } else { line += '.'; } } console.log(line); } } function prepShowRope(arenaSize, arenaShift, replayRope) { showRope(arenaSize, replayRope.tokens .map((i, index) => { return { x: i[0]+arenaShift[0], y: i[1]+arenaShift[1], c: '#', color: index+1 >= COLORS.length ? Math.ceil(Math.random() * (COLORS.length-1)) : (index +1) };}).concat( replayRope.visitedCoord.map(i => { return { x: i[0] + arenaShift[0], y: i[1] + arenaShift[1], c: '"', color: 0 };}) ) .sort((a, b) => { if (a.x === b.x && a.y === b.y) return a.c === '"' ? 1 : -1; if (a.y === b.y) return a.x-b.x; return a.y-b.y; }) .filter((value, index, self) => self.findIndex(i => i.x === value.x && i.y === value.y) === index)); } function mSleep() { return new Promise(ok => setTimeout(ok, 90)); } async function showReplay(rope, actions) { let arenaSize = [ rope.boundaries[0][1] - rope.boundaries[0][0] +1, rope.boundaries[1][1] - rope.boundaries[1][0] +1]; let arenaShift = [ -rope.boundaries[0][0], -rope.boundaries[1][0] ]; let replayRope = new Rope(rope.len); await prepShowRope(arenaSize, arenaShift, replayRope); for (let i of actions) { await mSleep(); replayRope.applyMove(i); prepShowRope(arenaSize, arenaShift, replayRope); } } async function main() { const move = { 'U': [0, -1], 'D': [0, 1], 'L': [-1, 0], 'R': [1, 0] }; let tokens = new Rope(1); let tokensPart2 = new Rope(10); let moveList = []; for await (let line of readline.createInterface({ input: process.stdin })) { if (!line || !line.length) continue; let reg = /([UDLR]) (\d+)/.exec(line); for (let i =0; i < reg[2]; ++i) { moveList.push(move[reg[1]]); tokens.applyMove(move[reg[1]]); tokensPart2.applyMove(move[reg[1]]); } } await showReplay(tokensPart2, moveList); console.log("Part 1: ", Object.keys(tokens.visited).length); console.log("Part 2: ", Object.keys(tokensPart2.visited).length); }; (main());