A Beautiful Game

03 May 2015

If you’ve met me and talked with me for some length of time, you’re probably aware that I really like video games. I don’t just like video games - I specifically enjoy matching up against players of equal skill and playing a thrilling mental battle.

When I was young, I used to butt heads against the world’s best (matter of fact, I was the world’s best in one game).

I am sometimes asked if “that’s it.” So.. you play a video game against people?

The point isn’t the video game. I happen to prefer First Person Shooters - they are raw, real, and in your face. You are against them. Nothing else matters.

What’s lovely about video games is that it’s a very even playing field. There are certain limitations that everyone is limited by - a player model can only move 100 units/second. That applies to everyone. Now, who can do the most damage with that restriction?

What’s especially dynamic about video games is that the code isn’t perfect. Sometimes, as in Quake, you can game the system just a bit. With Quake Live, you can strafe side to side and increase your move speed. Top players glide around the map like flying squirrels from tree to tree.

But - here is a disconnect. The average Quake population has been sharply segmented by this trivia. Are you now a player capable of strafe jumping around the map, or are you slow and frequently dead?

Furthermore, is it even fun for a strafe jumper to play against someone who doesn’t possess that talent? Someone who has put the time in to learn the small subtleties of movement in the Quake universe versus some average joe who can barely walk around the map?

There’s no joy in egregious victory. It serves neither player. The beaten player is so thoroughly beaten that very little can be learned - and the winning player has learned nothing.

"”I am trying to make you understand the game,” he said. “The entire game, not just the fiddling about with stones. The point is not to play as tight as you can. The point is to be bold. To be dangerous. Be elegant.”

It is true that the unskilled player will likely pick up on productive habits of the superior player. He will likely begin to at least understand why he is losing. He may not have the technical or mental ability to counter his opponent, but he can began to fit patterns together. If I got there - I die. He always watches that corner. And aha! - a spark! Maybe I can sneak up behind him when he expects me to turn the corner. And the game commences. Perhaps the superior player sees this gambit coming and tosses it away with ease. Years of playing against tricky minds will do that to you.

Quake duels are a mixture of strategy and raw skill. As skill levels increase, accuracy levels tend to level out. Even the best players are generally going to fall in an average range of accuracy. Top gamers are assumed to have ~48-55% Rail Gun accuracy. The same logic can follow for the Lightning Gun, Rocket Launcher, etc. Again, these are all hard objects - the Rocket Launcher shoots a predictable projectile with a fixed movement speed and a definite termination point. No one has a “better” rocket launcher. No one’s rocket launcher is suffering from mechanical failures. In other words, players are free to compete in a perfectly maintained environment, where the rules are repeatable and sensible. Much like chess, a small pool of objects can result in an enormously complex game. Mechanical motions are generally not the peak of talent in video games. Wisdom, game-sense, plain old “feelings” - these are the tools of the seasoned duelist.

This isn’t a duel, but it’s illustrative. Watch the above clip once or twice. There’s no solid reason I could give you for my decision to shoot towards that door. I, of course, can offer tons of rationalizations and do some calculations that would just assert that yes, I knew that scout was coming because of existing information in my mind.

I know a few things:

The typical move-speed and movement pattern of a scout (including the timing of double jumping).

The aggressive backdooring tendencies of LD50 (medianlethaldose in the video above.) I have played with and against LD50 and was quite familiar with his play-style.

The speed of my shot - taken for granted, but I threw that shot up at the right time because I understood the dynamics of the projectile.

I spot the blue scout leaving the middle point at :07 seconds. My mental timer is primed by him leaving and I keep his presence in the back of my mind, even as I engage other players in combat.

Is the shot lucky? Fuck yeah! But it’s not as lucky as it seems at first. I won’t be the first to belabor this point - luck is when prior preparation meets opportunity.

He tapped the board with two fingers. “Any man that’s half awake can spot a trap that’s laid for him. But to stride in boldly with a plan to turn it on its ear, that is a marvelous thing.” He smiled without any of the grimness leaving his face. “To set a trap and know someone will come in wary, ready with a trick of their own, then beat them. That is twice marvelous.”

