A Story About A Game

15 Years ago I wrote a small game called Hellspawn and I rediscovered it again when I was going through some old backup discs. It is a top down shooter and was developed in Borland C++ Builder (I think version 6). It was a very basic game (especially looking back now) from when I was still a very inexperienced developer, still studying to get a degree.

So if anyone is interested here it is: HellSpawn

To get it working on windows 10:

Use Hellspawn.exe to start the game, but first in file properties:

  • Set executable to run in compatibility mode  – Windows 98 / Windows ME
  • Reduced Color mode – 16-bit
  • Override DPI Scaling Behavior, Scaling performed by – Application
  • Can also set to run in 640 x 480, however is best to change screen resolution in windows to 1024 x 768 for best experience.

compatibility

The controls are as follows:

  • Arrow keys to move
  • Left ctrl keys to fire weapon

Simply kill all the enemies to proceed to the next level.

On another note I have some Steam game keys to give away!

For a chance to win one simply email killerrobotics.me@gmail.com with the subject line ‘Killer Robotics Steam Giveaway’ and for the message content just be creative.

Winners will be randomly selected and announced via twitter.

A Story About A Game

DIY VR Headset for $80 (DIY VR Part 2)

IMG_1817

The purpose of this series of posts was to look at ways to experience VR at home for the lowest cost possible. In Part 1 of DIY VR, we took a look at using a smart phone and a Google cardboard compatible headset to stream computer games to the phone in stereoscopic 3D. The main problem with this approach was that it was still relatively expensive as it required a smart phone (iOS or Android) to function.

Now in part 2 we will look at building a VR headset from scratch. My initial goal was to do this for under $150(USD), however after shopping around and changing some parts out for alternatives I managed to get this down to around $80. So let us get started.

The parts required are:

  • Toggle Flick Switch
  • 2x LED
  • 1x resistor 150 Ohm
  • 1x Micro USB cable (at least 2 meters long)
  • 1x HDMI Cable (thin ones work best as they hinder movement less, also at least 2 meters long)
  • Some jumper wires
  • DC Adapter plug 5V 3A (Raspberry Pi compatible one works great)
  • Push Button
  • Google Cardboard Compatible VR Headset (I recommend one with a phone compartment door that opens as it gives better access than the ones which uses a tray that slides in)
  • 6DOF MPU 6050 3Axis gyroscope and accelerometer
  • Arduino Micro (can use off brand alternative)
  • 5inch RaspberryPi LCD Screen 800×480 with HDMI interface

All of these parts can be acquired on AliExpress for about $80 ($82.78 to be precise), as shown in the image below:

Resistorflick_switchwireDC AdapmicroUSBHeadsetpushbuttonledMPU6050lcdHDMIarduinoMicrototals

You will also require Tridef3D or similar software (there are some free alternatives, but I have not had a chance to give them a try at present). Tridef3D is used to convert any Direct X 9/10/11 game into stereoscopic 3D. Tridef3D offers a 14-day free trial, which is plenty to give this a try. The full version of Tridef3D retails for $39.99.

Now that we have all the required components, let us begin with the assembly.

The assembly comprises of 3 main elements:

  1. The Arduino Micro circuit (containing the MPU 6050, push button and led)
  2. The Wiring (providing connectivity to Arduino Micro and power to Screen)
  3. Inserting the screen in the headset and connecting the micro USB cables as well as the HDMI cable.

The Arduino Micro circuit

The diagram below illustrates how the different components need to be connected to the Arduino Micro:

vr_bb1

The push button uses digital pin 5 and the MPU 6050 is connected to the Arduino Micro as follows:

– MPU 6050 SCL pin to Digital Pin 3 on Arduino

– MPU 6050 SDA pin to Digital Pin 2 on Arduino

– MPU 6050 VCC to 5V pin on Arduino

– MPU 6050 GND to GND pin on Arduino

The code to be loaded on the Arduino is as follows:

#include <Mouse.h>
#include <Wire.h>
#include <I2Cdev.h>
#include <MPU6050.h>

