ColbyDeHart.com :: Click for info about me

My name is Colby DeHart. I live in Nashville, Tennessee. I love dogs, music, programming, bikes, all games, solving problems, and learning new things. Feel free to get in touch!

 

Redux Side Effects In 12 to 16 Lines :: Permalink - 07 Jan 2017

I’ve been thinking (and perhaps overthinking) a bit about my redux workflow. Specifically how to handle side effects, such as async requests. I have used redux-thunk and redux-saga in the past. While they solve the problems of async redux well, something never felt quite right and I couldn’t put my finger on it.

Last week I came across this article on Mark’s Dev Blog that made me realize why I don’t like these solutions. This, along with using Elm for the last month or so, made me seek out a simpler solution. I got turned onto redux-loop which was closer to what I wanted but was a bit bulky and also allows batching actions, which I see as not so great (see this tweet). So I started writing a blog post titled…

Redux Side Effects Middleware in 12 lines

I was so young at this point. So foolish and bright-eyed. I posted this untested snippet into slack at an attempt to handle async actions like Commands in elm. Here’s the (totally nonsense) code.

const cmdMiddleware = store => next => action => {
  const res = next(action)
  if (!Array.isArray(res)) return res
  let [ state, command ] = res
  if (typeof cmd === 'function') {
    Promise.resolve(command()).then(a => store.dispatch(a))
  } else if (Array.isArray(command)) {
    let [cmd, ...args] = command
    Promise.resolve(cmd(...args)).then(a => store.dispatch(a))
  }
  return state
}

You’ll spot my error pretty quick. I forgot what the return value of next is in a middleware, which is just the returned action and not the updated state, also the return value of this function has no bearing on state.

The middle of this function (lines 3-10) were where I was on the right track. I wanted to be able to dispatch actions that were one of three things:

  • state - the updated state, just like normal
  • [state, cmd] - the updated state and a command, which will return an action to be dispatched, possibly async through a promise
  • [state, [cmd, ...args]] - same as before, but the cmd and args to be pass to it in an array.

I still needed to figure out how to intercept the actual reducer though and not the dispatch function. With great hubris, i titled a new blog post

Redux Side Effects Enhancer in 16 lines

Here I actually made an example application using create-react-app and tried a few things here, but then found out about the store’s replaceReducer and got pretty close

const cmdEnhancer = createStore => (reducer, preloaded, enhancer) => {
  const store = createStore(reducer, preloaded, enhancer)
  store.replaceReducer((state, action) => {
    const next = reducer(state, action)
    if (!Array.isArray(next)) return next
    const [ newState, command ] = next
    if (typeof command === 'function') {
      Promise.resolve(command()).then(a => store.dispatch(a))
    } else if (Array.isArray(command)) {
      const [ cmd, ...args ] = command
      Promise.resolve(cmd(...args)).then(a => store.dispatch(a))
    }
    return newState
  })
  return store
}

Keyword here is pretty close. I loaded this enhancer into my simple application and it worked! I could return commands in my reducer that would get fired off. Everything worked exactly as expected. I even began to publish my blog post and begin to enjoy the rest of my weekend when I saw the error.

What happens when you use combineReducers or reduceReducers or anything that a normal person using redux would use? This enhancer assumes that you have a single reducer that returns one of the three possible return types. I fiddled with the enhancer and shut my laptop case. It was too complicated to do in any lines of code worth bragging about. That is until I changed the title the second time.

Redux Side Effects in 14 Lines

I came back and discarded enhancers and middlewares. I realized that I needed access to all of the user’s reducers to make this actually work. And the only place I thought of to do that would be in the reduceReducers function. And then I came up with this.

const reduceCommandReducers = (reducers, store) => {
  return (state, action) => reducers.reduce((s, r) =>{
    const next = r(s, action)
    if (!Array.isArray(next)) return next
    const [ newState, command ] = next
    if (typeof command === 'function') {
      Promise.resolve(command()).then(a => store.dispatch(a))
    } else if (Array.isArray(command)) {
      const [ cmd, ...args ] = command
      Promise.resolve(cmd(...args)).then(a => store.dispatch(a))
    }
    return newState
  }, state)
}