Bredon’s expression softened, and his voice became almost like an entreaty. “Tak reflects the subtle turning of the world. It is a mirror we hold to life. No one wins a dance, boy. The point of dancing is the motion that a body makes. A well-played game of tak reveals the moving of a mind. There is a beauty to these things for those with eyes to see it.”

He gestured at the brief and brutal lay of stones between us. “Look at that. Why would I ever want to win a game such as this?”

I looked down at the board. “The point isn’t to win?” I asked.

To play a beautiful game of anything requires a deep comprehension of the subject matter. To fully explore a game, both competitors must be fluent in the subtle nuances of gameplay. They must both have awareness of common strategies, tactics, and tricks. Only then does the sheen of raw skill fall away, and wise talent emerges.

There will always be someone who can outshoot you in a video game. Especially as I age, I notice that I simple cannot keep up with teenagers. They react much more quickly than me and generally have stronger aim. But I can generally dispose of people with superior aim, because I have been playing dueling games since I was a little boy.

I just recently finished reading Zen and The Art of Motorcycle Maintenance. Phaedrus’s eternal struggle with the notion of Quality mirrors my frustration to explain the feeling of game-sense.

What is game sense? It’s not something that can be defined (by me, anyway), but here are some thoughts on the matter.

In a typical game of Quake, the player is placed on a map with lots of power-ups, items, weapons, etc. Some of the power-ups are more valuable than others - these are health or armor pickups (they make the character absorb more damage, increasing your odds of winning a fight).

The player must navigate the map, duel with his enemy, and remember the spawn times of 3-4 different items. Armors spawn in 25 second intervals. Mega-health pickups spawn every 35 seconds. A competitive player will seek to establish a route in which he arrives at armor spawns exactly as they appear. A second or two late, and the enemy will snag it.

A high-level game, then, is dramatically different than any other sort of Quake. It’s as if beginners to Quake are playing by an entirely different set of rules - they engage at random, fighting all over the map. They don’t strafe jump or rocket jump or plasma hop. They don’t time armors, so their health management strategies are haphazard and shoddy. They are, of course, still playing Quake. But not the beautiful game.

Our high-level Quake enthusiasts are simply playing a different game. They swoop and swirl at dizzying speeds across the maps, gracefully skipping over armors and power-ups right when they spawn. They use their weapons as diversions, distractions, buffers. They fire rockets down corridors because they know that an armor is due to spawn in 3 seconds, and the enemy player will perhaps run into that rocket. Or perhaps the enemy player, knowing full well that the armor is up in 3 seconds, diverts to another part of the map in order to avoid confrontation.

This level of decision making can only occur when both parties have a sufficient understanding of the game. Otherwise, one player is moving with purpose and motivation while the other player, ignorant of the game mechanics, shuffles aimlessly around the map.

When a game is tight between two talented players, the feeling is palpable. Players who are being forced to think, to improvise, to change their methods to win - that is a beautiful sight.

And all of this is accomplished by the clacking of a keyboard, a mouse in the palm of a hand, and the brain of a competitor. Such rich, diverse mental battles can be emotionally draining in the same way an argument with your Significant Other leaves you feeling emotionally exhausted.

What even happens in this clip? There’s a lot of information missing, but my subconscious game-sense fills in the blanks for me. Again, I know the speed of a scout. I know that one died at the beginning of the fight. I know that scouts commonly use that window to rush the middle point. I see the enemy demoman in a defensive posture. The only time I play that defensively as demoman is when I’m trying to lure people in. I apply my own knowledge to that situation in a split second and await the trap. I back up and spam a pipe at the window. Now, instead of a scout dropping my head, I have turned the situation around, and end up with a kill.

None of this is tangible. I have no knowledge of the scout truly coming out of there. It’s simply an educated guess - my game sense. It’s an awareness of the battlefield that transcends raw data at hand. It is one of the unique things about humans - we can build very interesting heuristics to govern our behavior. Game sense is a series of heuristics mixed with reasoning, prior observation, and guessing.

