Using only vim to solve AdventOfCode Challenges | Episode 2
Tags: vim, neovim, productivity, linux
Published: 2023-12-17 20:31:33

**Note: It's important to read the first episode to understand the context of how these challenges are solved.**

# 1\. Getting Started

Open [today's challenge](https://adventofcode.com/2022/day/1) and make sure that you are in sync with your team, if you don't have a team yet submit your application [here] (https://forms.gle/bwegPDvbgEuWKDF47)

Take your time with the challenge and solve the puzzle in the two mentioned scenarios. **Good luck.**

## 1.1 Rules

1. In the first scenario, if you plan to hardcode all possible outcomes of the game, avoid manual entry. Instead, find an automatic way to handle them. Imagine having one million potential outcomes for the game—your approach should be automated for speed. Utilize your editor for efficiency. **Remember, the emphasis in the first scenario is on speed.**
    
2. In the second scenario, write a Vimscript that accommodates the worst-case scenario. Consider a situation where each symbol might signify something else in the future. Structure your code to distinctly separate the game logic from symbol deciphering.
    

## 1.2 Sample Data

The provided sample data should be used for testing the commands. Once you feel comfortable and confident, you can apply these commands to your actual input data.

**Input**:

```plaintext
A Y
B X
C Z
```

**Expected output**:
* part1: 15
* part2: 12
    

# 2\. Solutions

This guide assumes you've already watched the accompanying walk-through YouTube video. If you haven't, please do.

{% embed https://www.youtube.com/watch?v=ACzoJj-sunE %}

## 2.1 The First Scenario

### 2.1.1 Part One

Our input data consists of symbols representing each player's move, and our task is to calculate the score of each game. The quickest way is to map all possible outcomes and replace each line with the corresponding score. For example:

```plaintext
A Y
B X
C Z
```

will translate to:

```plaintext
8
1
6
```

This mapping can be done using Vim dictionaries, acting like hashtables or objects. Below is a function `Swap` containing a dictionary with all possible game outcomes:

```plaintext
function! Swap(line)
  let scores = {
    \ 'A X': '4',
    \ 'A Y': '8',
    \ 'A Z': '3',
    \ 'B X': '1',
    \ 'B Y': '5',
    \ 'B Z': '9',
    \ 'C X': '7',
    \ 'C Y': '2',
    \ 'C Z': '6',
  \ }

  return scores[a:line]
endfunction
```

While we can use this function immediately, today's challenge rules prohibit hard coding outcomes. Instead, we need a fast and creative way to enter them.

To generate all possible symbols, use `:r! echo \\n{A..C}" "{X..Z}`.

In the walkthrough video, alternatives to Bash were discussed, showcasing the flexibility to choose more powerful tools based on specific needs.

This Haskell code achieves the same result as the previous Bash code, but with a key advantage: scalability. We can easily make further transformations to the generated output by adding additional functions to the pipeline.

```Haskell
main = putStr $ unlines [[x, ' ', y] | x <- ['A'..'C'], y <- ['X'..'Z']]
```

So far, this is what we have:

```plaintext
A X 
A Y 
A Z 
B X 
B Y 
B Z 
C X 
C Y 
C Z
```

and our end goal is:

```plaintext
A X: score 
A Y: score
... 
C Z: score
```

First let's create a function on the fly, that will assign the bonus points, if we loose we get no bonus, if we end with a draw we get the hand move point plus 3 and if we win we get the hand move point plus 6.

```plaintext
let BonusOf = {a, b -> a > b ? 0 : (a < b ? 6 : 3)
```

Now, to assign the correct score to each combination, let's record a quick macro.

Start by recording a macro on the first line with the following commands:

```plaintext
:let ln = line('.')
:let playerA = ln-1/3 + 1
:let playerB = ln%3 ? ln%3 : 3

gg0C'p<Esc>A: <C-r>=playerB + (playerA>1 && playerB>1 ? BonusOf(playerA,playerB) : BonusOf(playerA%3,playerB%3))jq
```

As we covered in the walkthrough video, we're using line numbers to determine the value of each hand. By default, "A, X" is 1, "B, Y" is 2, and so on.

We also use BonusOf(playerA%3, playerB%3) to round the value of each hand by three. This is because "Rock" is typically 1 and "Paper" is 2. However, we need to adjust this logic for "Scissors" (3), which should not be considered stronger than "Rock." The walkthrough video explains this rounding process in more detail.

Now, let's apply the macro we recorded on the first line to the rest of the lines. Simply use `.,$ norm @q` and you're good to go!

Then wrap everything in a function:

```plaintext
function! Swap(line)
  let scores = {
        \ 'A X': 4,
        \ 'A Y': 8,
        \ 'A Z': 3,
        \ 'B X': 1,
        \ 'B Y': 5,
        \ 'B Z': 9,
        \ 'C X': 7,
        \ 'C Y': 2,
        \ 'C Z': 6,
        \ }

  return scores[a:line]
endfunction
```

Open the input file and run the function on each line using `:%s/.*/\=Swap(submatch(0))`. To calculate the sum of all lines, use `%! paste -sd+ | bc`.

### 2.1.2 Part Two

For the second part of the challenge, the approach remains similar. Adjust the way you calculate the score for each round. You'll end up with a function like this:

```plaintext
function! Swap2(line)
  let scores = {
    \ 'A X': 3,
    \ 'A Y': 4,
    \ 'A Z': 8,
    \ 'B X': 1,
    \ 'B Y': 5,
    \ 'B Z': 9,
    \ 'C X': 2,
    \ 'C Y': 6,
    \ 'C Z': 7,
  \ }

  return scores[a:line]
endfunction
```

Apply the same steps as in Part One to obtain the answer for the second part efficiently. Don't retype your previous commands, just recall them from the command history. this step shouldn't take more than 5 seconds.

## 2.2 The Second Scenario

**Defining Essential Functions**

First and foremost, let's address the essential functions required for our game logic:

```plaintext
let Bonus = {a, b -> a > b ? 0 : (a < b ? 6 : 3)}
let Score = {a, b -> a>1 && b>1 ? g:Bonus(a, b) + b : g:Bonus(a%3, b%3) + b}
```

These functions establish the scoring mechanism for the game.

**Handling Hand Moves**

To proceed, we need to assign points to each hand move:

```plaintext
let pointOf = {'rock': 1, 'paper': 2, 'scissors': 3}
```

Additionally, we create a dictionary to map each move to its defeating move:

```plaintext
let next = {'rock': 'scissors', 'paper': 'rock', 'scissors': 'paper'}
```

**Processing Input Data**

Assuming our input data is stored in a file named `input`, we start by reading and printing its content:

```plaintext
echo readfile("input")
```

The output is a list of lines in the format `['symbolA symbolB', 'symbolA symbolB', ...]`. We transform it to `[['symbolA', 'symbolB'], ...]`:

```plaintext
echo
      \ readfile("inputdemo.vim")
      \ ->map("split(v:val, ' ')")
```

The next step now is to translate each symbol to what it mean, whether is it 'rock', 'paper' or 'scissors'. for now, we are still in the testing phase, let's create a dummy function that will do this translation.

```plaintext
function ToHand(symbol)
  return {_ -> 'rock'}
endfunction
```

Our `ToHand` function takes a symbol and returns a new function. This new function, in turn, takes an argument and ultimately returns the name of the hand. In our example, it will always return "rock."

You might wonder why we don't just create a function that directly returns the string "rock" instead of adding an extra layer of abstraction. The reason is that the function we return will later be able to accept symbols from either the left or right column. This allows each function to have information about the opponent's move.

While `ToHand` may seem simple now, we'll revisit it and expand its functionality later.

```plaintext
echo
      \ readfile("inputdemo.vim")
      \ ->map("split(v:val, ' ')")
      \ ->map({_, xs -> [ToHand(xs[0])(xs[1]), xs[1]]})
```

Here, we map over each element, applying ToHand to symbols in the left column for player one's move, and then applying it to the right column for player two's move.

```plaintext
      \ ->map({_, xs -> [xs[0], ToHand(xs[1])(xs[0])]})
```

While applying both functions simultaneously might seem tempting, it would make our lambda function unnecessarily complex. Instead, we'll follow the natural flow of the game: player one plays their move, the game state updates, and then player two reacts based on the updated state. This approach keeps our code easy to read.

Next, we convert hand names to their corresponding points:

```plaintext
      \ ->map({_, xs -> map(xs, "g:pointOf[v:val]")})
```

We call the Score function to obtain the score for each game round and sum up all the numbers in the list:

```plaintext
      \ ->map({_, v -> g:Score(v[0], v[1])})
      \ ->reduce({a, b -> a + b})
```

This encapsulates the entire game logic.

**Note**: While this script utilizes nested maps, it aligns with the nature of Vimscript. In other languages, we might explore composition or transduction, but for now, we'll keep Vimscript within its natural confines. Any future modifications will be limited to the `ToHand` function.

**Note**: I have written this using map on top of map. for this script is fine, in other languages, I might compose or transduce but let's not push vimscript doing things that aren't naturally built for. However, it's acknowledged that in future iterations, Lua may be explored for enhanced functionality, as hinted in the [seond season](https://github.com/cipherlogs/s909/blob/main/s2.md).

Moving forward, the sole modification we'll make is to our placeholder function `ToHand`; all other aspects of the script remain unchanged.

### 2.2.1 Part One

In the initial phase of the puzzle, we assign 'rock' to A and X, 'paper' to B and Y, and 'scissors' to C and Z.

Now, let's refine our 'ToHand' function to accurately represent each symbol.

```plaintext
function ToHand(symbol)
  let symbols = {
        \ 'A': {_ -> 'rock'},
        \ 'B': {_ -> 'paper'},
        \ 'C': {_ -> 'scissors'},
        \ 'X': {_ -> 'rock'},
        \ 'Y': {_ -> 'paper'},
        \ 'Z': {_ -> 'scissors'},
        \}

  return symbols[a:symbol]
endfunction
```

This modification ensures that our 'ToHand' function correctly translates each symbol to its corresponding hand gesture.

And that's it – we're finished!

### 2.2.2 Part Two

In the second part, we define the conditions for three scenarios: losing, drawing, and winning denoted by X, Y, and Z respectively. Achieving these outcomes involves specific modifications to our `ToHand` function.

```plaintext
        \ 'X': {x -> g:next[x]}
```

For scenario X, which corresponds to a loss, we take the enemy's hand move, represented by arg `x`, and call the `next` function to determine the subsequent move required for a loss.

```plaintext
        \ 'Y': {x -> x}
```

In the case of Y, signifying a draw, we simply return the same enemy move denoted by x.

```plaintext
        \ 'Z': {x -> g:next[g:next[x]]}
```

For Z, indicating a win, we perform the inverse of the X scenario.

### 2.2.3 The Final Script

That's all the necessary adjustments to the `ToHand` function. The final and comprehensive script is provided below.

```plaintext
function ToHand(symbol)
  let symbols = {
        \ 'A': {_ -> 'rock'},
        \ 'B': {_ -> 'paper'},
        \ 'C': {_ -> 'scissors'},
        \ 'X': {x -> g:next[x]},
        \ 'Y': {x -> x},
        \ 'Z': {x -> g:next[g:next[x]]},
        \}

  return symbols[a:symbol]
endfunction


" CORE, DO NOT TOUCH
let pointOf = {'rock': 1, 'paper': 2, 'scissors': 3}
let next = {'rock': 'scissors', 'paper': 'rock', 'scissors': 'paper'}
let Bonus = {a, b -> a > b ? 0 : (a < b ? 6 : 3)}
let Score = {a, b -> a>1 && b>1 ? g:Bonus(a, b) + b : g:Bonus(a%3, b%3) + b}

echo
      \ readfile("inputdemo.vim")
      \ ->map("split(v:val, ' ')")
      \ ->map({_, xs -> [ToHand(xs[0])(xs[1]), xs[1]]})
      \ ->map({_, xs -> [xs[0], ToHand(xs[1])(xs[0])]})
      \ ->map({_, xs -> map(xs, "g:pointOf[v:val]")})
      \ ->map({_, v -> g:Score(v[0], v[1])})
      \ ->reduce({a, b -> a + b})
```

## Exercises

1. In your preferred programming language, create a script that can automatically generate all potential game outcomes and their score.
    
2. Run your code in Vim to generate all the required data automatically.
    

Below is an example using Javascript:

```JavaScript
const log = console.log;

const getCombos = xs => ys =>
  xs.flatMap (x => ys.map (y => [x, y]));

const pointOf = move => {
  const moves = {'X': 1, 'Y': 2, 'Z': 3,};
  return moves[move];
};

const some = xs => xA => xB =>
  xs
    .map (x => x.split(""))
    .some (x => x[0] === xA && x[1] === xB);

const isDraw = some (['AX', 'BY', 'CZ']);

const isLost = some (['AZ', 'BX', 'CY']);

const format = combos => {
  return (
    combos.map (combo => {
      const enemyMove = combo[0];
      const myMove = combo[1];
      const shapeScore = pointOf (myMove);

      const score = (
        isDraw (enemyMove) (myMove)
          ? 3 + shapeScore
          : isLost (enemyMove) (myMove) ? shapeScore : 6 + shapeScore
      );

      return `'${enemyMove} ${myMove}':${score},`
    })
  );
};

log (format (getCombos (['A', 'B', 'C']) (['X', 'Y', 'Z'])))
```