This works with multiple reducers. All the async actions dispatch just as expected. You can achieve a similar approach with combineReducers as well, I just was uninterested in doing it. The part of this that is strange is that you have to reduce reducers after you create your store and then use the replaceReducer function like so

const store = createStore(state => state, {}, enhancer)
store.replaceReducer(reduceCommandReducers([...reducers], store))

This makes sense because you have to give your reducer access to dispatch to let it produce more actions. This goes against a lot of the main ideas of redux, but this pattern is inherently such.

All of this goes with the same caveats in redux-loop. Is it a good idea? Maybe. Does it put side effects in your reducers? absotively. I just wanted to see if I could get a reasonable approach to async actions in an afternoon and learn a bit more about enhancers and the createStore function.

I have put up a repo that uses this function just to show that it works for a simple use case. It is probably broken. It probably doesn’t play well with other middlewares and reducers. It most likely introduces some strange race conditions. I did not test it and won’t. The reason is that I had already figured out how to do all of this much more simply.

Redux Side Effects Middleware in 12 Lines: Redux

I forgot to mention my very first attempt at this was a middleware that put the commands in the action creators and not the reducer, which was much simpler and did not break the core tenets of redux. You would basically dispatch an [action, cmd] pair instead of just an action to get the same effect.

const cmdMiddleware = store => next => action => {
  if (!Array.isArray(action)) return next(action)
  const [ act, command ] = action
  const res = next(act)
  if (typeof command === 'function') {
    Promise.resolve(command()).then(a => store.dispatch(a))
  } else if (Array.isArray(command)) {
    const [ cmd, ...args ] = command
    Promise.resolve(cmd(...args)).then(a => store.dispatch(a))
  }
  return res
}

This approach is probably better. You don’t have to put side effects in reducers. Putting async bits in action creators isn’t too far off from thunks/sagas that folks are already used to. Also, it is 12 lines, which means I wouldn’t have had to change my post title. Three times.

Close

Overcoming Bookmarking Syndrome in the New Year :: Permalink - 01 Jan 2016

I save a lot of bookmarks for tech, Like, a lot. I went through all of my saved tutorial bookmarks, YouTube ‘Watch Later’ videos, Udemy courses, Instapaper feed, and unread tech books on my kindle and calculated that I have about 90 hours of learning content that I have on queue.

This list has been getting out of control for a while now. A hacker news article saved in Instapaper, a 2 hour conference talk posted on twitter gets added to ‘watch later’, someone in slack mentioning a new technology gets thrown in my haphazardly labeled TECH STUFF bookmark folder. It’s really easy to do this, but the more I do it, the larger the queue gets, the more intimidating it gets and, sadly, the less likely I am to even try to whittle down this goliath.

My first action of the new year, this morning, was to catalog every nook and cranny of this mountain, filter out things that aren’t relevant or that I probably never cared about in the first place (Still not certain why I bookmarked a digital signal processing library in Haskell). After this, I set a goal: two months. Any longer and the amount of new tech I would want to learn would clutter up my bookmarks again and create the same problem, any briefer and the effort per day would be too unpalatable.

In two months by doing about an hour and a half a day, I can get through learning Docker, figuring out org-mode, SICP, finally understanding what the heck machine learning is, two Elixir books, building an operating system in Rust data science for python, Rich Hickey’s apparently amazing talk that I still haven’t had time for, and about 20 other interesting articles, videos, and tutorials.

An hour and a half a day isn’t easy. Some days you don’t have that. Some days you don’t feel like it. Some times you forget that you should only focus on this iliadic Bloomberg article and not look up and fret over the mountain that you have set out to climb. I think I can handle it though. I keep my Sundays free and can catch some of the slack of fall-behind days through the week. I’m setting a recurring event in my calendar and plan on batching out what articles and tutorials I am going to get through each week. And I want to handle it because it is important to me.