A truly beautiful game pits two talented, insightful, and wise players against each other. There is a reason chess has been played throughout history, despite having the exact same pieces - the only limitations of the game are mental, and chess, at it’s very best, is simply a battle between fallible human minds. A beautiful game does not always result my victory - a beautiful game is simply one I’ve had the pleasure of participating in. Sometimes an opponent knows you better than you know yourself. There are Quake rounds where I’ve felt like I’m debating my mother - my opponent always has a snappy retort that leaves me stumbling. The purpose of the beautiful game is not to win. Winning is an afterthought, a result of the action. A beautiful game is independent of outcome or score. It is simply a feeling, an undefinable concept. But if you’ve played one, you know.

“The point,” Bredon said grandly, “is to play a beautiful game.” He lifted his hands and shrugged, his face breaking into a beatific smile. “Why would I want to win anything other than a beautiful game?”

-Patrick Rothfuss, The Wise Man’s Fear.


Bash - Get Name of Parent Directory

02 Mar 2015

For the path /foo/bar/, if the current directory is bar, and we want to get the name of the parent directory (i.e. foo)

path="$( cd "$( dirname $0 )" && cd -P "$( dirname "$SOURCE" )" && pwd )"
parentName=$(basename -- "$(dirname -- "$path")")
echo $parentName # will return parent directory's name

This resolves symbolic links (sidestepping the error where it returns “.” for the parent dir). Give it a try.


Stuxnet - A Bloodless Weapon

02 Nov 2014

Wired released a fascinating excerpt today about Stuxnet. Stuxnet defies definition, but could be vaguely defined as a disrupter of state-activity. It’s not a “weapon” per se - it hasn’t weaponized anything that we know of. It doesn’t kill people, doesn’t threaten, and is scarcely detectable. Rather than recapping Stuxnet’s penetration of Iranian nuclear facilities, which the book will do anyway, let’s talk about the implications.

The days of sending specialized covert teams for reconnaissance are not over. Modern warfare as we know it will still require the butchering of thousands of young men. Stuxnet is less of a weapon and more of a tool, but it’s a valuable learning experience for countries and companies interested in data security and penetration by malicious actors. The penetration gives us a small window into high-end corporate espionage, the likes of which are simply unimaginable to the average human being. There are programmers and hackers with elite skill sets currently in the employ of governments who desire more information.

What will warfare look like in the future? While it will still require the deaths of thousands of people, the way in which those deaths come about will be startlingly swift. In World War 1, crude reconnaissance airplanes provided huge advantage to commanders willing to use their information. In World World 2, reconnaissance planes paved the road to victory for the Allies marching towards Germany. During the Cold War, Soviet and American air forces competed in a technological race to control the skies. Witness the money continually poured into defense projects involving stealth bombers. What does this have to do with Stuxnet?

Modern militaries operate via the Internet, radio, and other communications. They need satellite imagery, terrain maps, location tracking, and much more. The cohesion and response times of the modern military are simply unparalleled at any other time in human history. Computers, programs, and networks enable this fantastic display of power and logistics.

Alexander the Great commanded roughly 47,000 soldiers and conquered much of the known world. He rampaged from province to province, smashing other armies. Today, that 47,000-strong force would be bombed to shreds as soon as it took the field. In fact, it would not take the field. It would be slaughtered while assembling.

Der Spiegel published an article on December 29th, 2013. This article illuminated a little-known branch of the NSA known as the TAO - The Office of Tailored Access Operations. This branch retains the top penetration minds in the business, and is fond of “getting the ungettable.” A former unit head states that TAO has unearthed “some of the most significant intelligence our country has ever seen.” In 2010, the TAO conducted 279 operations worldwide. Previous data would indicate that the TAO has operated in nearly every country worth penetrating. TAO specialists have accessed the president of Mexico’s email, hacked into secure European data channels, and backdoored sensitive hardware heading out-of-country.

Approximately 85,000 computers will be penetrated by NSA hackers this year. This figure does not include those being passively penetrated. The NSA uses spam, exploits, and hacks known to the general world of blackhats, but they also develop and upgrade their own proprietary penetration system, known as QUANTUM. QUANTUM takes advantage of vulnerabilities in popular applications and websites in order to exploit target systems and reveal sensitive information. QUANTUM is capable of penetrating Yahoo, Facebook, and other social services.

QUANTUMINSERT, which appears to be an extraordinarily advanced “man-in-the-middle” attack, boasts over 50% success rates using LinkedIn. The GCHQ used fake LinkedIn pages to target Belgian engineers who had access to proprietary information.