MPU6050 mpu;
int16_t ax, ay, az, gx, gy, gz;
int vx, vy;
int inputPin = 5;
bool enableMouse;

void setup() {
Serial.begin(9600);
Wire.begin();
mpu.initialize();
enableMouse = true;
pinMode(inputPin, INPUT);
if (!mpu.testConnection()) {
while (1);
}
Serial.println("Running...");
}

void loop() {
int val = digitalRead(inputPin);
if (val == HIGH) { // check if the input is HIGH
//Place logic here to execute when button is pressed
//Disables mouse movement while button is pressed, this allows you to set your view angle easily.
enableMouse = false;
}
else
{
enableMouse = true;
}
if(enableMouse)
{
mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
vx = -(gy)/150;
vy = (gz+100)/150;
Mouse.move(vx, vy);
delay(20);
}
}

Just note that the orientation of the MPU 6050 makes a difference to which of the axis of the gyroscope will be used. For the above code the MPU 6050 was mounted on the side of the headset as shown in the pictures below:

IMG_1795

In the event of the MPU 6050 being mounted with a different orientation you might have to substitute between the gx, gy and gz values until the desired configuration is achieved.

For my configuration I am rotating around the Y and Z axis.

Also the numbers associated with calculation of vx and vy might have to be tweaked to get the results (movement speed etc.) you desire.

I also added a push button, that when pressed temporarily disables the gyroscopic mouse movement. This is useful when you want to reset you point of view in games.

I attached all the parts of this circuit to the VR Headset using double-sided tape.

The Wiring

In order to have as few cables as possible connecting to the VR headset I modified the USB cable so that it pulls external power from a DC power adapter (a single USB port will not be able to power both the Arduino and the 5 inch LCD) as well as splitting into 2 micro USBs on one end (one only provided power to the LCD and the other one both power and connectivity to Arduino.) the below diagram shows how the wiring is connected:

VR2_bb

For reference a USB cables contains 4 wires:

  • Red wire – +5V DC
  • White or Yellow – Data connectivity
  • Green – Data Connectivity
  • Black – GND

I also included a switch to turn the power on and off (this is useful to turn off the mouse functionality until it is needed, otherwise it will interfere with mouse movement when it is not desired) as well as an LED to show when the headset is powered on.

IMG_1807

Inserting Screen in Headset and connecting all the wiring

The LCD screen is held in place by the clamps in the headset used to hold a phone (it is a snug fit). Then simply connect the 2 micro USBs to the LCD and Arduino respectively (ensuring the plug with the data connections is plugged into the Arduino and that the power only micro USB is plugged into the power socket on the LCD display). Try to run the cables in the extra spaces in the Headset around the screen in order to keep them out of the way.

Lastly connect the HDMI cable to the LCD.

The assembly is now complete.

IMG_1817

Connecting headset to PC and setting up software

To connect the headset to your PC do the following:

  1. Plug the DC adapter into mains power.
  2. Plug the USB connector into an available USB port in your PC.
  3. Connect HDMI cable into and available HDMI port on your PC graphics card (You can use a DVI port with an adapter)

Go to display settings and click on detect displays, then set Multiple displays to “Duplicate these Displays” and make sure your resolution is set to 800×480.

Open up Tridef3D and start-up a game.

You might have to play around with each individual games graphical settings as well as mouse sensitivity to get the best results.

For future enhancements I will look at getting a higher definition LCD screen and also work on head movement tracking by using infrared LEDs and a Wiimote (Wiimote used as a IR Camera).

And there you have it a DIY VR Headset for $80. Give it a try.

Here is a short demonstration video:

DIY VR Headset for $80 (DIY VR Part 2)

Bite Size C# – DataTable to RecordSet Converter

When working on legacy systems you may sometimes find that a wide variety of technologies have been used, and short of rewriting a huge amount of code the only thing that can be done is to create some functionality to map between the different technologies utilised.

