From 782e89d5ac463867274770c6143c9cfa77be52dd Mon Sep 17 00:00:00 2001 From: Dan Barclay Date: Sat, 26 Oct 2024 16:34:12 +0100 Subject: [PATCH] feat: moves rover to correct lovation --- .DS_Store | Bin 0 -> 6148 bytes bun.lockb | Bin 0 -> 3470 bytes index.ts | 186 ++++++++++++++++++++++++++++++++++++++++++ package.json | 14 ++++ test/.DS_Store | Bin 0 -> 6148 bytes test/manual/.DS_Store | Bin 0 -> 6148 bytes test/manual/data.txt | 5 ++ tsconfig.json | 29 +++++++ 8 files changed, 234 insertions(+) create mode 100644 .DS_Store create mode 100755 bun.lockb create mode 100644 index.ts create mode 100644 package.json create mode 100644 test/.DS_Store create mode 100644 test/manual/.DS_Store create mode 100644 test/manual/data.txt create mode 100644 tsconfig.json diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..65f9efe02003fdddc636af41e2373eab341f0a85 GIT binary patch literal 6148 zcmeHK%Wl&^6upxMVpA$*0a7(BAiXFN|5t=LAIrF$@9>?~Khltpqj2;qoiO7NrY~Moi7ZdHmH>{;= zY#@_;q{Qi*E+`zQ8_}l1C}0%WHU-4DYg0%iWz?s&@0Xv4DG$*qwuTdU|Cco6#CDJ% zKBAaDfmKjGPHXYs8u1-^N@F^u5j`iLM)hcRuMvG4eRJ9Sgf3;S#LVL6DD2AyZ$^RK ze*&5!zE2-12S00!_p`yX=?pVFp&>m3_AzEuXSAjbMjLC!e5k0$Nu9o~>GUTqoaZyG z=l5`yCVAnyKUiz0edA`w>R7whTmJ)}`$bTc`5?G@ElMvqPs3F{2wx`geA2yh%CjO! zviM38k|;*Vn^#E|@%ez4S(HnzuNo}dvM1gB#p2+}QO`Ma-DS^N938kl=h5Ng<Hn~VIGyWNCgfZ;o()Kj2K&ZVZo;4JPrt}`~!1OweNxT8;O00S@xIGH!CvEL+ z&y>tei~>f1Em1)14-Q0jh@CzAzENeQGtre^c90C zI_e!A=V`1IDmpQJ`Cxivrf(=rj*k8v8BWYoXi}qqQJ}8Cjw*J=`9He;{$D4Vl2O1Y z@Lwsw+CzUhz>@UYy0JKM);jQaaA6{@QYa}%^l>aR;wavSOGBF@GC)sbr4ThR^CKW- LFo{v%pDOSh>$$Kw literal 0 HcmV?d00001 diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..d18de1039818efd440e5e60b5b0245d526363d8b GIT binary patch literal 3470 zcmd5;dr(wW7{9wND4R(z_{h{2-wAv7#V(s{fMGd}lL|VDgvtUJ_ToO^-n+ciTtaCz z83fHSvoRGL6m`^@N+n+@i>4?l8D-Eo6_kiNlVnFr=oGhbCrR^%po~uZ8+U3xcR!7?64BAR5GaQV~%qer#aw$O&J6|)+sggxC z)p=6q#00cW+Z%CccWcQh@7l?jPPIj9YL3-j!=) zw7+c-`j0YaA?T)#xNcgA_DN}+z|G^?tdHLO~U&AfJb%ct)W;Pk33KjGH56_#D)6( zp&>&a^~LplGQ2m?iwOTu13n4S#Ve+kC68Dp$)8uz)VM2eXOVHmkN1)%mJE64e(RDc z!^>_aUYgf&FeU50sl4Xi2iNGEo~pnrt<9AUyHj2b^nCW^h?tSrMZCCfC~aB7w0+~+ zg9bkEN^EzYy%2irIrpdSbwwrf-kZIeb}scSB{tVymezlj9yQ#nKdPJ%n;LXu!>Zu- zkL;-4Q)_mAT_xhhb>Mkb!^pa~CPa0Xmnvju^A8NF3*W52_)AB-tD%K*Uyz2^rs~78 zitXA(GY<`2mVDYTqdybpzkboNhEZwze%QJ)PQ;7qiA}cu%*nP!Iwi*G$q$<%^{kzF zIAMQdOyp|HVqO!n>07Syb3+5WJi3~*gvP6DCFLjC=(3G;M8D__et9`bVVgv};(IO| zKWj}?+g9#n`L6h!b=#hw>k64`(RI{Dor}5Fnafp2R_zaMo%QaF`mw4bD>lwA@Jm*? zmma7Hy?gE0^uZa|3oAvu=-y$IwOqbf@nGsFu|JGtEamz{~gRfW?)l57% z-!UMm=|achle=x34;t+2CN=FDzjtd+Y4OP5Talw;W_;sZ6>#Q6Slfp);&n6Y4@}tR zE^WToSq;2>$#9Laf1KR<*p?n=3w;u^PxxS-61?2RGHE1jXF0RgN@BS|&S5AjTA@`d zSh|pMWP~ds6lN=(X?J9xKYYQwe|8z_qWgSX;2a2o0h!$7)M=vu`UnzKWAyz(-x_pR z&~F(1-i!i1u@^jOUBrR75D&s*F67;qOUPSpdP8ei;55SK48FWH-J8RD2J4b%Bn$PV znp6`6Rzz6Y43JvQ9K~`3fi)A>K2qODxfn8l1YB4JNhKOmL+Z2yQ76n`9VC^!X(aWe zMonO)g%uObXw{@fOB%8E!Ws%@^rY56Mmz*F3HXs!3H$!Zw|{ZHS<046G2|=x9L3np z*4Z3G+cTpTn(m`9b{`GT$FXU=KmL2j4rcw@5F}X9SYXR`)6J1NhdGv|7~ouRSy7ab zG5xm6y+h2~+U;Nxs&hE*~SVB`w2DK?Fvv$ void; + get: (x: number, y: number) => Direction | ''; + isValidPosition: (x: number, y: number) => boolean; +}; + +const createPlateau = (rows: number, columns: number): Plateau => { + const grid = Array(rows+1).fill(null).map(() => Array(columns+1).fill('')).reverse(); + + const arrayY = (y: number) => grid.length - 1 - y; + + return { + grid, + set: (x: number, y: number, value: Direction | '') => { + grid[arrayY(y)][x] = value; + }, + get: (x: number, y: number) => { + return grid[arrayY(y)][x]; + }, + isValidPosition: (x: number, y: number) => { + return x >= 0 && x < grid[0].length && y >= 0 && y < grid.length; + } + }; +}; + +const Instructions = ["M", "L", "R"] as const; +type Instruction = typeof Instructions[number]; + + +const Direction = ["N", "E", "S", "W"] as const; +type Direction = typeof Direction[number]; + +const readInput = async (filePath: string) => { + let data; + try { + const file = Bun.file(filePath); + data = await file.text(); + } catch (error: unknown) { + console.error('Error reading input file:', error); + return; + } + + const lines: string[] = data.split(/\r?\n/); + const formattedLines = lines.map(line => removeWhitespace(line.trim())); + + const upperRightCoordinatesOfPlateau = formattedLines.shift()?.split(""); + + if (!upperRightCoordinatesOfPlateau) { + console.error("Upper right coordinates of plateau are not provided"); + return; + } + + const [upperX, upperY] = upperRightCoordinatesOfPlateau; + + const plateau = createPlateau(safeParseInt(upperX), safeParseInt(upperY)); + + const roverInstructions = chunkArray(formattedLines, 2); + + roverInstructions.forEach(([initialCoordinatesWithDirection, instructions]) => { + const { x, y, direction} = placeRover(plateau, initialCoordinatesWithDirection); + explorePlateau(plateau, instructions, { x, y }, direction); + }); + + prettyPrintPlateau(plateau); +}; + +const initialCoordinatesSchema = z.tuple([ + z.string().transform((val) => parseInt(val, 10)), + z.string().transform((val) => parseInt(val, 10)), + z.enum(Direction) +]); + +const instructionsSchema = z.array(z.enum(Instructions)); + +const placeRover = (plateau: Plateau, initialCoordinates: string) => { + const parsedInitialCoordinates = initialCoordinatesSchema.safeParse(initialCoordinates.split('')); + + if (!parsedInitialCoordinates.success) { + throw new Error("Invalid initial coordinates"); + } + + const [x, y, direction] = parsedInitialCoordinates.data; + + plateau.set(x, y, direction); + + return { x, y, direction }; +}; + +const rotateRover = (currentDirection: Direction, instruction: Instruction): Direction => { + const directionMap: Record> = { + "N": { "L": "W", "R": "E", "M": "N" }, + "E": { "L": "N", "R": "S", "M": "E" }, + "S": { "L": "E", "R": "W", "M": "S" }, + "W": { "L": "S", "R": "N", "M": "W" } + }; + return directionMap[currentDirection][instruction]; +} + +const moveRover = (plateau: Plateau, currentDirection: Direction, currentCoordinates: { x: number, y: number }) => { + const directionMap: Record = { + N: { x: 0, y: 1 }, + E: { x: 1, y: 0 }, + S: { x: 0, y: -1 }, + W: { x: -1, y: 0 } + } + + const { x: dx, y: dy } = directionMap[currentDirection]; + + const newX = currentCoordinates.x + dx; + const newY = currentCoordinates.y + dy; + + if (plateau.isValidPosition(newX, newY)) { + plateau.set(newX, newY, currentDirection); + plateau.set(currentCoordinates.x, currentCoordinates.y, ''); + return { x: newX, y: newY }; + } else { + console.log("Invalid move. Rover stays in place."); + return currentCoordinates; + } +} + +const explorePlateau = (plateau: Plateau, instructions: string, placedRoverCoordinates: { x: number, y: number }, currentDirection: Direction) => { + const parsedInstructions = instructionsSchema.safeParse(instructions.split('')); + + if (!parsedInstructions.success) { + throw new Error("Invalid instructions"); + } + + let currentCoordinates = placedRoverCoordinates; + + parsedInstructions.data.forEach((instruction) => { + if (instruction === "L" || instruction === "R") { + currentDirection = rotateRover(currentDirection, instruction); + plateau.set(currentCoordinates.x, currentCoordinates.y, currentDirection); + } else if (instruction === "M") { + currentCoordinates = moveRover(plateau, currentDirection, currentCoordinates); + } + }); + + return { ...currentCoordinates, direction: currentDirection }; +} + + +const safeParseInt = (value: string): number => { + const parsed = Number.parseInt(value, 10); + if (isNaN(parsed)) { + throw new Error(`Invalid integer: ${value}`); + } + return parsed; +}; + +const chunkArray = (array: T[], chunkSize: number): T[][] => { + return array.reduce((acc, _, index) => { + if (index % chunkSize === 0) { + acc.push(array.slice(index, index + chunkSize)); + } + return acc; + }, []); +}; + +const removeWhitespace = (str: string): string => { + return str.replace(/\s/g, ''); +}; + +const prettyPrintPlateau = (plateau: Plateau) => { + const maxLength = Math.max(...plateau.grid.flat().map(item => item.toString().length)); + + plateau.grid.forEach((row, arrayY) => { + const y = plateau.grid.length - 1 - arrayY; + console.log( + row.map((cell, x) => cell === '' ? `(${x},${y})` : cell.toString().padEnd(maxLength, " ")).join(" | ") + ); + }); +}; + +readInput("./test/manual/data.txt"); + + + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..0f02715 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "mars-rover", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "zod": "^3.23.8" + } +} \ No newline at end of file diff --git a/test/.DS_Store b/test/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..9778fcbf6dd4fa1a93437222053e87cc003ff4b9 GIT binary patch literal 6148 zcmeHKK}*9h6i&A3T87Yr!j1v2L+d8u@KWbIc(tMjmD$>X#jY9a$_``Dv;HA}iGN4m zOEPgx!IOx44_?0H<-Ml)67mwp81LlK9%Bw;%mPI$*-(5T*pIp-C1XM4>Kg$|&JuYa zCNf;|Cc|H30M~Av4OqZZmP7e@{cuwpgYLX3QNO?78&0yJS?iZ@n8exGbzga@Tv=PM zT2-rN-TDu5;*W!Io^^uJHMTCKOu~6R2ruLQq~~m&$aEaUX@8^w;;0WUH&<~Q$w^1% zX_Tp0Uw2rxW%r!=blN;@H$=;IXALoJH{FKVZyn5LwzajrdvxA=Oa`fZQh&`j>`x^d z2IufRuMiRg!~ij{LJXKQ(WJ^bQmS{R)jAAy84N7<@4m c*FlwlU0?;!GgxQ@3kY2V6b)1m1Ha0^I|8{Asje-*K;k80 z%^EqYe$0IL@7~k3?Wx0$vf#!Ua0Z-#zsmr7w#eWpqFZOc8E^)+49NQ-p$cY(ZA5)_ z(C8I_*r!{CWBFv1oWw9QY$I}p5@ISbrUtuWgqTi$VsV*a8!@IM*ySVG%E4|ZL04z} zM8grXh;E$$XW%OXa`&SQrT<_2eE-)VH_m`F@IM&{{mFDP=H9Hgc6KSfHlf~AMI^3` kxJ}_eTQOs$6(3Vo=uc!o%naLz^icdmKxuH}4D6MGceH;^EdT%j literal 0 HcmV?d00001 diff --git a/test/manual/data.txt b/test/manual/data.txt new file mode 100644 index 0000000..0992434 --- /dev/null +++ b/test/manual/data.txt @@ -0,0 +1,5 @@ +5 5 +1 2 N +LMLMLMLMM +3 3 E +MMRMMRMRRM \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..666c94f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false, + + "types": ["bun-types"] + } +}