It is important because I really want to keep learning. I want to learn new languages. I want to learn how to build an AI that plays Street Fighter. I want to build my own digital synthesizer. I want to never again be bamboozled by what git command I want. I want to learn to make new things and how to distribute and deploy them. And I don’t want to get scared by the amount of stuff I want to learn.

I feel this ‘bookmarking-syndrome’ puts too much confidence on a mythical ‘one day’ and is a poor coping mechanism for dealing with information overload. Maybe something like clearing out a bunch of bookmarks and a video playlist seems trivial, but for me right now it’s kind of important that I don’t keep forming a habit of being so overwhelmed by all that I don’t know that I don’t even try to learn, and instead start clearing off my feeds, tinkering around with new tools, and grokking in the new year.

Close

Remote PHP Debugging in Vim :: Permalink - 19 Nov 2015

I love vim. Most people who use vim feel the same. It feels pure and simple. The commands make sense (after you learn them) and everything is configurable through plaintext files. It’s not for some people but for me it’s everything I need. Well, almost everything I need.

I tend to get envious of an IDE’s integrated debugger when I really need it, so I went searching for how to get the same functionality in my vim set up. I quickly found VDebug which also seems to be the only useful plugin for debugging in vim. I’m going to quickly walk through my setup for PHP debugging in vim (You can also use it for ruby, node, perl, and python though I have not tried these yet)

First, you need to install the plugin and configure a few settings. I have recently switched to neovim and have replaced Vundle with Vim-Plug, so my setup looks like this.

Plug 'joonty/vdebug'
let g:vdebug_options = {}
let g:vdebug_options["port"] = 9000
let g:vdebug_options["break_on_open"] = 0

I found that I had to initialize the options dictionary or I ran into problems assigning properties. I set the port to 9000 and turned off the break_on_open setting so that it doesn’t break on the first line. I use vagrant and a virtual machine to do my PHP development so I need to tell vim how to map from my home filesystem to the virtual machine’s. I have a line later on in my .nvimrc which sources a local config file so I can use project specific settings.

"Local Vimrc
if filereadable("./.lnvimrc")
    execute "source ./.lnvimrc"
endif

So in my php project i have a .lnvimrc that looks like this

let g:vdebug_options["path_maps"] = {
\    "/vagrant": "/Users/colby/Code/project-directory"
\}

You will need to change the path to your project of course. Just to be clear, that is the location of the project on my virtual machine on the left and my host machine on the right. Okay, cool that’s all you need on the vim side of things. You will now need to set some things up on the php side of things.

So ssh into your VM and install XDebug. This is the php module that will allow remote debugging. On an ubuntu box, simply running sudo apt-get install php5-xdebug should be good enough. You need to go to the xdebug site for instructions for your particular distro. This should automatically create an file at “/etc/php5/conf.d/xdebug.ini” which you will need to add the following to.

zend_extension=/usr/lib/php5/20090626/xdebug.so
xdebug.remote_enable=on
xdebug.remote_handler=dbgp
xdebug.remote_host={your.host.ip.address}
xdebug.remote_port=9000
xdebug.remote_connect_back=on

The zend extension part should be autopopulated, don’t copy the one above because the location of your .so file could be different depending on your version of php. You need to put your host machine’s IP address in the remote_host parameter. You can get this just by running ifconfig (or ipconfig on Windows). Now you should be ready to debug!

You can press <F10> to toggle a breakpoint in your code and then press <F5> to start the debugger, which will wait for 20 seconds for a connection. You will need to send a special signal in your request to tell PHP to start debugging. You can download a Chrome XDebug Helper plugin to toggle this, or just send a query string parameter of XDEBUG_SESSION_START=1 in your request. After this, you should have a debugging window pop up in your editor and you can see the VDebug docs for instructions on how to run through the script and evaluate code. Happy debugging!

