Completed Projects
Interactive Star System Model
  • THREE.js
  • node.js
  • socket.io
  • express
  • GLSL shaders
  • Demo: Available Here

    Orbital Model:

    • Easily customizable: all planet parameters including orbit variables, color, size, and texture are stored as objects in a data file. These can also be adjusted real-time with sliders.
    • Orbits and orbit path positions are calculated each frame. The speed of orbits can be increased separately from the camera rotation speeds.
    • Level of Detail (LOD) is used to simplify sphere models when zoomed out.
    • Smart camera system automatically faces the lit side of a body when the body is selected.
    • Custom shaders are used for the glowing effect on the sun, as well as the fresnel-atmosphere effect on the orbiting bodies.

    Text Chat:

    • Synchronized with socket.io
    • Chat Box is resizable
    • When minimized, the chat box animates to signify that a user has joined the chat or a new message has been posted
    • Unique starting nicknames are automatically generated and recycled. The server uses a smart recyling system to avoid generating multiple names with the same prefix unless absolutely necessary. A historic list of generated names is stored by the server until the chat is reset
    • The chat log and stored users are reset when the number of connections hits zero
    • Nicknames are stored as cookies so that a user's nickname is remembered upon refresh or opening a new tab

    Issues Encountered:

    • I ran into a bug where certain textures crashed iOS browsers. Initially I thought this was due to the large texture size. However, it turns out that non-power-of-two dimension textures will cause the crash. This was fixed by rescaling the textures to be 512x512 or 1024x1024
    • One challenge was allowing the camera to follow a moving object while still accepting manual camera controls. The trick to managing this was to store the camera-to-target radius before any mouse-down events, then apply the radius continually while the camera is being dragged into a new position
    • I was unable to find any existing shaders for the "atmospheric" fresnel effect around the edges of planets. In the end I manually created the shaders by cacluating the dot product between the camera-to-body vector and the normal vectors for each vertex. The fresnel effect is rendered as a separate mesh directly above the main planet mesh

    Ultimate Monopoly Board Game
  • node.js
  • THREE.js
  • socket.io
  • MongoDB
  • Demo: Available Here (Note: app may be down to conserve server resources)

    This is an attempt to create an electronic version of "Ultimate Monopoly". The idea originates from here.


    The Website:

    • Users can create accounts and sign in (no verification needed for demo simplicity)
    • Customizable profile avatars based on user "level"
    • Live updating list of game rooms that can be joined
    • Page for creating a game room

    Server/Client Synchronization:

    One challenge is synchronizing server and client events. I wanted to keep these in sync without sending constant packets, which means the movement paths of tokens have to be pre-programmed as opposed to simply sending updated coordinates each frame.

    Example of a synchronized event:

    Client side queue:
    1. Move token from Boardwalk to "Go"
    2. Pause on "Go" and show animation of player gaining $200
    3. Continue moving token to Vermont Avenue
    4. Stop and show animation of player paying $6 to another player

    Server side queue:
    1. Wait the time for token to move from Boardwalk to "Go"
    2. Add $200 to the player's balance and delay the same amount of time as the client. Send update to clients so that they see the updated balance.
    3. Wait the time for the token to move from "Go" to Vermont Avenue
    4. Take $6 from the player and give it to the other player. Send update to clients so that they see the updated balance.
    5. End action and send game update to show menu again for player

    Note that the client receives this entire queue as a single packet. This allows for a smooth chain of animations and requires less bandwidth. If a client lags behind for some reason, it will "jump" forward to resync when a new animation queue is received from the server.

    The Event Chain and Javascript Promises

    In a turn-based game like Monopoly, almost all user input events occur as a chain (the main exceptions to this being trading and auctions).
    The server must not only keep track of the event chain through user input events, but also handle exceptions, inject events into the chain, and interrupt the chain when necessary.

    My first approach was using callbacks in the traditional Nodejs style. However, I quickly realized that passing callbacks to functions was cluttering the code. It also created issues with interrupting event chains. Some events immediately end a player's turn -- for example, being sent to jail. With callbacks, there was no easy way to handle this scenario without adding more and more complexity to the functions being passed as arguments to every function.

    After struggling with this for awhile, I decided to rewrite the entire application with promises. This turned out to be quite a challenge, but greatly improved the flow of the program.

    The heart of the event chain is the "menu" stage. This is where the application generates a list of possible options for the player and awaits a decision:

    var actionAfter = () => {
      result(null);
      return this.showOptions();
    }
    
    switch (input.action) {
      case 'debugRoll':
        return this.rollDice(input.value).then(actionAfter).catch(onError);
      case 'roll':
        return this.rollDice().then(actionAfter).catch(onError);
      case 'rollJail':
        // TODO separate roll function for this check
        this.inJail = false;
        return this.endTurn();
      case 'rollAgain':
        return this.rollDice().then(actionAfter).catch(onError);
      case 'move':
        return this.moveDistance(this.rollDistance).then(() => this.moved = true).then(actionAfter).catch(onError);
      case 'moveAnywhere':
        result(null); // Update immediately to hide menu
        return this.jumpTo(this.game.getTile(input.value)).then(() => this.moved = true).then(actionAfter).catch(onError);
      case 'voucher':
        result(null); // Update immediately to hide menu
        return this.useVoucher(input.value).then(actionAfter).catch(onError);
      case 'continue':
        return this.gotoNextUnowned().then(actionAfter).catch(onError);
      case 'upgrade':
        return this.upgradeProperty(this.game.getTile(input.value)).then(actionAfter).catch(onError);
      case 'downgrade':
        return this.downgradeProperty(this.game.getTile(input.value)).then(actionAfter).catch(onError);
      case 'card':
        return this.useCard(input.value).then(actionAfter).catch(onError);
      case 'endTurn':
        return this.endTurn();
      case 'declareBankruptcy':
        this.active = false;
        return this.endTurn();
      default:
        return console.log('invalid input from player ' + this.name);
    }
                            

    At this point, there are no active promises in effect. But once the player sends an input, the promise chain is started. Each possible action is wrapped in an overarching promise which includes an error handling routine. On resolve, the event chain runs to completion before returning back to the menu generation stage. On reject, the exception routine is called, usually ending the player's turn early.

    There a HUGE benefit to this setup: any unexpected exceptions that occur during this event chain (specifically, bugs in the program), will simply end the player's turn and go on to the next player's turn. Without promises, these exceptions would require individual error handlers. Without promises, javascript does not natively pass exceptions back up the callback chain.

    Socket.io Through a Reverse Proxy

    This app runs on a local server port, and is mapped to the "monopoly.connorguingrich.com" subdomain through a proxy.

    <VirtualHost *:443> ServerName monopoly.connorguingrich.com ProxyPass / https://localhost:3004/ retry=0 ProxyPassReverse / https://localhost:3004/ RewriteEngine on RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteCond %{HTTP:Connection} upgrade [NC] RewriteRule ^/?(.*) "wss://localhost:3004/$1" [P,L] Include /etc/letsencrypt/options-ssl-apache.conf SSLCertificateFile /etc/letsencrypt/live/monopoly.connorguingrich.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/monopoly.connorguingrich.com/privkey.pem </VirtualHost>


    A proxy and reverse proxy ensures a one-to-one mapping between requests to "monopoly.connorguingrich.com" and the backend port.
    One complication in this scenario is the websocket connection. By default, Socket.io will fall back to an HTTP connection if the websocket fails. This can give the illusion that the websocket is working properly. However on further inspection, you can see that the request for the websocket connection has failed.

    The websocket will still operate on HTTP only, but with increased latency and connection timeouts.

    The easiest way to correct this is to use ReWrite Engine to properly direct wss:// requests to the backend server.

    3D Gameboard
    Mobile-responsive layout
    User icon selector
    Javascript Audio Visualizer
  • javascript
  • HTML canvas
  • Demo: Available Here

    A simple audio visualizer using the Web Audio API.