CoDEVIANT #2 (3/22/19) @ midnight-ish
They say that habits are hard to form. I would have to agree…except the bad ones, which is why I’m constantly waking up late, using my host family’s wifi allotment for stupid shit, and over eat at night. At any rate, I’m trying really trying to establish this good habit of doing CoDEVIANT daily so I don’t become the coder equivalent of Brad Pitt speaking Italian in Inglorious Basterds.
At any rate, let’s get to the first challenge for the day, and one that is after my own heart as an avid video game player:
Street Fighter 2 — Character Selection (https://www.codewars.com/kata/5853213063adbd1b9b0000be/train/javascript)
It’s like the internet gods knew I’d be wanting to play video-games and wait to win the lottery instead of d̵o̵i̵n̵g̵ ̵t̵h̵i̵s̵ ̵s̵h̵i̵t̵ bettering myself.
Directions:
You’ll have to simulate the video game’s character selection screen behavior, more specifically the selection grid. Such screen looks like this:
“Character Selection Screen for Street Fighter 2”
Selection Grid Layout in text:
| Ryu | E.Honda | Blanka | Guile | Balrog | Vega |
| Ken | Chun Li | Zangief | Dhalsim | Sagat | M.Bison |
** Input **
- the list of game characters in a 2x6 grid;
- the initial position of the selection cursor (top-left is (0,0));
- a list of moves of the selection cursor (which are up, down, left, right);
** Output **
- the list of characters who have been hovered by the selection cursor after all the moves (ordered and with repetition, all the ones after a move, wether successful or not, see tests);
** Rules **
Selection cursor is circular horizontally but not vertically!
As you might remember from the game, the selection cursor rotates horizontally but not vertically; that means that if I’m in the leftmost and I try to go left again I’ll get to the rightmost (examples: from Ryu to Vega, from Ken to M.Bison) and vice versa from rightmost to leftmost.
Instead, if I try to go further up from the upmost or further down from the downmost, I’ll just stay where I am located (examples: you can’t go lower than lowest row: Ken, Chun Li, Zangief, Dhalsim, Sagat and M.Bison in the above image; you can’t go upper than highest row: Ryu, E.Honda, Blanka, Guile, Balrog and Vega in the above image).
** Test **
For this easy version the fighters grid layout and the initial position will always be the same in all tests, only the list of moves change.
** Notice **: changing some of the input data might not help you.
** Examples **
1.
fighters = [
[“Ryu”, “E.Honda”, “Blanka”, “Guile”, “Balrog”, “Vega”],
[“Ken”, “Chun Li”, “Zangief”, “Dhalsim”, “Sagat”, “M.Bison”]
]
initial_position = (0,0)
moves = [‘up’, ‘left’, ‘right’, ‘left’, ‘left’]
then I should get:
[‘Ryu’, ‘Vega’, ‘Ryu’, ‘Vega’, ‘Balrog’]
as the characters I’ve been hovering with the selection cursor during my moves. Notice: Ryu is the first just because it “fails” the first upSee test cases for more examples.
2.
fighters = [
[“Ryu”, “E.Honda”, “Blanka”, “Guile”, “Balrog”, “Vega”],
[“Ken”, “Chun Li”, “Zangief”, “Dhalsim”, “Sagat”, “M.Bison”]
]
initial_position = (0,0)
moves = [‘right’, ‘down’, ‘left’, ‘left’, ‘left’, ‘left’, ‘right’]
Result:
[‘E.Honda’, ‘Chun Li’, ‘Ken’, ‘M.Bison’, ‘Sagat’, ‘Dhalsim’, ‘Sagat’]
— — — —
How Adrian is going to solve it:
Well first off, we start with this:
function streetFighterSelection(fighters, position, moves){ return “”;}
And here is the sort of test that our solution will be dealing with when we run the function streetFighterSelection
fighters = [ [“Ryu”, “E.Honda”, “Blanka”, “Guile”, “Balrog”, “Vega”], [“Ken”, “Chun Li”, “Zangief”, “Dhalsim”, “Sagat”, “M.Bison”]];moves = [‘up’, ‘left’, ‘right’, ‘left’, ‘left’];describe(“Solution”, function(){ it(“should work with few moves”, function(){ Test.assertSimilar(streetFighterSelection(fighters, [0,0], moves),[‘Ryu’, ‘Vega’, ‘Ryu’, ‘Vega’, ‘Balrog’]); });});
So right off the bat we know a couple of things…or rather I know a couple of things. That our fighters variable is going to be an incredibly obnoxious thing that looks like an array that I have never seen before. So I did some digging and found out that this is an example of what is known as a Two Dimensional Array.
Most arrays in JavaScript are like this:
var normalArray = [‘1’,’2’,’3’];
And you can get the first or the second value of the contents of the array like so:
console.log( normalArray[0] ); // You’ll get ‘1’console.log( normalArray[1] ); // You’ll get ‘2’
However, with a 2D Array, you’ve got a set up like this:
var 2dArray = [ [‘Mario’, ’Snake’, ‘Fox’] [‘Luigi’, ‘Otacon’, ‘Falco’]];
The row starting with Mario is considered row zero 0
The row starting with Luigi is considered row one 1
Like with normal arrays, these sub-arrays are also zero-indexed from left to right, 0 is the first, 1 is the second, 2 is the third, etc.
So if I wanted to refer to ‘Snake’ and have it display in my console. I would type the following:
console.log(2dArray[0][1]); // I select the row first, 0, and then skip over horizontally to get to 1. Then I get “Snake”.
Then if I wanted to refer to Fox’s friend, Falco, I would type the following instead:
console.log(2dArray[1][2]); // I select the row first, in this case 1, and then skip over 0 and 1 (horizontally speaking) to get to 2. And we get ‘Falco’.
So in the directions it tells us that we are going to be dealing with this two-dimensional array for Street Fighter 2 Characters. It also stipulates a few other rules:
- When at the extreme left or right positions of a row, pressing left (when at the left most), or pressing right (when at the right most) will result in you going to the complete other side of that row.
- In our example if I was on ‘Mario’ and I pressed left (when there is no left to go to) it would then give us ‘Fox’ (and vice versa).
- If we were hovering over ‘Luigi’ and pressed left (when there is no left to go to) it would give us ‘Falco’ (and vice versa).
- When at at a selection on the top row, pressing up will not do anything.
- When at a selection on the bottom row, pressing down will not do anything either.
Now we know a thing or two about Two-Dimensional Arrays, which I didn’t even know existed before today. Groovy.
Because I’m so professional, I’m going to start out with something like this:
function streetFighterSelection(fighters, position, moves){ console.log(fighters); console.log(position); console.log(moves); console.log(‘this is some crazy shit’); console.log(fighters[0][0]); console.log(fighters[0][5]); console.log(fighters[1][2]); return “”;}
When I run that, obviously nothing will pass, but I get a chance to see what the variables are that are being passed as arguments into the instance of the function being called.
We get:
fighters// [ [ ‘Ryu’, ‘E.Honda’, ‘Blanka’, ‘Guile’, ‘Balrog’, ‘Vega’ ],[ ‘Ken’, ‘Chun Li’, ‘Zangief’, ‘Dhalsim’, ‘Sagat’, ‘M.Bison’ ]]position// [ 0, 0 ]moves// [ ‘up’, ‘left’, ‘right’, ‘left’, ‘left’ ]an unprofessional comment by yours truly// this is some crazy shitfighters[0][0] //Ryufighters[0][5] //Vegafighters[1][2] //Zangief
So now I’m going to strategize with a bit of pseudo-code, basically wishlists of what I want to be able to pull off so that this all makes sense to me.
function streetFighterSelection(fighters, position, moves){
1 //make an answer array
2 //make a variable to represent the current position, this should be able to change with each move
3 //set an if/else statement or some conditional logic that prevents certain movement if the
//current position variable from the step above is trying to go up/down when it can not,
//or is trying to go left/right when at the extremes of the 2D Array.
4 //bearing the rules in mind from the conditional logic in the step above, when a shift is made in position,
//we want to update the variable of the current position, find the value in the 2D array of where that
//current position variable references, & PUSH that information into the answer array.
5 //when we’re all done with ‘moves’ we should then return the answer array.
return “”;
}
*If you want to refer to the first argument of a position variable [0, 3], you’d refer to it as position[0], whether or not variable [0,3] starts with a 0 or a 9 or whatever. For the second argument in [0,3], you’d refer to it as position[1].
So I do the following:
- Make my variable called answer that will be an array:
let answer = [];
- Create another variable called currentPosition that will also be an array equaling the position argument fed into the argument of the function we are trying to make do what we want.
let currentPosition = position;
What we have at this point:
function streetFighterSelection(fighters, position, moves){ let answer = []; let currentPosition = position; return “”;}
What we need to do now is set up some conditional logic where the moves
which when console logged look like this:
console.log(moves); // [ ‘up’, ‘left’, ‘right’, ‘left’, ‘left’ ]
We would want ‘up’ to result in currentPosition[0] to subtract 1 unless currentPosition[0] currently equals ‘0’.
We would want ‘down’ to result in currentPosition[0] to add 1 unless currentPosition[0] currently equals ‘1’.
We would want ‘left’ to result in currentPosition[1] to subtract 1 unless currentPosition[1] currently equals ‘0’ (in which case it will become ‘6’)
We would want ‘right’ to result in currentPosition[1] to add 1 unless currentPosition[1] currently equals ‘5’ (in which case its ill become ‘0’).
- I am going to create a for-loop that iterates over each element in the moves array.
- Inside the block of this for-loop, I am going to use a switch statement.
What is a switch statement? Well it’s basically a short hand version of a long form if/else statement that has a fuck ton of if/else caveats, except it’s more readable.
switch(‘horse’) { case ‘pig’: console.log(‘It is the police’); break; case ‘crip’: console.log(‘Blue stay true’); break; case ‘horse’: console.log(‘Mr. Ed says hello’); break; default: console.log(‘none of the cases matched so now you get this’);}
Basically, if the value being passed as the argument to the switch statement matches with any of the cases, then the code block following the colon after the case example get’s executed and will go on until it runs into break; If none of them match, then the code block after default: will run instead.
So back to our problem:
I’m using a for-loop to iterate over the moves array
for(let i = 0; i <= moves.length — 1; i++ ){//fun stuff coming soon}
Now for our switch statement I know the following. I know that I want moves[i] to be what is analyzed and for different things to occur to our currentPosition variable based on what moves are being iterated over from the moves array.
switch(moves[i]) { case ‘up’: if(currentPosition[0] !== 0) { currentPosition[0] -= 1; } break;//If the moves[i] turns out to be ‘up’,//if the first value of the currentPosition variable is not 0, then we will subtract 1 from the //first value of the currentPosition variable//other wise, nothing happens case ‘down’: if(currentPosition[0] !== 1) { currentPosition[0] += 1; }//if the moves[i] turns out to be ‘down’,//If the first value of the currentPosition variable is not 1, then we will add 1 to the first//value of the currentPosition variable//otherwise nothing happens. break; case ‘left’: if(currentPosition[1] == 0){ currentPosition[1] = fighters[0].length — 1; } else { currentPosition[1] -= 1 }//if the moves[i] turns out to be ‘left’,//If the second-value of the currentPosition variable is 0, then we will assign the length of a row of the fighters 2D array to currentPosition[1]. *We subtract 1 because things are zero indexed and we will have an error pop up if we don’t do that*//If currentPosition[1] is NOT 0, then we will just subtract 1 from what ever it is. break; case ‘right’: if(currentPosition[1] == fighters[0].length -1 ){ currentPosition[1] = 0; } else { currentPosition[1] += 1 }//if the moves[i] turns out to be ‘right’,//If the second-value of the currentPosition variable is the length of a fighters 2D Array row (minus 1, because zero-indexing). then we will assign the second-value of the currentPosition to be 0.//Otherwise we will just add 1 to whatever it currently is. break; default: console.log(“pay me”);//if for some reason it is neither up, down, left, or right then you should pay me.}
So we are going to place this switch statement inside of the for loop. Like so:
for(let i = 0; i <= moves.length — 1; i++ ){ switch(moves[i]) { case ‘up’: if(currentPosition[0] !== 0) { currentPosition[0] -= 1; } break; case ‘down’: if(currentPosition[0] !== 1) { currentPosition[0] += 1; } break; case ‘left’: if(currentPosition[1] == 0){ currentPosition[1] = fighters[0].length — 1; } else { currentPosition[1] -= 1 } break; case ‘right’: if(currentPosition[1] == fighters[0].length -1 ){ currentPosition[1] = 0; } else { currentPosition[1] += 1 } break; default: console.log(“pay me”); } recordSelection();}
What is recordSelection, you may ask? I am pleased to enlighten ye.
Basically recordSelection is going to push the character name that you would get by referencing the fighters array with the currentPosition variables values.
It’s a simple little function that lives right at the beginning of the main function’s block and it looks like this:
function recordSelection() { answer.push( fighters[currentPosition[0]][currentPosition[1]] );}
Then after all that we just return the answer array.
So put it all together and you get this:
function streetFighterSelection(fighters, position, moves){ function recordSelection() { answer.push( fighters[currentPosition[0]][currentPosition[1]] ); }let answer = [];let currentPosition = position;for(let i = 0; i <= moves.length-1; i++){ switch(moves[i]) { case ‘up’: if(currentPosition[0] !== 0) { currentPosition[0] -= 1; } break; case ‘down’: if(currentPosition[0] !== 1) { currentPosition[0] += 1; } break; case ‘left’: if(currentPosition[1] == 0){ currentPosition[1] = fighters[0].length — 1; } else { currentPosition[1] -= 1 } break; case ‘right’: if(currentPosition[1] == fighters[0].length -1 ){ currentPosition[1] = 0; } else { currentPosition[1] += 1 } break; default: console.log(“pay me”); } recordSelection(); }return answer;}
And ka-bam! Ya boi has dun it again, cuhz.
Now how did ryanjmack (https://www.codewars.com/users/ryanjmack) & eukavlin (https://www.codewars.com/users/eukavlin) solve it? (They’re the super smart people who gave the best answer apparently).
function streetFighterSelection(fighters, position, moves){var result = []; moves.forEach(function(move) { if (move === “up”) { position[0] = 0; } else if (move === “down”) { position[0] = 1; } else if (move === “right”) { position[1] = (position[1] === 5) ? 0 : position[1] + 1; } else if (move === “left”) { position[1] = (position[1] === 0) ? 5 : position[1] — 1; } result.push(fighters[position[0]][position[1]]); }); return result;}
So it’s pretty similar. Here are the basic steps I’m seeing here:
- We create variable called ‘result’ that is an empty array.
- We use the forEach method on the moves argument that is passed into the streetFighterSelection function. With ‘move’ being the placeholder for the particular element in moves being iterated on at the moment inside of a large chain of if/else statements:
if ‘move’ happens to be up,
- you make it only be ‘0’ which makes sense because you can’t go less than zero
Why didn’t I think of that?
if ‘move’ is down,
- you make it only be ‘1’…again
Why didn’t I think of that TOO? Grrr..
if ‘move’ is right
- We set position’s second-argument is determined with the use of a conditional/ternary operator
- If position’s second argument is 5, position’s second argument becomes 0.
- If position’s second argument is not 5, then it becomes the position’s second argument plus 1.
if ‘move’ is left
- We set position’s second-argument is determined with the use of a conditional/ternary operator
- If position’s second argument is 0, position’s second argument becomes 5.
- If position’s second argument is not 0, then it becomes the position’s second argument minus 1
We covered condition/ternary operator in the last entry, but basically it’s a shortened version of an if statement.
Regular If Statement
if(‘right’){ if(position[1] == 0){ position[1] == 5; } else { position[1] = position[1] + 1; }}
Conditional/Ternary Operator
if (move === “right”) { position[1] = (position[1] === 5) ? 0 : position[1] + 1; if yes — — → 0 : if not ===> position[1] + 1}
- After this if/else chain we push the value of the fighters 2D-Array at the reference point defined by our newly altered position argument’s values.
Moral of the Story
- You don’t always have to create new variables to deal with altering the values of an argument passed into a function.
- Conditional/Ternary operators are your friend.
- 2D arrays are weird, but could be pretty useful if you ever want to create a front-end experience like this or want to program your own fighting game menu.
And I’m out…I know I promised I would try to do 2 or 3 a day, but I’m currently in rehearsals for Don Giovanni with Opera Bend in Central Oregon. We perform on April 5, 6, and 7. I’m playing Masetto. Come see it!
Rehearsals are getting intense, so just one today. I’m exhausted, sue me. ❤️