Close

Make console apps with node :: Permalink - 24 Apr 2015

So you’ve got an idea for the next Ack, but you don’t know how to write console applications! No worries, you can write console applications with JavaScript and publish them to npm pretty easily. I recently did this with a project called sfold which allows you to quickly scaffold files and folders for a project.

##Setup

First you need to make an empty directory and run npm init. If you’ve never done this, it simply sets this directy up to hold a node project and will create a package.json file. For the rest of this tutorial, let’s assume we want to make a console application called salute which takes in a name and then prints to the console “Hello, your_name”.

Let’s now make a main.js file which will hold our application. This will be the main file for our console app. These are the full contents of the file.

#!/usr/bin/env node
'use strict';

console.log('Hello ' + process.argv[2]);

The first line is a shebang which says that we should use the node program to run this script. Then we just print to the console the string “Hello “ and the 3rd argument. The reason we want the 3rd is because when you call this from the command line using npm, the first argument will be ‘node’ and the second will be the absolute path to your main.js file so that when calling salute colby, the 3rd argument is actually ‘colby’.

##Running it

Now we need to edit the package.json a little bit. Delete the property called ‘main’ and add one called ‘bin’ which should look like this.

{
    "name": "salute",
    ...
    "bin": {
        "salute": "main.js"
    },
    ...
}

The bin attribute contains key-value pairs where the key is the name of the command called from the command line, which we want to be salute. If you wanted to call your application by typing ‘say_name’, you would change salute to that here. And the value is the location of the script that will be run, which is just main.js for us.

Now we need to hop back into your terminal. To test this, first we need to link this package, which will allow you to run it locally. just run npm link. Now your app should be linked to your system so you can just run salute colby and it will print out “Hello, colby” back to you. Great! now we need to publish it.

##Publishing

If you haven’t already, you need to go to the npm website and register an account. Then from your terminal you can login with npm login with your credentials. After that, all you have to do is run npm register ./ and your application will be publically available. All people will have to do is run npm install --global salute (or whatever you name your app), and they can use your awesome command line application!

Close

When v.1.0 :: Permalink - 05 Apr 2015

So I’m finally fully ready to announce When V.1.0 is ready! When was my first capstone at Nashville Software School and is a group activity planner. It’s powered by Firebase with an Angular frontend and you can go ahead and log in and use it here

Basically the idea is that you login and can create events for groups. You pick a name and a time range when the event can possibly happen and the app generates a link. You can send the link to whoever you want to attend. They put in their name and email and then edit their availability on a calendar widget. Then you, the creator of the event, can view the merged calendar of everyone’s availability. In the case where there is no possible way that every participant can attend the event, the app will sort the participants by busyness and then find the most optimal number of participants.

Feel free to give it a spin and if you have any issues, you can submit an issue on the GitHub repo or put a comment below.

Close

Functional JavaScript with Lodash :: Permalink - 08 Mar 2015

EDIT: I’ve redone my whole website since this post, so the game is no longer on here, but you can check it out by looking at the code.

I’ve been getting into breaking functions down into smaller chunks and write more functional type JavaScript. This was prompted by wanting to learn and utilize lodash better as well as I have been teaching myself Python, which highly values collection manipulation and more compressed, functional type methods. So inspired by pythonic coding, I wrote a the classic game snake in Javascript with lodash.

You can see the game here. I will be referencing it throughout the rest of the post. Now this post title is a bit misleading, the code I wrote isn’t super functional, but some parts of it do emphasize how utilizing set theory can help write more concise code. Whatever, let’s have a look.

First a super simple example, When the user presses a key on the page with snake, I want to act upon it if it is an arrow key and return early otherwise. This is very easy in lodash.

