DEV Community

Cover image for Emacs Literate Programming - Advent of Code
Andrew Lamichhane
Andrew Lamichhane

Posted on

Emacs Literate Programming - Advent of Code

Table of Contents

  1. First step
  2. The Problem
  3. Solution - Part 1
  4. Solution - Part 2
  5. Conclusion

Literate programming is a cool way to intergrate natural language into the source code for the explanation rather than using comments.
It was introuduced by Donald Knuth.

Emacs has an interesting way to achieve this, I haven't really seen it being used anywhere else than for configuring emacs itself,
so as an example let's solve Advent of Code 2022 (Day 2), using emacs org literate programming, for this the language of my choice is typescript.

I am not really good at typescript, so bear in mind that my code might not be follwing the standards.

First step

Let's create a file named "day2.ts" and initialize the neccessary configs for the typescript file,

npm init -y
tsc -init
Enter fullscreen mode Exit fullscreen mode

Create a file with ".org" extension, this will be recognized as an standard emacs org doc, and add this line below,

#+begin_src :tangle day2.ts 
  console.log("Hello Literate !"); 
#+end_src
Enter fullscreen mode Exit fullscreen mode

invoke the command "org-babel-tangle" using the "alt-x" or "C-c C-v t", and when you open the "day2.ts" file you should see the codeblock written from the org file into it.

if everything goes well then, we are set to solve the problem.

The Problem

The gist of the problem is that we are competing with a elf on a rock-paper-scissors match,

A -> Rock
B -> Paper
C -> Scissors

X -> Rock
Y -> Paper
Z -> Scissors 
Enter fullscreen mode Exit fullscreen mode

the points for the match is calculated on the basis of the shape you select,

Rock -> 1
Paper -> 2
Scissors -> 3

and the outcome,

Draw -> 3
Lost -> 0
Win -> 6
Enter fullscreen mode Exit fullscreen mode

Solution - Part 1

let's import the fs module to read the "input.txt" file and store the data into a variable,

    import fs from "fs";

    const data = fs.readFileSync("./input.txt", "utf8").toString();
Enter fullscreen mode Exit fullscreen mode

we can use enums to define the points and the hand, and pattern matching object to use later on,

    enum HAND {
      X = "rock",
      Y = "paper",
      Z = "scissors",
      A = "rock",
      B = "paper",
      C = "scissors"
    }

    enum HAND_POINTS {
      rock = 1,
      paper = 2,
      scissors = 3
    }

    enum GAME_POINTS {
      WIN = 6,
      DRAW = 3,
      LOST = 0
    }

    const pattern = {
     "rock paper": "win",
     "paper scissors": "win",
     "scissors rock": "win",
     "scissors scissors": "draw",
     "paper paper": "draw",
     "rock rock": "draw",
     "paper rock": "lose",
     "scissors paper": "lose",
     "rock scissors": "lose",
    }
Enter fullscreen mode Exit fullscreen mode

now that we have the data, let's split the string into a list,

    const games = data.split("\n").map((str) => str.replace(/\r/g, "")).filter(Boolean);
Enter fullscreen mode Exit fullscreen mode

The pointReturn function takes a single game match and returns the
point that the player wins,

    function pointReturn(games: string[]): number {
      let counter = 0;
      for(const match of games){
        const game = match.split(" ");
        const opponentHand = HAND[game[0] as keyof typeof HAND];
        const playerHand = HAND[game[1] as keyof typeof HAND];
        const playerPoint = HAND_POINTS[playerHand as keyof typeof HAND_POINTS];
        const opponentPoint = HAND_POINTS[opponentHand as keyof typeof HAND_POINTS]
        const moveset: string = [opponentHand, playerHand].join(" ");

        if (pattern[moveset] == "win") {
          counter += GAME_POINTS.WIN + playerPoint;
        } else if (pattern[moveset] == "draw") {
          counter += GAME_POINTS.DRAW + playerPoint;
        } else if(pattern[moveset] == "lose") {
          counter += GAME_POINTS.LOST + playerPoint;
        }
       }

      return counter;
     }
Enter fullscreen mode Exit fullscreen mode

finally we can return the data and log it,

    const points = pointReturn(games);
    console.log(points)
Enter fullscreen mode Exit fullscreen mode

Solution - Part 2

In part two of the problem, we need to make sure that we lose and win
in only particluar situaions.

if "X" then lose, "Y" Draw and "Z" is win, this ensures that we don't
get found out as a cheater.

The function below takes the input data and accoriding to the rules
changes the player input i.e our turn data into the one that is according
to the strategy.

    function strategizeInput(game: string[]){
      let newInput = [];
      for(const match of game){
        const [opponent, player] = match.split(" ");
        if(player == "X" && opponent == "A"){
         newInput.push([opponent, "Z"].join(" ")); 
        } else if(player == "X" && opponent == "B") {
          newInput.push([opponent, "X"].join(" ")); 
        } else if(player == "X" && opponent == "C") {
          newInput.push([opponent, "Y"].join(" "));
        }else if(player == "Y" && opponent == "A") {
          newInput.push([opponent, "X"].join(" "));
        } else if(player == "Y" && opponent == "B") {
          newInput.push([opponent, "Y"].join(" "));
        } else if(player == "Y" && opponent == "C") {
          newInput.push([opponent, "Z"].join(" "));
        } else if(player == "Z" && opponent == "A") {
          newInput.push([opponent, "Y"].join(" "));
        } else if(player == "Z" && opponent == "B") {
          newInput.push([opponent, "Z"].join(" "));
        } else if(player == "Z" && opponent == "C") {
          newInput.push([opponent, "X"].join(" "));
        }
      }

      return newInput;
    }

    console.log(pointReturn(strategizeInput(games)));
Enter fullscreen mode Exit fullscreen mode

Conclusion

Emacs org babel is a fun way to do literate programming, especially
for documenting the emacs configuration, other than that I am not
very sure how to use it rather than maybe for learning a topic
while writing the code and documenting your thougths at the same time.

A few things I found annoying while doing this exercise is that the
code blocks typically don't have "warnings" to indicate if you're
doing something wrong and I had to keep reviweing the code in the
"day2.ts" file, other than that it was a really interesting way to
solve this problem, it allowed me to express my ideas more while writing, more than the feeling of coding, it felt like I was writing an explanation for someone else to learn.

If you are viewing it in my blog this means that the entire document is the actual source code for the "problem" and for dev.to I used the org to markdown plugin in emacs to post it here.

Github Repo

Top comments (0)