Wondering if your data is safe? It’s not. According to Der Spiegel, “the NSA has planted backdoors to access computers, hard drives, routers, and other devices from companies such as Cisco, Dell, Western Digital, Seagate, Maxtor, Samsung, and Huawei.”

Do you use encryption? Perhaps you use the popular RSA algorithm, one of the newest and most secure encryption methods ever invented. Unfortunately, the NSA paid RSA $10 million in order to secure a backdoor in the encryption. Don’t be outraged, it’s simply the cost of doing security business. The government is interested in your information, and they have an edge in that the average citizen cannot comprehend the methods with which they obtain that information.

How does all of this relate to modern and future warfare?

Controlling intelligence will become the dominant warfare aim in the near future. While wars in the past were typically dictated by money and hardware, new wars will be won and lost digitally. I would be truly interested (in an academic sense) to see two technological giants go at each other. The NSA has a national security interest in placing backdoors in foreign computers that goes beyond simple corporate and economic espionage - they may be able to shut down key enemy systems in the event of full fledged war. The long-term goal of the NSA, TAO, and organizations in that vein is ultimately securing future dominance in information warfare. Stealth bombers are useless if they can be countered electronically. Nuclear launch sites could fall victim to attacks much like Stuxnet. The U.S./Israeli alliance has (almost certainly, though it will likely never be proven) already used electronic surveillance to spy and summarily execute several key Iranian nuclear scientists.

The Internet is an incredible tool. It allows for the distribution of information on a scale never thought possible. Commanders can follow real-time progress of their soldiers on foot, view recent satellite imagery, and drop bombs with pin-point accuracy. These advanced technological capabilities all have weakness and vulnerabilities. Discovery and exploitation of these weaknesses could bring a mighty military to its knees. A pre-internet military will simply not be able to logistically compete with a fully-networked army. The future of warfare resides in controlling information, not guns.


Infinity Table Code Breakdown - Arduino

11 Apr 2014

This article showcases one of the more interesting pieces of code written for this Infinity Table.

Here’s the goal:

Random bgColor
Black bgColor

Here’s a diagram showing the layout of our addressable LEDs on the tab

​I established the LEDs that I wanted to start and end at. I also divided the table into quarters, and assigned a counter for each quarter.

You can access all of the following code on my Github.

int northEastCounter, southEastCounter, northWestCounter, southWestCounter;
int northPixel = 150; // pixel at the top of the table
int southPixel = 63;
int eastPixel = 108; // right side halfway point
int westPixel = 18;

So far, this is pretty simple. For each tick, we update all 174 LEDs. If their value falls between the starting address (northPixel and southPixel) and the counter’s endpoint, then flip those LEDs on with an RGB code.

  for (i = 0; i < strip.numPixels(); i++) {    // led update loop, this updates all 174 leds (if applicable)
    if (i == southPixel || i == northPixel) {  // always leave these on, since they're our marker for north/south
      strip.setPixelColor(i, 255, 0, 0);       // red
    }
    else if (i >= southPixel + southWestCounter && i <= southPixel && i >= westPixel) {
      strip.setPixelColor(i, 255, 0, 0);  //red
    }
    else if (i <= southPixel + southEastCounter && i >= southPixel && i <= eastPixel) {
      strip.setPixelColor(i, 255, 0, 0);
    }
    else {
      strip.setPixelColor(i, 0, 0, 0); //black
    }
  }

  strip.show();
  delay(wait);

  northWestCounter++;
  northEastCounter--;
  southWestCounter--;
  southEastCounter++;
}

The problem was jumping the awkward gap between LED 174 (the end of our strip) and LED 0 (the beginning). In order for our light to extend smoothly outwards, the loop logic would have to jump nicely from 174 to 0 and extend the counter.

Here’s how I solved that. I wrapped this all in another loop, and I created a variable named remainder there. remainder is the sum of northPixel + northWestCounter, which we then subtract the number of pixels on the strip. Look at the diagram above if you need help visualizing this.

If our remainder is greater than zero, it means that the northWestCounter has extended past 174, and we can use this knowledge to create a logical check - if remainder has a value greater than or equal to zero, it meant the counter had “jumped” back to 0. We could use this remainder to guide our updates along the rest of the strip, up until the desired stopping point (westPixel).