I had a scenario where I had to integrate 2 legacy applications utilising different technologies for database access, one utilised ADODB and the other ADO.NET. Due to time constraints I could not change the underlying code of the systems and rather decided to create a helper class to map an ADO.NET DataTable to an ADODB RecordSet in order to facilitate the integration.

My DTtoRSconvert is a static class that resides in the Core.Data.Help namespace. It contains a static method ConvertToRecordSet that takes a DataTable as a parameter and returns a RecordSet. It also has one static private method TranslateType which is used to map between the DataTable and RecordSet Data types.

Here is the code:

using System.Data;
using System.Reflection;
using ADODB;

namespace Core.Data.Helper
{
    //Helper class that convert ADO.Net DataTables to ADODB RecordSets
    public static class DTtoRSconvert
    {
        public static Recordset ConvertToRecordSet(DataTable inTable)
        {
            var recordSet = new Recordset { CursorLocation = CursorLocationEnum.adUseClient };

            var recordSetFields = recordSet.Fields;
            var inColumns = inTable.Columns;

            foreach (DataColumn column in inColumns)
            {
                recordSetFields.Append(column.ColumnName
                                    , TranslateType(column.DataType)
                                    , column.MaxLength
                                    , column.AllowDBNull
                                          ? FieldAttributeEnum.adFldIsNullable
                                          : FieldAttributeEnum.adFldUnspecified
                                    , null);
            }

            recordSet.Open(Missing.Value
                        , Missing.Value
                        , CursorTypeEnum.adOpenStatic
                        , LockTypeEnum.adLockOptimistic, 0);

            foreach (DataRow row in inTable.Rows)
            {
                recordSet.AddNew(Missing.Value,
                              Missing.Value);

                for (var columnIndex = 0; columnIndex &lt; inColumns.Count; columnIndex++)
                {
                    recordSetFields[columnIndex].Value = row[columnIndex];
                }
            }

            return recordSet;
        }

        private static DataTypeEnum TranslateType(IReflect columnDataType)
        {
            switch (columnDataType.UnderlyingSystemType.ToString())
            {
                case "System.Boolean":
                    return DataTypeEnum.adBoolean;

                case "System.Byte":
                    return DataTypeEnum.adUnsignedTinyInt;

                case "System.Char":
                    return DataTypeEnum.adChar;

                case "System.DateTime":
                    return DataTypeEnum.adDate;

                case "System.Decimal":
                    return DataTypeEnum.adCurrency;

                case "System.Double":
                    return DataTypeEnum.adDouble;

                case "System.Int16":
                    return DataTypeEnum.adSmallInt;

                case "System.Int32":
                    return DataTypeEnum.adInteger;

                case "System.Int64":
                    return DataTypeEnum.adBigInt;

                case "System.SByte":
                    return DataTypeEnum.adTinyInt;

                case "System.Single":
                    return DataTypeEnum.adSingle;

                case "System.UInt16":
                    return DataTypeEnum.adUnsignedSmallInt;

                case "System.UInt32":
                    return DataTypeEnum.adUnsignedInt;

                case "System.UInt64":
                    return DataTypeEnum.adUnsignedBigInt;

                //System.String will also be handled by default
                default: 
                    return DataTypeEnum.adVarChar;
            }
        }
    }
}
Bite Size C# – DataTable to RecordSet Converter

Bite Size C# – XLS File Merger

This will be the last Bite Size C# post revolving around CSV and XLS files (for a while at least). The next one will be on a different and probably a bit more exciting topic.

Today we will look at my XLS File Merger, which resides in the Core.XLS namespace of my Core.dll library. It is a static class with one static method MergeFiles. The purpose of this method is to merge 2 or more XLS files into a single file, i.e. taking the worksheets of 2 or more files and combing them into a single file. The MergeFiles method takes 2 arguments: a list of locations of files to be merged (filePathList) and the destination file that will be created (outputFile).

Here is the code:

using System;
using Microsoft.Office.Interop.Excel;

