Showing posts with label Programming. Show all posts
Showing posts with label Programming. Show all posts

Sunday, February 8, 2015

Setting up a Node.js server in Cloud9 that can communicate with clients via websockets

This one took me a while to figure out, so I figured I would share it here.
  1. Make an account on Cloud9 (if you don't already have one), and log in.
  2. Go to the Cloud9 homepage and click on Dashboard in the top right corner.
  3. Click on Create New Workspace, the green button to the right, then Create a New Workspace, (not Clone From URL).
  4. Name your workspace, set Hosting to hosted, click on Node.js in the menu below, then click Create.
  5. You'll be redirected to your Dashboard. On the left you will see your project with a loading thing next to it. Wait for your project to be created. It might help to refresh the page as well.
  6. Click on your project, then press Start Editing.
  7. Click on Window at the top, then press New Terminal
  8. Type in the terminal "npm install --save ws". See ws for more information on this Node.js websocket library.
  9. Double click on Server.js in the navigation bar to the left, then paste in the Server.js code below.
  10. Press Run, the green arrow at the top of the screen.
    • The server starts via the current file that you have open, so make sure Server.js's contents are displayed before pressing run.
    • If you accidentally run when selecting, say, an .html file, an Apache server will start up. 
    • If this happens, stop the server, then go to your terminal and type "/etc/init.d/apache2 stop". This is needed because the Apache server keeps running in the background otherwise.
    • Then when you go and start your server again after having Server.js's contents displayed, it will blow up. Stop the server and restart it, and then it will work fine. No idea why this happens but restarting it seems to work.
    • Sometimes Cloud9 claims it "lost connection" and everything freezes up. Simply refresh the page and this should fix it. I think this has to do with internet browsers trying to "optimize" by not letting Cloud9 do some stuff when it's not the active tab.
  11. Open http://jsfiddle.net/wfvok3bd/, and modify :"ws://appName.userName.c9.io/" to your domain name, which you can find in the console below in Cloud9 once you press Run. Specifically you will see something like "Your code is running at https://appName.userName.c9.io/." You might need to scroll up to find this. Make sure that you specifically put "ws://appName.userName.c9.io/" with the / at the end.
  12. Run the jsfiddle, and it should open, send "yo wazzup" to the server, and the server will echo this message and send a "yo wazzup" back to the client. You should the messaged received on the server console and in the jsfiddle output box.  Then you are done.
Server.js code:
var WebSocketServer = require('ws').Server
  , wss = new WebSocketServer({ port: process.env.PORT });



wss.on('connection', function connection(ws) {
  console.log("connection opened");
  ws.on('message', function incoming(message) {
    console.log("connection has message: " + message)
    ws.send(message);
    
  });
  ws.on('close', function closeSocket() {
    console.log("connection closed");
  });
  ws.on('error', function socketError() {
    console.log("connection has error");
  });
});
Alternatively:
var WebSocketServer = require('websocket').server;
var http = require('http');

var server = http.createServer(function(request, response) {
    console.log((new Date()) + ' Received request for ' + request.url);
    response.writeHead(404);
    response.end();
});
server.listen(process.env.PORT, function() {
    console.log((new Date()) + ' Server is listening on port 8080');
});

var wsServer = new WebSocketServer({
    httpServer: server,
    autoAcceptConnections: false
});

function originIsAllowed(origin) {
  // put logic here to detect whether the specified origin is allowed.
  return true;
}

wsServer.on('request', function(request) {
    if (!originIsAllowed(request.origin)) {
      // Make sure we only accept requests from an allowed origin
      request.reject();
      console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.');
      return;
    }

    var connectionAccepted = false;
    try {
        var connection = request.accept('echo-protocol', request.origin);
        connectionAccepted = true;
    } catch (e) {
        console.log("Invalid protocol given");
    }
    if (connectionAccepted)
    {
        console.log((new Date()) + ' Connection accepted.');
        connection.on('message', function(message) {
            if (message.type === 'utf8') {
                console.log('Received Message: ' + message.utf8Data);
                connection.sendUTF(message.utf8Data);
            }
            else if (message.type === 'binary') {
                console.log('Received Binary Message of ' + message.binaryData.length + ' bytes');
                connection.sendBytes(message.binaryData);
            }
        });
        connection.on('close', function(reasonCode, description) {
            console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
        });
    }
});
The second one is from here. I'm listing it as well because I can't seem to get ws working with http servers, while websocket does.

Note that you will need to type "npm install websocket" into the terminal before use.

A protocol is also used in this example code, so you will also need to modify
new WebSocket("ws://appName.userName.c9.io/");
to
new WebSocket("ws://appName.userName.c9.io/", "echo-protocol");
see http://jsfiddle.net/jraqx1ye/.

There is nothing special about a protocol except that all incoming connections that aren't using this protocol will be ignored by the server (and it will log "Invalid protocol given").

Here's an example of how to make a chat server:
// Remember that we can't simply log connection because I guess internally it has a reference to itself, see http://stackoverflow.com/questions/4816099/chrome-sendrequest-error-typeerror-converting-circular-structure-to-json

var WebSocketServer = require('websocket').server;
var http = require('http');
var express = require('express');
var app = express();

var server = http.createServer(app, function(request, response) {
    console.log((new Date()) + ' Received request for ' + request.url);
    response.writeHead(404);
    response.end();
});
server.listen(process.env.PORT, function() {
    console.log((new Date()) + ' Server is listening on port 8080');
});

var wsServer = new WebSocketServer({
    httpServer: server,
    autoAcceptConnections: false
});

function originIsAllowed(origin) {
  // put logic here to detect whether the specified origin is allowed.
  return true;
}

var connections = [];
var curConnectionID = 0;

function SendMessageToEveryone(messageSending)
{
    for(var i = 0; i < connections.length; i++)
    {
        connections[i].sendUTF(messageSending);
    }
}

function ProcessClientMessage(connection, clientMessage)
{
    if(!connection.yamsInfo.hasUsername && clientMessage === "Username: ")
    {
        connection.sendUTF("Username may not be blank")
    }
    
    else if(!connection.yamsInfo.hasUsername && clientMessage.length > 10 && clientMessage.substring(0, 10) === "Username: ")
    {
        var curUsername = clientMessage.substring(10);
        var uniqueUsername = true;
        for(var i = 0; i < connections.length; i++)
        {
            if(curUsername === connections[i].yamsInfo.username)
            {
                uniqueUsername = false;
            }
        }
        
        if(uniqueUsername)
        {
            connection.yamsInfo.username = curUsername;
            
            connection.sendUTF("Name is unique");
            SendMessageToEveryone(curUsername + " connected.");
            connection.yamsInfo.hasUsername = true;
        }
        else
        {
            connection.sendUTF("Someone else already has that username")
        }
    }
    else if(connection.yamsInfo.hasUsername && clientMessage.length >= 9 && clientMessage.substring(0, 9) === "Message: ")
    {
        SendMessageToEveryone("Message from " + connection.yamsInfo.username + ": " + clientMessage.substring(9));
    }
}

function ProcessClientBinaryData(processClientBinaryData)
{
    
}

// Todo - Figure out why https:// doesn't work

// Much of this is from http://stackoverflow.com/questions/14273978/integrating-websockets-with-a-standard-http-server
wsServer.on('request', function(request) {
    if (!originIsAllowed(request.origin)) {
      // Make sure we only accept requests from an allowed origin
      request.reject();
      console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.');
      return;
    }

    var connectionAccepted = false;
    try {
        var connection = request.accept('chat-server', request.origin);
        connectionAccepted = true;
    } catch (e) {
        console.log("Invalid protocol given");
    }
    if (connectionAccepted)
    {
        // Using this name so we're (fairly) sure it doesn't override anything else, sorry it's painful
        connection.yamsInfo = {id: curConnectionID, hasUsername: false};
        connections.push(connection);
        curConnectionID++;
        connection.sendUTF("Connection accepted");
        
        console.log((new Date()) + ' Connection accepted.');
        connection.on('message', function(message) {
            if (message.type === 'utf8') {
                console.log('Received Message: ' + message.utf8Data);
                ProcessClientMessage(connection, message.utf8Data);
            }
            else if (message.type === 'binary') {
                console.log('Received Binary Message of ' + message.binaryData.length + ' bytes');
                ProcessClientBinaryData(message.binaryData);
            }
        });
        connection.on('close', function(reasonCode, description) {
            console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
            
            // Remove connection from connection list
            for(var i = 0; i < connections.length; i++)
            {
                if(connections[i].yamsInfo.id == connection.yamsInfo.id)
                {
                    connections.splice(i, 1);
                    i = i - 1;
                }
            }
            
            if(connection.yamsInfo.hasUsername)
            {
                SendMessageToEveryone(connection.yamsInfo.username + " disconnected.")
            }
            
        });
    }
});


var fs = require('fs');


// This fetches html files from the client folder (if they exist), and returns a "Page could not be found" error otherwise (this can be customized to some other 404 error page as desired)
app.get('*', function (req, res) {

    var urlReading = req.url;
    if (urlReading === "/")
    {
        urlReading = "/index.html";
    }
    urlReading = __dirname + "/client" + urlReading;

    console.log("Loading: " + urlReading);

    fs.readFile(urlReading, function (err, html) {
        if (err) {
            console.log("Could not find " + urlReading)
            res.writeHead(200, { 'Content-Type': 'text/html' });
            res.end("Page could not be found

Page could not be found

"); } else { console.log("Found " + urlReading) res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(html); } }); });

For the client, see http://jsfiddle.net/ocgpos98/. You can put this in an index.html in your client folder in cloud9 (just make sure to put the script in the head and include jsquery), and that will cause it to load as your default page. You can even just put it in an html file on your desktop and run it, it will still work as normal.

If you make your server hold this client code, then give a client that requests https://appName.userName.c9.io/page.html this client code, you'll find that this example work. If you change it to http instead of https it will though.

Monday, June 2, 2014

Implementing Continuum Crowds, Part 2: Fields

Edit: An probably better explanation of these concepts is given here.

Okay, so now you have your happy eikonal equation solver and you're ready to use it in Continuum Crowds. To be clear you don't actually need any of the stuff here, an eikonal equation solver often simpler than the one given in the previous article possibly combined with RVO works for this kind of flow field pathfinding, but if you want something more like this or this that's what this post is for.

The first thing to note is that, in the previous post, I described $\nabla{T}$ as having one x and y component for each grid node. In the paper, the author actually uses 4 components per node, two x for left and right, and two y for up and down. The method I described averages the x values and y values together, and though it's a little different than the paper, I prefer it. It makes for cleaner implementations, and in practice is basically identical.

The idea behind continuum crowds is that you start with a discomfort and height field, and a set of units. From the positions of these units you compute a density field, and from their velocities you compute a velocity field. Using all these fields you compute a speed field, solve an eioknal equation to find a potential field, then compute the gradient of that solution and multiply by your speed field to find the final velocities of units. You update those units' positions and velocities, and repeat.

From Continuum Crowds:



So first, every field here is the same width and height. Like the eikonal solver from last post, the entire algorithm is running on every grid point every timestep, because that allows for a single computation to be reused for every unit that has the same goal.

The discomfort field $g$ (I have no idea why the choice of 'g') is full of positive floating point values: The higher the value, the less units like to go there. This allows units to prefer things like a paved trail if you give it a lower discomfort than the terrain surrounding it.

The height field $h$ is full of floating point values, and sorta acts like the discomfort field: People prefer to go downhill, aren't affected by no change in slope, and avoid going uphill when possible.

Both of these fields are inputs to Continuum Crowds, and aren't modified by the algorithm - though they can be "dynamic" and changed between timesteps if the caller wants to.

Next, we have the density field $\rho$ (also called the potential field). This field also acts like the discomfort field, helping people avoid places of high density and prefer places of low density. $\bar{\rho}$ is a positive floating-point input parameter between 0.0 and 1.0 (0.7 is fine), where each unit will contribute a minimum of $\bar{\rho}$ to it's own cell, and a maximum of $\bar{\rho}$ to it's neighbor cells. The individual potential field $\rho_i$ for a individual $unit=units_i$ is then created through an interpolating process basically the reverse of the one described in the previous post:

$topLeftX = (int)floor(unit_{posx})$
$topLeftY = (int)floor(unit_{posy})$
$\Delta{x} = x - topLeftX$
$\Delta{y} = y - topLeftY$
$density(topLeftX, topLeftY) = min(1 - \Delta{x}, 1 - \Delta{y})^\lambda$
$density(topLeftX + 1, topLeftY) = min(\Delta{x}, 1 -  \Delta{y})^\lambda$
$density(topLeftX, topLeftY + 1) = min(1 - \Delta{x}, \Delta{y})^\lambda$
$density(topLeftX + 1, topLeftY + 1) = min(\Delta{x}, \Delta{y})^\lambda$

Where $\bar{\rho}={1/2}^\lambda$, so solving for $\lambda$ gives $\lambda=log_{1/2}{\bar{\rho}}$

You sum each individual potential field $\rho_i$ to get the potential field $\rho$, IE:
$$\rho= \sum\limits_{i}\rho_i$$
The velocity field $\bar{v}$ can be created at the same time, by multiplying each potential field by that unit's velocity before summing them all together. After summing all the individual velocity fields together the velocity field $\bar{v}$ is also divided by the total potential field $\rho$ at that point. In summary:
$$\bar{v_x}= \frac{\sum\limits_{i}{\rho_i*unit_{velx}}}{\rho}$$
$$\bar{v_y}= \frac{\sum\limits_{i}{\rho_i*unit_{vely}}}{\rho}$$

Okay, so now we have all those fields down, there's only two left: the potential field $\phi$ and the speed field $f$.

The speed field $f$ is the most painful, so I'll start with that. You can think of this field as being the same as $F$ in the previous post, because it will be used the same way for solving the eikonal equation and moving the units with the normalized final solution. Essentially the idea is we have a topological speed $f_T$, which is how fast we can travel when considering slopes, and a flow speed $f_{\bar{v}}$, which is how much our movement is impeded by those around us. This is the key idea in Continuum Crowds: units are slowed down if they are moving against the flow, and sped up if they are moving with the flow. It causes the formation of lanes, vortices, and other emergent phenonoma that have been observed in real crowds.

So first, we need to compute the gradient of the height field $\nabla{h}$, which is done in the same way as $\nabla{T}$ in the previous post. This essentially gives us the slope (as an x and y component) for each node.  Now we want a speed field that is slow (low values) when units are traveling uphill, and fast (high values) when units are travelling downhill. We'll pretend all units are moving in the positive x and y directions (herby referred to as right and down), and then we can make a small tweak for our result to work in all directions.

Looking at x first, we need to figure out whether a positive gradient values mean we're travelling up or downhill. Because, according to the definition in the previous post, a positive gradient value means that the slope is increasing (as you move to the right), a positive gradient = going uphill. Thus, for higher slope values we want to have lower speeds, and for lower slope values we want to have higher speeds. This is especially true for negative gradient values: that means we're going downhill, so the slope should speed us up instead of slow us down. At this point the paper assumes a maximum and minimum slope and a maximum and minimum speed and uses a weird averaging thing accordingly, but I'd prefer to be working with any possible value.

Specifically we want a function that returns 1 when the gradient equals 0, returns a value greater than 1 when the gradient is less than 0, and returns a value between 0 and 1 when the gradient is greater than 0. An exponential like $e^{-x}$ works, however it shrinks and grows a little to slowly/quickly. Really we just want a line that passes through 1 at x = 0, and caps off at some minimum speed, I think. I believe this is the reason for using the min and max slope and speed values, and it would probably be nice to have a max speed value, so I'll guess I'll conform. The above logic is also true for y, so for some minimum and maximum speed $f_{min}$ and $f_{max}$, and for some minimum and maximum slope $s_{min}$ and $s_{max}$:

$$f_{T_{x}}(x, y)=f_{min}+\frac{(-\nabla{h_x}(x, y)-s_{min})}{(s_{max}-s_{min})}(f_{max}-f_{min})$$
$$f_{T_{y}}(x, y)=f_{min}+\frac{(-\nabla{h_y}(x, y)-s_{min})}{(s_{max}-s_{min})}(f_{max}-f_{min})$$

Where $f_{min}$ and $f_{max}$ would be something like 0.1 and 5.0, and $s_{min}$ and $s_{max}$ would be something like -10.0 and 10.0. This equation works, as when the slope equals $s_{min}$ it will result in $f_{max}$, when the slope equals $s_{max}$ it will result in $f_{min}$, and anything between gives a result between as well.

Next we need to find the flow speed $f_{\bar{v}}$. This uses the velocity field $\bar{v}$ computed above, specifically the velocity field at the point we are moving into. As described in the paper, "indeed, if not for this offset, a person's speed would be dominated by their own previous speed, an undesirable effect." What does that mean? Since we are assuming everyone is moving right and down, we look at $\bar{v}(x + 1, y + 1)$. If this velocity is going in the same direction as us, we want a high speed, if this velocity is going in the opposite direction as us, we want a low speed. We don't ever want to be pushed backwards though (by having a negative speed), so we'll clamp it at our $f_{min}$ from before.

$$f_{\bar{v}_x}(x, y)=max(f_{min}, \bar{v_x}(x + 1, y + 1))$$
$$f_{\bar{v}_y}(x, y)=max(f_{min}, \bar{v_y}(x + 1, y + 1))$$

Finally, given some minimum and maximum densities $\rho_{min}$ and $\rho_{max}$, we'll interpolate between flow speed and topological speed:

$$f_x(x, y) = f_{T_x}(x, y) + \frac{\rho(x + 1, y + 1) - \rho_{min}}{\rho_{max}-\rho_{min}}(f_{\bar{v_x}}(x, y)-f_{T_x}(x, y))$$
$$f_y(x, y) = f_{T_y}(x, y) + \frac{\rho(x + 1, y + 1) - \rho_{min}}{\rho_{max}-\rho_{min}}(f_{\bar{v_y}}(x, y)-f_{T_y}(x, y))$$

The intuition behind this is that when moving into higher densities, we want movement to be dominated by the overall crowd direction there, while at lower crowd densities we want movement to be mostly dominated by the terrain units are moving from. $\rho_{min}$ and $\rho_{max}$ aren't hard limits either: if $\rho(x + 1, y + 1)<\rho_{min}$ then $f(x, y)= f_T(x, y)$, if $\rho(x + 1, y + 1)>\rho_{max}$ then $f(x, y)= f_{\bar{v}}(x, y)$.

If we are moving to, say, $(x - 1, y - 1)$, we would simply look up our $\bar{v_x}$ and $\rho$ values there instead of $(x + 1, y + 1)$. We would also use $\nabla{h}$ instead of $-\nabla{h}$ and $-\bar{v}$ instead of $\bar{v}$, because we're now moving in different directions so uphill and the "same" movement direction are different. You do similar things for $(x - 1, y + 1)$ and $(x + 1, y - 1)$.

This gets back to what I was getting at at the start of this post: At this point, the paper asks you to find $f(x, y)$ for all 4 possible directions ($(x - 1, y - 1)$, $(x + 1, y - 1)$, $(x - 1, y + 1)$, and $(x + 1, y + 1)$), then use them as needed. To understand why this is needed, it's worth talking about how the potential function $\phi$ is used and computed.

Formally, the potential function is written in terms of the eikonal equation:

$$||\nabla{\phi}(x, y)||=C(x, y)$$

Where

$$C(x, y) = \frac{f(x, y)+1+g(x, y)}{f(x, y)}$$

evaluating $f(x, y)$ in the direction of the optimal path to the goal.

What does this mean? In terms of the previous post, $C(x, y)$ is the same as $F(x, y)$.  The key here is that you only ever evaluate $F(x, y)$ when you're solving the quadratic:

$$(T(x, y) - a)^2 + (T(x, y) - b)^2 = (1/F(x, y))^2$$

Where

$$a = Min(T(x + 1, y), T(x - 1, y))$$

$$b = Min(T(x, y + 1), T(x, y - 1))$$

Look closely at $a$ and $b$ here: The value that we choose tells us what direction we're going to move. Because we always move in the direction where T(x, y) decreases most quickly (because that gets us to the goal fastest), if, say, $T(x + 1, y)$ and $T(x, y - 1)$ are smallest, that means we are moving in direction $(x + 1, y -1)$. Now, if when solving the top quadratic we're using $C(x, y)$ instead of $F(x, y)$, this means we evaluate $f(x, y)$ in the direction $(x + 1, y - 1)$ at that point.

Once you have your solution $T(x, y)$, $\phi(x, y)=T(x, y)$ so you're already done computing $\phi$! All that remains is to compute $\nabla\phi$, normalize it, and multiply by $f(x, y)$ at each unit's position to get the final velocity. You look at the sign of $\nabla\phi_x(x, y)$ and $\nabla\phi_y(x, y)$ to tell whether you need to evaluate $f(x, y)$ in direction $(x - 1, y - 1)$, $(x + 1, y - 1)$, $(x - 1, y + 1)$, or $(x + 1, y + 1)$). Then move all units according to those computed velocities, repeat, and your continuum crowds implementation will be complete :) If you want multiple different "groups," each with different goals, simply have one density and velocity field for all groups, and otherwise compute speed and potential fields independently per group.

Saturday, December 7, 2013

Conway's Game of Life in a C# Windows Form Application

I'm starting a programming blog here, for sharing anything I've made that I've found useful myself. Whether or not anyone else sees this doesn't really matter to me, it's just nice to have my progress/projects here for sentimental reasons. Also, due to medication I've started to get out of that depressive state I've been in for a month or two and actually want to do things again, so I figured this would make a nice Saturday project to get my mind going and practice extending my programming techniques, as I used to do often.

So to begin, the intent was to see if I could have efficient pixel writing without using the Get and Set Pixel methods, instead by using an int[] array, and it worked out fairly well.
  1. Open up Visual Studio 2010 (maybe?) or 2012.
  2. Make a new Windows Form Application
  3. Navigate to your Form (probably called Form1 on the right next to your code)
  4. Add a Picture Box by dragging from the toolbox (View -> Toolbox if it's not already somewhere visible) and dropping onto to your form.
  5. Stretch this Picture Box to whatever dimensions you want Conway's Game of Life to run in - feel free to test the limits, it's pretty efficient I hope.
  6. In the Properties window (View -> Properties like the toolbox if it's not already visible), rename it to GraphicsBox or another name of your choice (you'll just have to edit the given code).
  7. Right click anywhere in the form editor and click "Show Code," then paste in the code below.
  8. Build and run, and you have Conway's Game of Life - enjoy - and the reader's challenge would be to make a rainbow version that propagates colors around when duplication occurs according to the most dominant color around.
  9. (optional) If you want even more efficient access than what is given here, you'll need to right click on your project, click on Properties (at the bottom), Build, then check "Allow unsafe code."
    1. This allows the use of pointers, which using graphicsData.Scan0 as your initial position, let you access the memory in the bitmap directly. I avoid this because the array copy method works about as well, and allows me to separate out the code into an object much more easily. Plus unsafe code is scary and bad.

Essentially, the idea here was to be able to write to bitmap data directly. This is done by using the RGBA 32-bit color format in the Bitmaps, which lines up nicely with 32-bit integers, the default integer size (since there's 8 bits/1 byte per value).

This is my simple function to convert from RGBA values to the equivalent integer, essentially just by "packing" the bytes into a single integer using bit shifts, I'm pretty sure this is the standard way to do it. The ()'s are needed here, bit-wise precedence is even lower than boolean operators like == and > I think. That's got me too many times.
public int rgbaToColor(byte r, byte g, byte b, byte a)
{
    return r + (g << 8) + (b << 16) + (a << 24);
}
Now, I just wanted an int[] array that I could address pixels in, where

index = x + y * width

is the formula for converting between (x,y) coordinates of a pixel and positions in the array.

The CustomBitmap class I made does this well, by "locking the bits" - essentially just assigning the memory of the Bitmap to a specific location. I can write to this location by using a Marshal class, which just is a convenient way of copying large arrays.

I double buffered (two bitmaps) everything as well, just to ensure that the bitmap being displayed never had it's bit's locked, since for some reason if you try and display a locked bitmap all you see is a big red X.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;


namespace Conway_Test
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            randomGen = new Random();

            black = rgbaToColor(0, 0, 0, 255);
            white = rgbaToColor(255, 255, 255, 255);

            this.Load += FormLoaded;
            
        }

        public void FormLoaded(object sender, System.EventArgs e)
        {
            InitializeGraphics((Form1)sender);
        }

        CustomBitmap graphics;

        public Random randomGen;

        public Form1 formHolding;

        private int[] newArray;
        private int[] oldArray;

        int black;
        int white;

        /// 
        /// This initializes front and back buffer
        /// The idea here is that one of these can be the old array,
        /// which is the one that you read from when making changes.
        /// These changes then occur by writing to the new array,
        /// then that new array is passed into the CustomBitmap (graphics)
        /// object above to actually update the graphics.
        /// 
        /// You can see an example of this in the DrawStuff method below.
        /// 
        public void InitializeGraphics(Form1 formHolding)
        {
            graphics = new CustomBitmap(GraphicsBox, formHolding);

            int graphicsSize = graphics.GetDataSize();

            newArray = new int[graphicsSize];
            oldArray = new int[graphicsSize];

            // Fill buffers initially with random data
            for (int i = 0; i < newArray.Length; i++)
            {
                if ((randomGen.Next() & 1) == 0)
                {
                    newArray[i] = black;
                    oldArray[i] = black;
                }
                else
                {
                    newArray[i] = white;
                    oldArray[i] = white;
                }
            }

            // Assign data to buffers (and flip the buffers to display data)
            graphics.SetBitmapData(newArray);

            // Create a separate thread to do the heavy processing
            // This allows other non-existent GUI functions to run as needed
            Thread frameThread = new Thread(() => EveryFrame());
            frameThread.IsBackground = true;
            frameThread.Start();
        }

        /// 
        /// This method is intended to be run as a seperate thread,
        /// and updates graphics (and other game logic) repeatedly,
        /// computing the FPS every second as well.
        /// 
        private void EveryFrame()
        {
            long curSecond = DateTime.Now.Second;
            int numPerSecond = 0;
            while (true)
            {
                if (curSecond != DateTime.Now.Second)
                {
                    // This value can be displayed as needed
                    int FPS = numPerSecond;

                    numPerSecond = 0;
                    curSecond = DateTime.Now.Second;
                }
                numPerSecond++;

                DrawStuff();
            }
        }

        /// 
        /// Does one cycle of rules for Conway's Game of Life,
        /// and updates the graphics accordingly.
        /// 
        /// As explained above, after we swap them (so old array is the
        /// previous new array), we read from old array and never write to it,
        /// and write to new array but never read from it (since it's values
        /// before they are assigned are old old - I mean you could read from it,
        /// it just wouldn't help).
        /// 
        /// Then once we have the new generation values, we pass them into
        /// our graphics to be stored in a Bitmap and displayed.
        /// 
        public void DrawStuff()
        {
            // Swap the two byte buffers (so the new is now old and we can modify other)
            int[] tempArray = oldArray;
            oldArray = newArray;
            newArray = tempArray;

            int width = GraphicsBox.Width;
            int height = GraphicsBox.Height;

            // oldArray is intended to be read from for data,
            // and newArray is intended to be written to based on that data.
            for (int x = 1; x < width - 1; x++)
            {
                for (int y = 1; y < height - 1; y++)
                {
                    int neighbors = 0;
                    int curPos = x + y * width;
                    if (oldArray[curPos - 1 - width] == white) neighbors++;
                    if (oldArray[curPos - width] == white) neighbors++;
                    if (oldArray[curPos + 1 - width] == white) neighbors++;
                    if (oldArray[curPos - 1] == white) neighbors++;
                    if (oldArray[curPos + 1] == white) neighbors++;
                    if (oldArray[curPos - 1 + width] == white) neighbors++;
                    if (oldArray[curPos + width] == white) neighbors++;
                    if (oldArray[curPos + 1 + width] == white) neighbors++;

                    if (oldArray[curPos] == white && (neighbors < 2 || neighbors > 3))
                        newArray[curPos] = black;
                    else if (oldArray[curPos] == black && neighbors == 3)
                        newArray[curPos] = white;
                    else
                        newArray[curPos] = oldArray[curPos];

                }
            }
            graphics.SetBitmapData(newArray);
        }

        /// 
        /// "Packs" the given rgba byte values (0-255) into 
        /// the corresponding integer value, for use in the array
        /// above.
        /// 
        /// Technically a byte array could be used throughout
        /// this code and then this method wouldn't be needed, but I
        /// think an integer array is more efficient and actually makes
        /// the code cleaner in the long run.
        /// 
        public int rgbaToColor(byte r, byte g, byte b, byte a)
        {
            return r + (g << 8) + (b << 16) + (a << 24);
        }
    }

    /// 
    /// Contains a Bitmap to be written to as needed.
    /// The idea here is that a PictureBox is provided,
    /// and a Bitmap of that same size is created.
    /// 
    /// GetDataSize() will then return the size of the int[]
    /// array that needs to be created, and an int[] array of that
    /// size with colors created by the rgbaToColor method above.
    /// That int[] array can be passed into the SetBitmapData method
    /// which will display the corresponding graphics on the screen.
    /// 
    public class CustomBitmap
    {
        private PictureBox holder;
        private Bitmap frontBitmap;
        private Bitmap backBitmap;
        private Rectangle bitmapRectangle;
        private Form1 formHolding;

        private bool isFormClosed;

        public CustomBitmap(PictureBox holder, Form1 formHolding)
        {
            this.holder = holder;
            this.formHolding = formHolding;

            this.formHolding.FormClosed += FormHoldingCloseCallback;
            isFormClosed = false;
            
            frontBitmap = new System.Drawing.Bitmap(holder.Width, holder.Height, PixelFormat.Format32bppArgb);
            backBitmap = new System.Drawing.Bitmap(holder.Width, holder.Height, PixelFormat.Format32bppArgb);
            bitmapRectangle = new Rectangle(0, 0, holder.Width, holder.Height);
        }

        public void FormHoldingCloseCallback(object sender, System.EventArgs e)
        {
            this.isFormClosed = true;
        }

        public int GetDataSize()
        {
            return holder.Width * holder.Height;
        }

        public void SetBitmapData(int[] buffer)
        {
            // Lock the data bits of the back bitmap
            BitmapData graphicsData =
                backBitmap.LockBits(bitmapRectangle, System.Drawing.Imaging.ImageLockMode.ReadWrite,
                backBitmap.PixelFormat);

            // Copy the RGBA values of the given buffer to the back bitmap's data
            System.Runtime.InteropServices.Marshal.Copy(buffer, 0, graphicsData.Scan0, buffer.Length);

            // Unlock the data bits of the back bitmap
            backBitmap.UnlockBits(graphicsData);

            // Swap the front and back bitmaps
            Bitmap tempBitmap = backBitmap;
            backBitmap = frontBitmap;
            frontBitmap = tempBitmap;

            // Display the new front bitmap (the one we just drew on)
            if (!this.isFormClosed)
            {
                formHolding.BeginInvoke(new Action(() => { holder.Image = (Bitmap)frontBitmap; }));
            }
        }
    }
}

Let me know if you have any further questions by email (DaniPhye@Gmail.com) or by commenting below. I place this code in the public domain for anyone's use, have fun.

Edit: The multi-threading (which isn't actually needed in this example) was being done on a non-background thread, meaning that closing out of the program didn't terminate the process since there was still an invisible background thread running. Fixed now.

Edit edit: Fixed a race condition upon opening and closing the form. Technically there is still a slight race condition in that the form can be closed after the test at the bottom but before calling formHolding.BeginInvoke(...), however I'll leave it to the reader to fix this if desired, because it just made the code too gross to provide as a helpful example.