for (tick = 0; tick < 43; tick++) { // one full lap

    remainder = (northPixel + northWestCounter) - strip.numPixels();

    // If our remainder is positive, that means we've crossed over from LED 174 to LED 0.
    // So, we add our counter and starting position (northPixel), and subtract the number of LEDs.
    // The remainder is what needs to be added to 0 to keep the led update pushing from 0-westPixel

    for (i = 0; i < strip.numPixels(); i++) { // led update loop
      if (i == southPixel || i == northPixel) {  // always leave these on, since they're our marker for north/south
        strip.setPixelColor(i, 255, 0, 0);
      }
      else if (i <= northPixel + northWestCounter && i > northPixel ) {  // start sending a red wave out from the northPixel, +1 every tick
        strip.setPixelColor(i, 255, 0, 0);
      }
      else if (remainder >= 0 && i >= 0 && i <= remainder && i <= westPixel) {  // if the remainder is greater than 0 (i.e. we have overrun the number of LEDs in the strip)
        strip.setPixelColor(i, 255, 0, 0); // fill up to the remainder (which will be between 0-18)
      }    // this is just to handle going from LED 174 -> 0 -> 1 -> etc

      else if (i >= northPixel + northEastCounter && i <= northPixel && i >= eastPixel) {
        strip.setPixelColor(i, 255, 0, 0);
      }
    // I redacted the south side setup for brevity
    }

    strip.show();
    delay(wait);

    northWestCounter++;
    northEastCounter--;
    southWestCounter--;
    southEastCounter++;
  }
}

Handling the reversing of the effect was done by switching the counter directions: i.e northWestCounter++ became northWestCounter--.

We also made two variations of the code, one with a black background, and one with a random background color.

A little note on the background color - the “tracer” colors are generated every time a full loop is completed. These tracer color values are stored before the start of the next loop, and the value is passed to the bgColor variable. This gives our colors a smooth continuity, as it looks like a random color bursts over our previously painted work.

I’ve pasted the full code below. This will work on your Arduino Uno with the Adafruit Neopixel Library and exactly 174 LEDs on your strip, arranged in exactly the manner we have. That’s my way of saying you’re going to need to adjust this code. Use it as inspiration, not a plug and play gist.

#include <adafruit_neopixel.h>
#define PINdroite 1
#define STRIPSIZE 174
Adafruit_NeoPixel strip = Adafruit_NeoPixel(STRIPSIZE, PINdroite, NEO_GRB + NEO_KHZ800);

void setup() {
  strip.begin();
  strip.setBrightness(55);  // Lower brightness and save eyeballs
  strip.show(); // Initialize all pixels to 'off'
}

void loop() {
  connectingPixels(20);
}

int returnNumber(int number) {
  return number;
}