namespace Core.XLS
{
    public static class XlsFileMerge
    {
        public static void MergeFiles(string[] filePathList, string outputFile)
        {
            var app = new Application {Visible = false};

            app.Workbooks.Add("");
            foreach (var file in filePathList)
            {
                app.Workbooks.Add(file);
            }


            for (var i = 2; i &lt;= app.Workbooks.Count; i++)
            {
                var count = app.Workbooks[i].Worksheets.Count;

                app.Workbooks[i].Activate();
                for (var j = 1; j &lt;= count; j++)
                {
                    var ws = (_Worksheet) app.Workbooks[i].Worksheets[j];
                    if (ws.UsedRange.Rows.Count &lt;= 1) continue;
                    ws.Select(Type.Missing);
                    ws.Cells.Select();


                    var sel = (Range) app.Selection;
                    sel.Copy(Type.Missing);

                    var sheet = (_Worksheet) app.Workbooks[1].Worksheets.Add(
                        Type.Missing, Type.Missing, Type.Missing, Type.Missing
                                                 );
                    sheet.Name = ws.Name;
                    sheet.Paste(Type.Missing, Type.Missing);
                }
            }
            app.Workbooks[1].SaveAs(@outputFile);
            app.Quit();
        }
    }
}
Bite Size C# – XLS File Merger

Bite Size C# – XLS Creator

