Comments
This part is almost straight from my programming style document.
This might sound stupid, but try to comment every other line (unless you have a lot of repeating or similar tasks). By doing so you get two advantages:
- Someone unskilled in programming or unfamiliar with what you are doing is able to read your code.
- You know what you are doing
The first advantage is important unless you are working alone and never expect to see your code again. In that case you should really ask yourself if you should even be writing that code.
The second one is important even if you have no trouble reading code. Let's take a look at the following example.
for(int index_person = 0; index_person < persons.size(); ++index_person)
{
for(int index_kids = 0; index_kids < person[index_person].kids.size(); ++index_kids)
{
/* ... */
}
}
And then take a look at this example:
/* Iterating through the persons in the lists */
for(int index_person = 0; index_person < persons.size(); ++index_person)
{
/* Iterating through the kids of the persons in the list */
for(int index_kids = 0; index_kids < person[index_person].kids.size(); ++index_kids)
{
/* checking if any of the persons in the list have a kid who is dead */
/* ... */
}
}
In the last example I only have to track back to the first comment prior to my line of code to know what I'm doing here. It's a pain to write so many comments but it makes code a whole lot easier to read.
Splash screens
I always have my doubts about splash screens. As far as I know the majority of the splash screens have no function. A minority does have a function (they do background loading).
The main purpose of splash screen is, in my opinion, advertising. For a few second the user sees the name of the application and the company behind it. I admit that you can use it to load things in the background, but showing a splash screen also takes time. In that case you are better off loading the application and showing a progress bar. At that point the user sees it as the application is loading content. If you are using a splash screen for that (and I'm going to assume it makes no difference in performance or time that it requires) than the users perceives it as an annoyance.
This is a weird thing. Because showing a splash screen that does nothing but is only showed for a 2 seconds after which the application starts loading is seemed to be less intrusive than a splash screen that takes longer but actually causes the application to load faster (it doesn't require two useless seconds).
So how do you create a splash screen?
First of all start with a small image (take the dimensions of the office 2007 splash screens), add some abstract art (not something complex). Then create a form without borders in C# that closes after a few seconds and then modify the program.cs so that it looks like this.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace Client
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new SplashScreen()); // load the splash screen
Application.Run(new MainWindow()); // load the application
}
}
}
I know it's a cheap trick, but it looks nice if you do it well. And most customers care a lot about appearance.
Events in C++
This week I have decided to do some little work on Nova as I would like to use it for a game. But I’m currently missing a GUI. Since writing a GUI is often a pain while a nice GUI is its weight worth in gold it was worth to invest some time in it.
One of the very first things I have decided on is that I want a good and proper event management system. I like how it is done in C#
myButton.Click += new EventHandler(this.myButton_Click);
However something equally nice doesn't exist in plain C++, so I have decided to write one.
struct GuiEvent
{
bool cancel;
GuiEvent() : cancel(false) {}
};
class Button
{
public:
Event<GuiEvent> OnDown;
Event<GuiEvent> OnUp;
void FireClickEvent()
{
size_t cycle = 0; GuiEvent e;
while(OnDown.Fire(cycle, e)) {
if(e.cancel == true) return;
}
e = GuiEvent(); cycle = 0;
while(OnUp.Fire(cycle, e));
}
};
class Application : public BaseEvent::Receiver<Application>
{
Button m_StartButton;
public:
Application()
{
RegisterEvent<GuiEvent>(m_StartButton.OnDown, &Application::StartDown);
RegisterEvent<GuiEvent>(m_StartButton.OnUp, &Application::StartUp);
m_StartButton.FireClickEvent();
}
void StartUp(GuiEvent& eventParam)
{
std::cout << "The start button was released" << "\n";
}
void StartDown(GuiEvent& eventParam)
{
// If you set event param to true the release will never be called
//eventParam.cancel = true;
std::cout << "The start button is pressed down" << "\n";
}
};
int main(int argc, const char* argv[])
{
Application myApp;
return 0;
}
The above is rather primitive as I have written it quickly, but it looks nice and is code that is easy to understand. For the full source click here (C++ example of events)
EULA of Visual Studio 2010 from dreamspark
EULA: End User License Agreement
You know that annoying check box you need to check when you install new software or games? The one that says "I have read and agree with the EULA"? Do you ever read what it says above it? No? Oh...
Seriously. The EULA is important and you should read it... in some cases.
One of those cases was when I was browsing DreamSpark (free Microsoft development tools) which after registering and confirming you are a student (one of the few times I happily admit I'm a student) you can download all kind of free development tools at no charge with all features.
In general there is no such thing as free stuff. "Buy one and get another free" is not free, it is 50% discount when you buy two. Still I decided to check it out and to my surprise (actually, the school forum had point it out). There was also the new Microsoft Visual Studio 2010 Professional.
"Cool!!" was my first reaction, but after checking the site further out I noticed that Visual Studio 2008 and many others had a license that by downloading I agree to not use it for "commercial". Here is the section I refer to:
b. Restrictions. You may not use the Software:
• for commercial purposes (except as permitted under Section 3(d); or
• to develop or maintain Your own administrative or IT systems, or those of Your educational institution.
(Full Microsoft DreamSpark License)
However for some strange reason the above EULA was not on the screen of VS2010. So after downloading I checked if the installer had such a restriction and it did not. That means I now have a full and legal version of Visual Studio at no cost with no restrictions other than those you would have if you bought it.
For that I thank you Microsoft!
Solved undesired template specification
A while ago (why do I always start with that) I wrote an blog entry about undesired template specification, what to encounter and how to work around it.
Anyway here is a quick definition of two structures I will be using in the article:
/**
* I will be using the following structures throughout the article
*/
template <typename T> struct Vector3<T> {
union {
struct { T x, y, z; }
T array[3];
};
Vector3<T>() : x(0), y(0), z(0) {}
Vector3<T>(T nx, T ny, T nz) : x(nx), y(ny), z(nz) {}
};
template <typename T> struct Vector4<T> {
union {
struct { T x, y, z, w; }
T array[4];
};
Vector4<T>() : x(0), y(0), z(0), w(0) {}
Vector3<T>(T nx, T ny, T nz, T nw) : x(nx), y(ny), z(nz), w(nw) {}
};
typedef Vector3<unsigned int> Vector3u;
typedef Vector3<float> Vector3f;
typedef Vector4<unsigned int> Vector4u;
typedef Vector4<float> Vector4f;
struct SVertex
{
Vector4f pos;
Vector3f normal;
Vector2f texcoord;
};
Because of Brick (3D random dungeon generator that takes design into account) I have noticed that there is one thing I do failry often:
Vector3f position; SVertex vertex; /* Need to draw it, so I store position in vertex */ vertex.pos = position; // ERROR! Trying to assign Vector3f to Vector4f!!
And finally I used defines to do the conversion for me:
#define VEC3TOVEC4(v) Vector4f((v).x, (v).y, (v).z, 0) #define VEC4TOVEC3(v) Vector3f((v).x, (v).y, (v).z) /* New code becomes */ vertex.pos = VEC3TOVEC4(position); // Works
Of course the above code is not nice to look at and I find it even plain ugly, but it works. However I don't want to do that in future projects (it feels like a hack), I would need to define something like that for every type (float, double, unsigned int et cetera) and on top of that it generates warnings:
Vector3<double> position; // Notice it is unsigned! SVertex vertex; /* Need to draw it, so I store position in vertex */ vertex.pos = VEC3TOVEC4(position); // Works, but generates warning about losing information
But I wouldn't be writing this post unless I tackled that little issue, and for once I can add that the solution is quite nice as well.
Vector3<double> position; // Notice it is unsigned! SVertex vertex; /* Need to draw it, so I store position in vertex */ vertex.pos.Set(position.array, 3); // Works, no errors and no warnings vertex.pos = Vector4f(position.array, 3); // Works fine as well
So what did I change?
Well, I used mutliple template (one for the class and for the function/constructor). This looks something like this:
template <typename T> struct Vector4
{
/* ... */
template <typename R>
explicit inline Vector4<T>(const R* values, const unsigned int elements/*=4*/);
template <typename R>
inline Vector4<T>& Set(const R* values, const unsigned int elements/*=4*/);
/* ... */
};
// Implementation
template <typename T> template <typename R>
Vector4<T>::Vector4(const R* values, unsigned int elements)
: x(elements > 0 ? (T)values[0] : 0), y(elements > 1 ? (T)values[1] : 0)
, z(elements > 2 ? (T)values[2] : 0), w(elements > 3 ? (T)values[3] : 0)
{
}
template <typename T> template <typename R>
Vector4<T>& Vector4<T>::Set(const R* values, const unsigned int elements)
{
x = (elements > 0 ? (T)values[0] : 0);
y = (elements > 1 ? (T)values[1] : 0);
z = (elements > 2 ? (T)values[2] : 0);
w = (elements > 3 ? (T)values[3] : 0);
return *this;
}
If you take a look at the code I think it is quite clear except that you might have some questions.
-
Q: Why do you use
explicitwith the constructor?A: Because that prevents the implicit use of constructors. If I would allow it aVector4ucould be implicit assigned toVector3f, although it would be missing an argument. However I think that when you are converting, you should be somewhat aware of it, especially when it can be expensive. -
Q: Why have you commented out the default value for
elements?A: Because you don't know how many elements there are invaluesmight be (there is a method to find out). -
Q: So why don't you find out automatically and what is with those conditionals in the constructor?A: Those two are related. By telling it explicitly there is a real good chance that the compiler removes the conditionals, so the
( check ? true-value : false-value)check will be removed.
Combo hit!! in code
I have always wondered how hard it would be to write a combo system. Not that hard I guess. And after a bit of morning programming I already got it working.
The only reason it took longer than anticipated was because of muscle memory. One of the features I wanted to test was the delay. For example you want to do the "asdf" combo, but you are for some reason not fast enough, than the combo should not start. Simulating this is easy, just begin the combo and stop somewhere for a second and then complete the combo. So "asd", pause and then "f".
However when I tried that for some reason the combo was sometimes completed. Only after adding the debug messages I noticed that I often automatically did complete the combo. The problem was muscle memory.
Anyway below is the code and if anyone wants to extend it (wrong next key, combo breakers, roman cancel, and follow-up combos) feel free to do so and let me know.
/*******************************************************************************
* The MIT License
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
* Copyright (c) 2010 Wouter Lindenhof (http://limegarden.net)
*
* Demonstration of a simple ComboHit system
*******************************************************************************/
#include <Windows.h>
#include <iostream>
#include <vector>
#pragma comment(lib, "Winmm.lib")
#define DEBUGLOG 1 // Set to 0 to turn debug messages off
class KeyHit
{
public:
UINT m_Key;
UINT m_Delay;
UINT m_Waiting;
public:
KeyHit(UINT key, UINT delay) : m_Key(key), m_Delay(delay), m_Waiting(0) { }
};
class HitCombo
{
UINT m_SequenceIndex;
std::vector<KeyHit> m_Keys;
public:
HitCombo() : m_SequenceIndex(0) { }
void Cancel();
void Update(DWORD ms);
operator bool();
HitCombo& operator << (const KeyHit& hit);
};
int main(int argc, const char* argv[])
{
std::cout << "-----------------------------------------------" << std::endl;
std::cout << "This is a combo key tester: " << std::endl;
std::cout << "Press \"ASDF\" quickly to do a combo hit" << std::endl;
std::cout << "Press \"Wouter\" quickly to write my name" << std::endl;
std::cout << "Press SHIFT and then escape to quit" << std::endl;
std::cout << "-----------------------------------------------" << std::endl;
HitCombo QuitApplication, ComboHit, WouterCombo;
QuitApplication << KeyHit(VK_SHIFT, 250) << KeyHit(VK_ESCAPE, 250);
ComboHit << KeyHit('A', 250) << KeyHit('S', 250) << KeyHit('D', 250)
<< KeyHit('F', 250);
WouterCombo << KeyHit('W', 250) << KeyHit('O', 250) << KeyHit('U', 250)
<< KeyHit('T', 250) << KeyHit('E', 250) << KeyHit('R', 250);
DWORD lastTime = timeGetTime();
DWORD curTime = lastTime;
DWORD difference = 0;
while(true)
{
curTime = timeGetTime();
difference = curTime - lastTime;
lastTime = curTime;
if(ComboHit)
{
std::cout << "You did a combo hit!!" << std::endl;
WouterCombo.Cancel();
QuitApplication.Cancel();
}
if(WouterCombo)
{
std::cout << "You wrote 'Wouter', good for you!" << std::endl;
ComboHit.Cancel();
QuitApplication.Cancel();
}
if(QuitApplication)
{
std::cout << "You quit the application!" << std::endl;
ComboHit.Cancel();
WouterCombo.Cancel();
break;
}
ComboHit.Update(difference);
QuitApplication.Update(difference);
WouterCombo.Update(difference);
}
return 0;
}
void HitCombo::Cancel() {
m_SequenceIndex = 0;
}
// Implementation
HitCombo& HitCombo::operator <<(const KeyHit &hit) {
m_Keys.push_back(hit);
return *this;
}
HitCombo::operator bool() {
return m_SequenceIndex == m_Keys.size();
}
void HitCombo::Update(DWORD ms)
{
if(m_SequenceIndex < m_Keys.size())
{
KeyHit& hit = m_Keys[m_SequenceIndex];
hit.m_Waiting += ms;
if(GetAsyncKeyState(hit.m_Key) != 0)
{
if(hit.m_Waiting < hit.m_Delay)
{
hit.m_Waiting = 0;
m_SequenceIndex++;
#if DEBUGLOG = 1
std::cout << "you pressed key " << (char)hit.m_Key << std::endl;
#endif
}else
{
#if DEBUGLOG = 1
std::cout << "Delay too long" << std::endl;
#endif
hit.m_Waiting = 0;
m_SequenceIndex = 0;
}
if(m_SequenceIndex == 0)
{
hit.m_Waiting = 0;
}
}else if(m_SequenceIndex==0)
{
hit.m_Waiting = 0;
}else
{
if(hit.m_Waiting > hit.m_Delay)
{
m_SequenceIndex = 0;
hit.m_Waiting = 0;
#if DEBUGLOG
std::cout << "Delay too long" << std::endl;
#endif
}
}
}else
{
m_SequenceIndex = 0;
KeyHit& hit = m_Keys[m_SequenceIndex];
hit.m_Waiting = 0;
}
}
Smart pointers: A dumb idea?
A while ago I wrote a smart pointer for Nova and I have been wondering on whether or not I should use it. And if I did choose to use it to what degree.
Smart pointers are useful, I use the boost variant often enough, but like everything you shouldn’t fully depend on it. Pointers are in many cases good enough. The only thing why smart pointers are so useful is that you don’t have to worry about the lifetime of the object (when the object is destroyed).
In many cases the only time you worry about is when data needs to be shared, for example a texture, as you don’t want to load the same data twice. But another simple technique is the IUnknown interface which creates an object that will delete itself when it has been released as many times as it was created. You still don’t have to worry about the lifetime of the object, you only have to remind you to call release when you no longer need an object. If nothing requires that object the call to release will cause it to delete itself.
Here is the basic code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /* IBaseObject.h */ class IBaseObject{ private: unsigned int m_ReferenceCounter; protected: IBaseObject(); public: virtual ~IBaseObject() = 0; unsigned int AddRef(); unsigned int Release(); }; /* IBaseObject.cpp */ #include "IBaseObject.h" IBaseObject::IBaseObject() : m_ReferenceCounter(1) { } IBaseObject::~IBaseObject() {} unsigned int IBaseObject::AddRef(){ return ++m_ReferenceCounter; } unsigned int IBaseObject::Release() { --m_ReferenceCounter; if(m_ReferenceCounter == 0) { delete this; } return m_ReferenceCounter; } |
After some thought on the issue I have decided that IUnknown is far superior to a smart pointer. The majority of the smart pointers I have encountered and written work on the same basic principle. Each smart pointer has a pointer to the object and also a pointer to an integer in which you store the amount of copies. That means that if you look at the code it will look something like this:
1 2 3 4 5 6 | template <typename T> class smart_ptr { T* m_Data; unsigned int* m_Copies; }; |
When you think about how smart_ptr really works, you will think how stupid the idea is. For every copy of the object you will have two extra pointers, assuming 32 bit system, that would be 8 bytes extra for every copy.
The second issue is a bit more hidden but if you have this function:
1 2 | class CObject; // Just an object void accidental_copy(smart_ptr<CObject> copyParam) { /** does something **/ } |
You will be making an copy of the object, which means that you will have an extra construct and destruct function to take in account. Which means that a smart pointer not only increases the amount of memory needed, but also reduces the speed. With a pointer you won't have the above problems.
There is however one huge advantage that a smart pointer has but IUnknown doesn't: Smart pointers can work with virtually any type while to use IUnknown the object needs to be derived from it. However since I'm writing the code for Nova I can just make sure that all objects derive from IUnknown.
To add an conclusion, I have decided to before using smart pointers I should first try and see if there is another solution.
Nova – Alpha 1
I have given Nova, my graphics engine, a milestone named “Alpha 1”. This means that the engine will need to fulfill certain conditions at a certain time.
The good news is that I might release it to the public for free (including source) if I find the quality good enough.
I have been using Nova for quite sometime now for my graduation project and except for a few small issues (the math part is not to my liking) I have absolute no issues with it. This is rare since I tend to over criticize my work (I once argued with my teacher after getting the highest possible score because I felt the need to point out the mistakes I made).
If your expecting a fully fledged engine with scene graphs, model loaders and million of other features I think I will have to disappoint you. Nova was intended to be an engine that was in the first place easy to use and in the second place was flexible. At the current state it is a perfect engine for rapid prototyping and highly specialized low level work. But that might change with later iterations. After all this is only the first release (an alpha to boot).
Anyway I will post more information about the possible release over time. The milestone for now has been set on 1 may of 2010.
“Ordered Programming” Technique
When it comes to programming there many, many techniques. One of the techniques I tried is ordered programming. The idea behind it is that quality is assured and nothing can be forgotten. While I was rewriting Brick I decided to use this technique. What it basically does is that you write a “TODO” in the code, for example: “Load configuration file”. And when you run the application in debug mode, it will break when it arrives at that point.
As you can see this has one huge disadvantage and that is that in order to run the entire program all the “TODO” have to be resolved. There is also one huge advantage which is that after you have done something, you will change “TODO” in to “DONE”. The next time you go trough your application, you will notice that the code has now been documented. This is a huge advantage when you write large pieces of code, which you will rarely though in the near future.
However the disadvantage, having to program in the order that the program runs might be too big in some cases. For example some features will be added later as they are not needed now. For example you have written a OBJ loader that handles triangles, but not quads, in this case you might not want to write the quad loading code just yet as you have to focus on the rendering part. For that reason I have decided to add levels, the lower the level the higher the priority. You can think of it in terms as first todo, second todo, etc. If you reach a stage in your development, where you have done all first todo’s, you just increase the number and see where it breaks then.
Here is an example of how it looks when you are writing todo’s.
1 2 3 4 5 6 7 8 9 | #include "OrderProgramming.hpp" int main(int argc, const char** argv) { ORDER_PROG_TODO("Setup Memory Checkpoint", 0); ORDER_PROG_TODO("Initialize graphics engine", 0); ORDER_PROG_TODO("Run the game", 0); ORDER_PROG_TODO("Shutdown the graphics engine", 0); ORDER_PROG_TODO("Check if there are memory leaks", 0); } |
And here how it looks at a later stage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <crtdbg.h> #include "OrderProgramming.hpp" class IGraphics; IGraphics* InitGraphicsEngine(); void GameFunction(IGraphics* graphics); int main(int argc, const char** argv) { _CrtMemState memstate; _CrtMemCheckpoint(&memstate); ORDER_PROG_DONE("Setup Memory Checkpoint", 0); IGraphics* myGraphics = InitGraphicsEngine(); ORDER_PROG_DONE("Initialize graphics engine", 0); GameFunction(myGraphics); ORDER_PROG_DONE("Run the game", 0); if(myGraphics) delete myGraphics; ORDER_PROG_DONE("Shutdown the graphics engine", 0); _CrtMemDumpAllObjectsSince(&memstate); ORDER_PROG_DONE("Check if there are memory leaks", 0); } |
And here is the header you will need to include:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | // OrderProgramming.hpp // Generic functions can be defined before inclusion // - ORDER_PROG_DEBUGBREAK: Breaks the process if possible // - ORDER_PROG_ASSERT: Tries to assert a function // - ORDER_PROG_OUTPUT: Debug output function #ifndef __ORDER_PROGRAMMING_HPP__ #define __ORDER_PROGRAMMING_HPP__ #define ORDER_PROG_STRINGIFY(x) #x #define ORDER_PROG_TOSTRING(x) ORDER_PROG_STRINGIFY(x) #define ORDER_PROG_FILE __FILE__ #define ORDER_PROG_LINE ORDER_PROG_TOSTRING(__LINE__) #define ORDER_PROG_HERE ORDER_PROG_FILE"("ORDER_PROG_LINE") : " #ifndef ORDER_PROG_DUMMY # define ORDER_PROG_DUMMY() {(void)0;} #endif // Lower than this level will cause the order to be taken in account #ifndef ORDER_PROG_LEVEL # define ORDER_PROG_LEVEL 1 #endif #ifndef ORDER_PROG_DEBUGBREAK # if defined(_WIN32) # include <intrin.h> # define ORDER_PROG_DEBUGBREAK __debugbreak(); // # elif (???) # else # define ORDER_PROG_DEBUGBREAK {__asm{int 3};} # endif #endif // Platform independent (most of the time) #ifndef ORDER_PROG_ASSERT # include <cassert> # define ORDER_PROG_ASSERT(expression) assert(expression); #endif // Platform dependent #ifndef ORDER_PROG_OUTPUT # if defined(_WIN32) # include <windows.h> # define ORDER_PROG_OUTPUT(output) OutputDebugStringA((output)); //# elif (???) //# define ORDER_PROG_OUTPUT(output) ::std::clog << (output); # else # define ORDER_PROG_OUTPUT(output) ORDER_PROG_DUMMY() # endif #endif #ifdef ORDER_PROG_NOBLOCK # define ORDER_PROG_BLOCK(reason) ORDER_PROG_DUMMY(); #else # define ORDER_PROG_BLOCK(reason) ORDER_PROG_ASSERT(0 && (reason)) #endif #define ORDER_PROG_TODO(reason, level) { if((level) < ORDER_PROG_LEVEL){ ORDER_PROG_DEBUGBREAK; ORDER_PROG_BLOCK(reason) } } #define ORDER_PROG_TODO0(reason) ORDER_PROG_TODO(reason, 0); #define ORDER_PROG_DONE(reason, level) { if( (level) < ORDER_PROG_LEVEL) { static bool _once=true; if(_once) { ORDER_PROG_OUTPUT(ORDER_PROG_HERE); ORDER_PROG_OUTPUT(reason); ORDER_PROG_OUTPUT("\n"); _once = false; } } } #define ORDER_PROG_DONE0(reason) ORDER_PROG_DONE(reason, 0) #endif // __ORDER_PROGRAMMING_HPP__ |
Learning Java
Recently I had a small discussion with a friend about what language he should choose for a personal project of him. He was doubting between Visual Basic and C#. I believe he decided to go with C# as that would be compatible with Mono (a cross platform variant of C#) and well, an other reason was that is another language the he normally used.
Personally I don’t like working in VB as it has some, not quirks, but behavior issues that don’t match my style of programming (Comments are done different for one).
I do like C# a lot as it looks like C++ (call me shallow if you want), but I have worked in that language for quite some time at my previous job and I found that the Graphical User Interface used by Windows (.NET components) to be a little bit lacking. Now I could buy a professional components (like Devexpress) but I’m not that rich and frankly my experience with it has not yet been enjoyable, most likely because I’m still getting used working with (at my job).
Recently my interest in Linux has been rekindled thanks to Linux Mint and I decided that if I’m going to write software I don’t wish to be limited to one platform. By the way, I only share this view as a software developer and not as a game developer.
After some thinking I decided to try out Java. The criticism that I had about Java 6 years ago have all been gone, so I decided to check it out again.
I have been playing around with it that past three days (1~2 hours each day) and I’m enjoying. Eclipse is an awesome IDE and the only thing I really find lacking is the fact that I can’t drag and drop components as it is done in VS2008. Not that I mind because creating a form (a shell in Java) is quite easily done. I still have a lot of thinking to do about the application I want to write (it’s one of those applications you try to write from time to time) and see if I can actually get it to work.