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 and make sure that you are in sync with your team, if you don't have a team yet submit your application here
Take your time with the challenge and solve the puzzle in the two mentioned scenarios. Good luck.
1.1 Rules
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.
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:
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.
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:
A Y
B X
C Z
will translate to:
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:
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.
main = putStr $ unlines [[x, ' ', y] | x <- ['A'..'C'], y <- ['X'..'Z']]
So far, this is what we have:
A X
A Y
A Z
B X
B Y
B Z
C X
C Y
C Z
and our end goal is:
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.
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:
: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:
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:
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:
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:
let pointOf = {'rock': 1, 'paper': 2, 'scissors': 3}
Additionally, we create a dictionary to map each move to its defeating move:
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:
echo readfile("input")
The output is a list of lines in the format ['symbolA symbolB', 'symbolA symbolB', ...]
. We transform it to [['symbolA', 'symbolB'], ...]
:
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.
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.
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.
\ ->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:
\ ->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:
\ ->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.
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.
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.
\ '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.
\ 'Y': {x -> x}
In the case of Y, signifying a draw, we simply return the same enemy move denoted by x.
\ '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.
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
In your preferred programming language, create a script that can automatically generate all potential game outcomes and their score.
Run your code in Vim to generate all the required data automatically.
Below is an example using 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'])))
Top comments (0)