Today we will look at my XLS Creator Class. It is very similar to my CSV Creator class discussed in an earlier post (BITE SIZE C# – CSV FILE CREATOR), except that it creates a well formed XLS file instead of a CSV file.

It is a static class contained in the Core.XLS namespace and contains 1 static method ExportToExcel. Like the CSV Creator class it uses a generic list of objects to construct a XLS file. The method also takes 2 string values xlsNameWithExt and sheetName.

xlsNameWithExt which is used to define the name and location to save the created XLS file and sheetName which is used to set the sheet name the data will be inserted on.

Here is the code:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Core.XLS
{
  public static class XlsCreator
  {
  public static void ExportToExcel<T>(List<T> list, string xlsNameWithExt, string sheetName)
  {
  var columnCount = 0;

  var StartTime = DateTime.Now;

  var rowData = new StringBuilder();

  var properties = typeof (T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

  rowData.Append("<Row ss:StyleID=\"s62\">");
  foreach (var p in properties)
  {
  if (p.Name.ToUpper() == "ENTITYSTATE" || p.Name.ToUpper() == "ENTITYKEY")
    continue;

  if (p.PropertyType.Name != "EntityCollection`1" && p.PropertyType.Name != "EntityReference`1")
  {
    var type = "String";
    columnCount++;
    rowData.Append("<Cell><Data ss:Type=\"" + type + "\">" + p.Name + "</Data></Cell>");
  }
  else
    break;
  }
  rowData.Append("</Row>");

  foreach (var item in list)
  {
  rowData.Append("<Row>");
  for (var x = 0; x < columnCount; x++) //each (PropertyInfo p in properties)
  {
    var o = properties[x].GetValue(item, null);
    var value = o == null ? "" : o.ToString();
    rowData.Append("<Cell><Data ss:Type=\"String\">" + value + "</Data></Cell>");
  }
  rowData.Append("</Row>");
  }

  var sheet = @"<?xml version=""1.0""?>
    <?mso-application progid=""Excel.Sheet""?>
    <Workbook xmlns=""urn:schemas-microsoft-com:office:spreadsheet""
    xmlns:o=""urn:schemas-microsoft-com:office:office""
    xmlns:x=""urn:schemas-microsoft-com:office:excel""
    xmlns:ss=""urn:schemas-microsoft-com:office:spreadsheet""
    xmlns:html=""http://www.w3.org/TR/REC-html40"">
    <DocumentProperties xmlns=""urn:schemas-microsoft-com:office:office"">
    <Author>MSADMIN</Author>
    <LastAuthor>MSADMIN</LastAuthor>
    <Created>2011-07-12T23:40:11Z</Created>
    <Company>Microsoft</Company>
    <Version>12.00</Version>
    </DocumentProperties>
    <ExcelWorkbook xmlns=""urn:schemas-microsoft-com:office:excel"">
    <WindowHeight>6600</WindowHeight>
    <WindowWidth>12255</WindowWidth>
    <WindowTopX>0</WindowTopX>
    <WindowTopY>60</WindowTopY>
    <ProtectStructure>False</ProtectStructure>
    <ProtectWindows>False</ProtectWindows>
    </ExcelWorkbook>
    <Styles>
    <Style ss:ID=""Default"" ss:Name=""Normal"">
    <Alignment ss:Vertical=""Bottom""/>
    <Borders/>
    <Font ss:FontName=""Calibri"" x:Family=""Swiss"" ss:Size=""11"" ss:Color=""#000000""/>
    <Interior/>
    <NumberFormat/>
    <Protection/>
    </Style>
    <Style ss:ID=""s62"">
    <Font ss:FontName=""Calibri"" x:Family=""Swiss"" ss:Size=""11"" ss:Color=""#000000""
    ss:Bold=""1""/>
    </Style>
    </Styles>
    <Worksheet ss:Name=""" + sheetName + @""">
    <Table ss:ExpandedColumnCount=""" + (properties.Count() + 1) + @""" ss:ExpandedRowCount=""" +
    (list.Count() + 1) + @""" x:FullColumns=""1""
    x:FullRows=""1"" ss:DefaultRowHeight=""15"">
    " + rowData + @"
    </Table>
    <WorksheetOptions xmlns=""urn:schemas-microsoft-com:office:excel"">
    <PageSetup>
    <Header x:Margin=""0.3""/>
    <Footer x:Margin=""0.3""/>
    <PageMargins x:Bottom=""0.75"" x:Left=""0.7"" x:Right=""0.7"" x:Top=""0.75""/>
    </PageSetup>
    <Print>
    <ValidPrinterInfo/>
    <HorizontalResolution>300</HorizontalResolution>
    <VerticalResolution>300</VerticalResolution>
    </Print>
    <Selected/>
    <Panes>
    <Pane>
      <Number>3</Number>
      <ActiveCol>2</ActiveCol>
    </Pane>
    </Panes>
    <ProtectObjects>False</ProtectObjects>
    <ProtectScenarios>False</ProtectScenarios>
    </WorksheetOptions>
    </Worksheet>
    <Worksheet ss:Name=""Sheet2"">
    <Table ss:ExpandedColumnCount=""1"" ss:ExpandedRowCount=""1"" x:FullColumns=""1""
    x:FullRows=""1"" ss:DefaultRowHeight=""15"">
    </Table>
    <WorksheetOptions xmlns=""urn:schemas-microsoft-com:office:excel"">
    <PageSetup>
    <Header x:Margin=""0.3""/>
    <Footer x:Margin=""0.3""/>
    <PageMargins x:Bottom=""0.75"" x:Left=""0.7"" x:Right=""0.7"" x:Top=""0.75""/>
    </PageSetup>
    <ProtectObjects>False</ProtectObjects>
    <ProtectScenarios>False</ProtectScenarios>
    </WorksheetOptions>
    </Worksheet>
    <Worksheet ss:Name=""Sheet3"">
    <Table ss:ExpandedColumnCount=""1"" ss:ExpandedRowCount=""1"" x:FullColumns=""1""
    x:FullRows=""1"" ss:DefaultRowHeight=""15"">
    </Table>
    <WorksheetOptions xmlns=""urn:schemas-microsoft-com:office:excel"">
    <PageSetup>
    <Header x:Margin=""0.3""/>
    <Footer x:Margin=""0.3""/>
    <PageMargins x:Bottom=""0.75"" x:Left=""0.7"" x:Right=""0.7"" x:Top=""0.75""/>
    </PageSetup>
    <ProtectObjects>False</ProtectObjects>
    <ProtectScenarios>False</ProtectScenarios>
    </WorksheetOptions>
    </Worksheet>
    </Workbook>";

  using (var sw = new StreamWriter(xlsNameWithExt))
  {
  sw.Write(sheet);
  }
  }
  }
}
Bite Size C# – XLS Creator

Bite Size C# – XLS to CSV Converter

Let us have a look at my XLS to CSV Converter class today.

It is a static class, which is located in the Core.CSV namespace. It contains one static method CovertExcelToCsv which takes 3 parameters excelFilePath, csvOutputFile and worksheetNumber. The excelFilePath parameter is used to input the source Excel file, csvOutputFile is used to input the destination CSV file and lastly worksheetNumber is used to define which worksheet to convert, as CSV files do not have worksheets only one worksheet can be converted to CSV file at a time. The worksheetNumber parameter has a default value of 1 in the event that no value is passed in.

using System;
using System.Data;
using System.Data.OleDb;
using System.IO;

namespace Core.CSV
{
 public static class XlsToCsvConverter
 {
  public static void CovertExcelToCsv(string excelFilePath, string csvOutputFile, int worksheetNumber = 1)
  {
   if (!File.Exists(excelFilePath)) throw new FileNotFoundException(excelFilePath);
   if (File.Exists(csvOutputFile)) throw new ArgumentException("File exists: " + csvOutputFile);

   // Connection string
   var connectionStr =
    String.Format(
     "Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties=\"Excel 8.0;IMEX=1;HDR=NO\"",
     excelFilePath);
   var cnn = new OleDbConnection(connectionStr);

   // Get schema information and then data in spreadsheet
   var dt = new DataTable();
   try
   {
    cnn.Open();
    var schemaTable = cnn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
    if (schemaTable != null && schemaTable.Rows.Count < worksheetNumber)
     throw new ArgumentException("The worksheet number requested does not exist.");
    if (schemaTable != null)
    {
     var worksheet = schemaTable.Rows[worksheetNumber - 1]["table_name"].ToString().Replace("'", "");
     var sql = String.Format("select * from [{0}]", worksheet);
     var da = new OleDbDataAdapter(sql, cnn);
     da.Fill(dt);
    }
   }
   catch (Exception ex)
   {
    //Insert logging of exception here
    throw;
   }
   finally
   {
    // Close connection to the spreadsheet
    cnn.Close();
   }

   // Write out CSV data
   using (var wtr = new StreamWriter(csvOutputFile))
   {
    foreach (DataRow row in dt.Rows)
    {
     var firstLine = true;
     foreach (DataColumn col in dt.Columns)
     {
      if (!firstLine)
      {
       wtr.Write(",");
      }
      else
      {
       firstLine = false;
      }
      var data = row[col.ColumnName].ToString().Replace("\"", "");
      wtr.Write(String.Format("{0}", data));
     }
     wtr.WriteLine();
    }
   }
  }
 }
}
Bite Size C# – XLS to CSV Converter

Bite Size SQL – Basic Query Optimisation

How SQL queries are written can have a huge impact on how quickly and efficiently they execute. In this post I will explain some very basic methods that can be used to optimise SQL queries.

  • When writing select queries, only select the columns you need. In general using “SELECT *” is very bad practice.
  • Avoid using sub queries, rather use joins. What is meant by this is instead of using:
        SELECT COLUMNA FROM TABLEA WHERE COLUMNB IS IN (SELECT COLUMNB FROM TABLEB)
        Rather use:
        SELECT COLUMNA FROM TABLEA AS TA INNER JOIN TABLEB AS TB ON TA.COLUMNB = TB.COLUMNB
  • Use WHERE to limit the result set to only what you need. There is no point in pulling 10000 records if you are only looking for records, for example in a certain date range.
  • Use WITH NOLOCK with SELECT queries when feasible, this can prevent deadlocks which can cause serious performance issues. However note that using WITH NOLOCK can result in a non-accurate result set being returned which contains non committed records, so keep this in mind when using. But when querying a production system database this risk might be outweighed by the risk of deadlocks occuring.

One last point on stored procedures, this is not really an optimisation point as much as a best practice point. When developing stored procedures always write them to return result sets of the same dimensions. I have worked on numerous legacy systems were this was not the case and it results in a complete nightmare, especially with projects that utilise an ORM.

Bite Size SQL – Basic Query Optimisation