$(window).on('keydown', function(e){
    if(!_.contains([37, 38, 39, 40], e.keyCode))
        return;
    //38, 37, 40, 39 : up, left, down, right

_.contains is a lodash function that takes in an array and an item and returns true if the array contains the item. So if the array [37, 38, 39, 40] (The character codes of arrow keys) does not contain the keyCode of the event, return early. This is much simpler than checking for each key with equality. Alright, more complicated/cooler example now.

My game of snake is based on a 16x16 grid. The snake and apple are just a collection of x,y coordinates. I also keep track of the head of the snake and the direction in a dir variable which is an x,y vector so if the snake was moving up, the dir would be [0,-1] (Move horizontal 0 and vertically upwards 1)

Whenever the snake moves I have to see if the snake dies and restart the game. Here is the code for that.

var head = snake[snake.length-1],
    next = head.map(function(el, i){return el + dir[i]});
if( _.any(next, function(val){return val < 0 || val > 15}) ||
    _.any(snake,function(val){ return _.isEqual(next, val) })){
    //Kill that snake

So first I get the next position the snake will be moving to by mapping the position of the head of the snake with the dir vector (in the map function, el is the coordinate and dir[i] will be the corresponding vector direction)

Next I check and see if the snake is about to go off the map. So the map is 16x16 with coordinates from 0 to 15, inclusive. So I use lodash’s any method to see if either of the coordinates of next are greater than 15 or less than 0. _any will return true if any item in the collection satisfies the condition in the function, which makes sense.

Then I have to find out if the snake has run into itself, which would end the game. This is a bit more complicated because I have to make sure the next coordinate is not equal to any of the snake’s body part’s coordinates, but lodash makes this easy.

_.any(snake,function(val){ return _.isEqual(next, val) })

lodash’s isEqual gives us a deep equals so we can compare arrays which is just awesome. With that known, it almost reads like English. If any item in snake isEqual to the next coordinate, return true. Okay, one more example.

function styleAt(x, y){
    if(_.any(snake, function(e){return _.isEqual(e, [x,y])}))
        return '#74D13D';
    if(_.isEqual(apple, [x,y]))
        return '#ED9898';
    return '#ccc';
}

This function is for coloring each cell on the canvas, I pass in an x and y, and the function returns green if it is a snake cell, red if it is the apple cell, or grey if it is empty. The first if statement checks to see if any element, e in the snake isEqual to the [x,y] coordinate.

Hopefully these examples give you a few ideas on how you can use lodash and JavaScript’s built in collection methods like _map and reduce. Set theory posits that through simple functions like these that any one collection or set of data can be transformed into any other set of data. That makes them very useful and I would encourage everyone to make use of them. It will make your life much easier.

Close

Writing Node Scripts :: Permalink - 24 Feb 2015

You can write simple scripts in your package.json for your projects that will run simple commands like jshint *.js or karma start, but you can also write your own js scripts and run them with node so that npm run new_post will run node ./scripts/new_post.js for anything you need.

Here is the simple script I wrote to make a new post in Wintersmith.

var fs = require('fs'),
    prompt = require('prompt'),
    path = require('path'),
    changeCase = require('change-case');

prompt = prompt.start();

prompt.get(['title'], function(err, result) {
    var title = result.title
        cleanTitle = changeCase.snake(title); 

    fs.mkdirSync('contents/articles/' + cleanTitle);

    var content = '---\n';
    content += 'title: "' + title + '"\n'
    content += 'author: colby-dehart\n'
    content += 'template: article.jade\n'
    content += 'date: ' + printDate() + '\n'
    content += '---\n'

    fs.writeFileSync(
        'contents/articles/' + cleanTitle + '/index.markdown',
        content
    );
});

function printDate(){
    var d = new Date(),
        res = '';

    res += d.getFullYear() + '-';
    res += (d.getMonth()+1) + '-';
    res += (d.getDate()+1);

    return res;
}

I prompt the user (myself) for a title, then create a directory that is named the title in snake case. Then I create some Yaml front matter for the post and write it in a file in the new folder called index.markdown. I have this script loaded in a folder in my root named scripts and then in my package.json I have.

"scripts": {
    "new_post": "node bin/new_post"
}

Now whenever I want to make a new post, i just run npm run new_post and I am prompted for a title and all of the directory making and front matter generation is handled for me. Using this method is great for one-off tasks that wouldn’t necessarily make sense in an automated task runner like gulp.

Close

Testing Angular Apps pt.2 :: Permalink - 20 Jan 2015

Back again with pt. II of my Angular testing post. This time I will show you how to create tests for controllers, generate a new controller for each test, and how to test http requests. Alright, lets get right into it.

So here is the controller we are going to be testing. It gets a name from our location and handles posting a new calendar then redirect.

So let’s see how we want to get our controller into our tests. Now, what we actually want is a function which creates a custom scope based on what we need to test, but this isn’t too hard in angular.

//new.spec.js
Describe('New Calendar', function(){
  beforeEach(module('new'));
  var calFn, http, scope, loc;
  beforeEach(inject(function($controller, $httpBackend, $rootScope, $location) {
      http = $httpBackend;
      scope = $rootScope.$new()
      loc = $location;
      calFn = function(scp){
        return $controller('NewController', {$scope: scp})
      };
}));

So here we have injected $rootScope into the variable scope, which will let us make new scope objects on the fly. We also made a function which uses Angular’s built-in $controller service so we can initialize the controller with a different scope per test. let’s make sure we can test grabbing the name of the new cal out of the location first.

it("Should pull the name from the location.", function () {
  loc.search({name:"Hello"});
  calFn(scope);
  expect(scope.event.name).to.equal("Hello");
})

In the controller, we grab the name from the query string and set it to the event object’s name. This is a great example of why we needed a function that makes our controller, instead of initializing it in the beforeEach. We need to be able to initialize the location before th controller is instantiated. Then we call calFn which makes our controller and changes scope.event based on the location we just made.

This is all good, but pretty simple. What if we want to test something more complicated, like http requsts? Here we use $httpBackend to spoof a server and make sure our controller is sending out the POST request.

it("should post to server on scope.createEvent", function () {
  loc.search({name:"Hello"});
  calFn(scope);
  http.expectPOST('http://some.url', { name:'Hello', participants:{} })
  scope.createEvent;
})

We initialize the location and controller in the same way again, but then we use $httpBackend with the expectPOST request. It takes in the arguments of what URL we are expecting and the data that it is expecting us to post. We then create the event and the $httpBackend will automatically assert our test for us.

This is a pretty example, $httpBackend can accept all kinds of requests, respond with custom values or error, and has pretty good documentation. From here you should be able to test all your controllers basic functionality and your http request.

Close

Testing Angular Apps pt.1 :: Permalink - 06 Jan 2015

Recently I’ve been diving into developing applications with AngularJS. I’ve been building my capstone project for NSS with it and it has been going great. At first I had a few hiccups trying to test it, but Google has made Angular easy to test once you figure out the basics of injecting your modules into your tests.

I’m going to assume you’ve set up karma and mocha and focus on how to use Angular in your tests. if you haven’t there are many tutorials out there to show you how to set up a test environment and you can also check out my GitHub repo for an idea of how I have mine configured.

So here is the factory I am going to test. I’m started with a factory because angular controllers are a bit more complicated to test, but I will go over them in part 2 of this.

This factory handles creating and merging calendars for participants of a group event. Now to test it, we need to include this module in our test. This is pretty easy as long as you have included angular-mocks in your Karma config.

//calFactory.spec.js
Describe('Calendar Factory', function(){
  beforeEach(module('calFactory'));
  ...

So here we use the function module provided my angular-mocks to inject the module. now we need to use Angular’s injector to give us our actual factory.

var $cal;
beforeEach(inject(function(cal) {
  $cal = cal;
}));
it("should exist", function(){
  $cal.should.exist;
});

The inject function will give us access to the Angular injector, just like in the initialization function of a controller or factory. Here we inject cal, which is our factory and assign it to a variable that we can use in our tests. Then our first test just makes sure we have successfully injected our factory. This test actually does more than just that. If our module breaks or our factory is unable to be built, then the test will fail. A simple test like this for all of your modules will be able to quickly alert you if your app breaks. But we want more than that, so let’s test some functionality.

function createCal() {
  var firstDay = new Date(),
      firstDayHolder = _.cloneDeep(firstDay),
      nextWeek = new Date();
  nextWeek.setDate(nextWeek.getDate()+7);
  return $cal.newCal(firstDay, nextWeek);
}

var calendar;
beforeEach(function() {
  calendar = createCal();
});

it("should create calendars", function(){
  calendar.length.should.equal(7);
  calendar[0].should.have.keys('noon', 'morning', 'night', 'date');
});
it("should be able to make date objects", function(){
  var dates = $cal.convertDates(calendar);
  dates[0].should.be.an.instanceOf(Date);
});

So I make a utility function to create a week long calendar with my factory. Then I test that it creates the proper lengt calendar with the correct keys. Then i check the convertDates method correctly gives me Date objects.

This is a fairly simple example, but shows how to inject Angular into your tests. I will post a second part to this post that will show how to inject controllers properly and also how to handle async code.

Close

DOM Collision Detection :: Permalink - 04 Dec 2014

I was working on a recreation of the Hasbro board game Battleship in the browser. I had to check and make sure that when I was placing my ships in the game that they could not overlap, therefore had to figure out collision detection in the DOM.

This is the function in its entirety.

function willCollide(block) {
  var top = block.offset().top,
      left = block.offset().left,
      right = left + block.width(),
      bottom = top + block.height(),
      result = false;
      
  $blocks.each(function(i, el){
    if($(this).attr('id') !== block.attr('id')){
      var blockLeft = $(this).offset().left,
          blockTop = $(this).offset().top,
          blockRight = shipLeft + $(this).width(),
          blockBottom = shipTop + $(this).height();
      if (!(top >= blockBottom || bottom <= blockTop ||
          left >= blockRight || right <= blockLeft)){
            result = true;
      }
    }
  });
  return result;
}

This function takes in a block, which is just a DOM element (I’m using jQuery here). My blocks are just divs in this case. willCollide will return true if the piece is colliding with another block.

The first few lines get the top, left, right, and bottom edges of the block and set them to variables using the element’s offset and dimensions.

After that, I’m looping through each block element in $blocks. Each block has a unique id, so the if statement will prevent me from checking my main block against the others. If it is a unique block, I get the top, left, right, and bottom exactly how I did at the top of the function. Then it gets a little weird with this if statement:

if (!(top >= blockBottom || bottom <= blockTop ||
    left >= blockRight || right <= blockLeft)){
      result = true;
}

Okay, so lets break this down. Since I’m comparing two blocks and saying ‘block’ over and over again is going to get confusing, i am going to call the main block that was passed into the function the mainBlock and the block that is currently being compared in the for loop the currentBlock. Alright.

top >= blockBottom || bottom <= blockTop

Keep in mind that height increases as it goes down in DOM coordinates. So we see if the top of the mainBlock is lower than the bottom of the currentBlock or if the bottom of the mainBlock is higher than the top of the currentBlock. If either of these are true, than there is no way that the blocks could overlap each other vertically, but we don’t know anything about horizontal collision. That’s where the next line comes in.

(!(top >= blockBottom || bottom <= blockTop ||
left >= blockRight || right <= blockLeft))

The left and right check do the same as the top and bottom then we wrap the whole conditional up in parenthesis a negate it, basically saying, ‘If there is no way that these two elements are not going to collide, then they collide’. Then we set the result accordingly and return it at the end of the function. It looks a little convoluted, but after you get a hang of the parts it is actually pretty simple.

Close