void connectingPixels( uint8_t wait) {
  int i, looper, tick, northEastCounter, southEastCounter, northWestCounter, southWestCounter, northPixel, southPixel, eastPixel, westPixel, remainder;
  int traceR, traceG, traceB, bgR, bgG, bgB;

  northPixel = 150; // pixel at the top of the table
  southPixel = 63;  //pixel at the bottom of the table
  eastPixel = 108;  // right side halfway point
  westPixel = 18;   // left side

  traceR = rand() % 255; // re-roll the random dice every time a loop is completed
  traceG = rand() % 255; // we need a value between 0 and 255 (to pass in as RGB values)
  traceB = rand() % 255;

  bgR = 0; //background colors
  bgG = 0; //initialize these values to whatever you want
  bgB = 0; //they are only used on the first lap. think of it as "booting up"

  // this loop controls the number of times the full sequence will run.
  // a full sequence begins with two pixels enabled in the middle of the north and south ends // the table
  // tracers are then deployed towards east and west ends of the table.
  // a sequence ends when the tracers return to their starting position and a new RGB value is generated
  // set cycle to 50 to see this effect 50 times, for example

  for (int cycle = 0; cycle < 30; cycle++) {
    for (tick = 0; tick < 43; tick++) {   // one loop

      remainder = (northPixel + northWestCounter) - strip.numPixels();

      // If our remainder is positive, that means we've crossed over from LED 174 to LED 0.
      // So, we add our counter and starting position (northPixel), and subtract the number of LEDs.
      // The remainder is what needs to be added to 0 to keep the led update pushing from 0-18

      for (i = 0; i < strip.numPixels(); i++) {   // led update loop
        if (i == southPixel || i == northPixel) {  // always leave these on, since they're our marker for north/south
          strip.setPixelColor(i, traceR, traceG, traceB);
        }
        else if (i <= northPixel + northWestCounter && i > northPixel ) {  // start sending a red wave out from the northPixel, +1 every tick
          strip.setPixelColor(i, traceR, traceG, traceB);
        }
        else if (remainder >= 0 && i >= 0 && i <= remainder && i <= westPixel) {  // if the remainder is greater than 0 (i.e. we have overrun the number of LEDs in the strip)
          strip.setPixelColor(i, traceR, traceG, traceB);                         // fill up to the remainder (which will be between 0-18)
        }                                                                         // this is just to handle going from LED 174 -> 0 -> 1 -> etc

        else if (i >= northPixel + northEastCounter && i <= northPixel && i >= eastPixel) {
          strip.setPixelColor(i, traceR, traceG, traceB);
        }
        else if (i >= southPixel + southWestCounter && i <= southPixel && i >= westPixel) {
          strip.setPixelColor(i, traceR, traceG, traceB);
        }
        else if (i <= southPixel + southEastCounter && i >= southPixel && i <= eastPixel) {
          strip.setPixelColor(i, traceR, traceG, traceB);
        }
      }

      strip.show();
      delay(wait);

      northWestCounter++;
      northEastCounter--;
      southWestCounter--;
      southEastCounter++;
    }

    for (tick = 0; tick < 43; tick++) { // this loop reverses the tracer, filling the background color behind it

      remainder = (northPixel + northWestCounter) - strip.numPixels();

      // If our remainder is positive, that means we've crossed over from LED 174 to LED 0.
      //  So, we add our counter and starting position (northPixel), and subtract the number of LEDs.
      // The remainder is what needs to be added to 0 to keep the led update pushing from 0-18

      for (i = 0; i < strip.numPixels(); i++) {  // led update loop
        if (i == southPixel || i == northPixel) { //always leave these on, since they're our marker for north/south
          strip.setPixelColor(i, traceR, traceG, traceB);
        }
        else if (i <= northPixel + northWestCounter && i > northPixel ) {         // start sending a red wave out from the northPixel, +1 every tick
          strip.setPixelColor(i, traceR, traceG, traceB);
        }
        else if (remainder >= 0 && i >= 0 && i <= remainder && i <= westPixel) {  // if the remainder is greater than 0 (i.e. we have overrun the number of LEDs in the strip)
          strip.setPixelColor(i, traceR, traceG, traceB);                         // fill up to the remainder (which will be between 0-18)
        }                                                                         // this is just to handle going from LED 174 -> 0 -> 1 -> etc

        else if (i >= northPixel + northEastCounter && i <= northPixel && i >= eastPixel) {
          strip.setPixelColor(i, traceR, traceG, traceB);
        }
        else if (i >= southPixel + southWestCounter && i <= southPixel && i >= westPixel) {
          strip.setPixelColor(i, traceR, traceG, traceB);
        }
        else if (i <= southPixel + southEastCounter && i >= southPixel && i <= eastPixel) {
          strip.setPixelColor(i, traceR, traceG, traceB);
        }
        else {
          strip.setPixelColor(i, bgR, bgG, bgB);
        }
      }

      strip.show();
      delay(wait);

      northWestCounter--;
      northEastCounter++;
      southWestCounter++;
      southEastCounter--;
    }

    northEastCounter = 0;
    northWestCounter = 0;
    southEastCounter = 0;
    southWestCounter = 0;

    bgR = returnNumber(traceR);
    bgG = returnNumber(traceG);
    bgB = returnNumber(traceB);

    traceR = rand() % 255;  // re-roll the random dice every time a loop is completed
    traceG = rand() % 255;
    traceB = rand() % 255;
  }
}

“That’s it!” :)

Here are some more pictures of the finished product.