• ### Announcements

• entries
359
237
• views
188157

Current topic is the development of a toy language to test a few syntax ideas.

## And so it goes...

I've decided to let my gamedev subscription lapse. The main thing I used it for was for this sounding board to discuss my personal programming projects. It's been a few months since I last posted, and over a year since I've done any actual game related discussion. And even over the years I never really saw a lot of feedback or discussion that really made the posts satisfying. With my workplace gearing up for a big release, I probably won't have the time or motivation for personal projects until that's done.

I'll still be around, but I just can't justify paying the (relatively small) cost of the subscription for a cartoon cow and a journal I rarely use; especially these days. Perhaps I'll re-up once I start doing more development again, or get more disposable income.

## Tangent, now with usable error messages!

In a long overdue effort to get people to actually use the software I write, the Tangent rehash now provides line numbers with error messages. w00t!

public goose class foo{	public bar x;}

moocow.tan(2,9) : error : Unknown Type 'bar'

## Back to work!

My vacation is over. It's useful to remind myself every so often why I don't travel and hate people. I did take some time and think about what I wanted to use my spare time for. In the end, I decided to use it for whatever the hell I want. So I'm going to try to do a quickie rewrite of Tangent with lessons learned and a renewed attempt at Moe. Ideally, two projects will keep me busy coding and provide enough varied problems that I don't get stuck on one problem where I don't have enough time/intelligence/skill to get through it.

First, a rehash of Moe. Moe is a placeholder name for a game I've had in mind for a little while now. It is a fantasy, turn based, 4x game. The closest game to it is probably Master of Magic. The main differentiator in the game idea is a de-emphesis on warfare.

The idea is to keep warfare viable, but not the hands down best way to win. Push it towards the desperate player or the player who needs might to make up for other failings. The two sides of that goal are fairly straightforward; make war expensive, and make peace an alternative means of conquer.

The key way war is being made expensive is by making units finite. Your population will be born, grow old, and die (and occasionally be raised as zombie labour). Losing population in even a successful war (or the lack of births due to wartime) is a non-trivial cost.

Making peace a means to conquer is more tricky. Civilization tried to push culture as an option, but it was always trumped by war. Too slow, too narrow. Moe aims to push the general concept to a bit more wide-ranging scope. Technologies will flow naturally towards more successful trading empires. Heavy trade increases happiness and desirability of your cities. Technological superiority breeds dissention in your neighbors. Desirability will allow you to siphon off immigrants from neighboring empires. Expansive trade routes allow armies to range farther and fight longer.

Unfortunately, it seems that XNA/SlimDx UI libraries are few and far between. reimplementing a UI isn't something I'm looking forward to. So at least for now, most of my energies are focused on Tangent again.

Mostly, this Tangent rewrite is going to take lessons learned and help make it more solid from the get-go; slim it down. Properties are out. Indexers are out. (just use phrases to implement them). Tuples are out; generics are de-emphasized. Must get users! Error reporting must be there from the start.

The biggest change though is probably going to be actually compiling into something. The last implementation was just way too slow. I spent a bit too much time futzing about reinventing the VM wheel. I've not decided what I'm going to compile into yet. Maybe C# and then recompile that. The DLR seems an interesting option but the docs are poorly written and/or over my head. Straight to MSIL seems to be the least pleasant option.

Multiple dispatch and the funky type system are the biggest things that are out of the ordinary for other VMs. Something I'm going to need to think about before I set to work on that. For now though, the parser(s) have been slimmed down and better (but still terrible) error reporting run through them. A basic compiler shell is there with better (and actually not bad) error reporting. The in-memory type representations are mostly back, with a few decisions yet to be made.

More to come as it gets done.

## Tangent: Haitus

I realized the other day while working on something barely non-trivial in Tangent just how shoddy the performance is. Memory usage, leaking, actual execution speed... And there's not a few things that are just bleh in the project itself. Combined with the lack of actual active interest by anyone who isn't me, and the renewed desire to work on other things I'm going to put Tangent down for a little while. Maybe make the source available once I clean out the worst of the memory leaks. Not that I won't come back to it, but I think other projects might do me a little good. Maybe even make Game Development posts on gamedev.net!

And at the very least, Tangent has done exceptionally well in its original goal of being a learning experience and a prototype. While it's fresh in my memory, some post-mortem like review:

### Good Stuff

Phrases - This has a few parts; infix parameters, explicit identifiers, juxtaposition as application... This is 'what makes your language different?'. It was very powerful, elegant, intuitive. What else do you want?
Order of Operation inference - I personally never encountered woeful mis-inference. It wasn't super slow. It allowed certain syntaxes that were pleasingly unencumbered by decoration.
Multiple Dispatch - Especially with phrases, this had little downside, while freeing programmers from the 'to specialize you must sub-class' bindings.
Structural Typing - Allowed a certain flexibleness when doing design that was unfortunately lessened by free functions. Still, it seems the way to go.
Goose Types - And being able to put a 'normal' class hierarchy ontop of the type system with a mere keyword is elegant and useful.
Multiple Inheritance - No headaches when actually implementing it (beyond constructor vs invariant fights). I still think this will be a boon once more OO is done with the system.

No Community - No input, no people actually using the language pushing for quality or certain features, or just debating the utility of some ideas.
The Runtime - it was easy to implement, but took a little time with certain concepts, and the major source of performance issues. Plus there's a few little things I forgot, or did a shortcut with that are awkward now. It should've gone to the CLR (at least indirectly) as the original concept envisioned.
Too much backwards compatibility concerns - Originally, C# was to be more integrated. This provided a few too many constraints on design and too much demand on the naive way about how it should be implemented.
Tuples - Annoying, special case sort of constructs.
Visibility - The language tended towards 'classes are just records' style design, which didn't need or rely on visibility too much. Protected becomes kinda moot once inheritance acts oddly. And visibility mismatches created not a few type-mismatch bugs.
Generic Replacement - The implementation of generics replaces (or actively binds) instances into parameters rather than just referencing them. Big, ugly. Generics themselves are still of dubious utility.
Idealism around certain bits - Types really should know where they are declared. Scopes should be smarter about saying what things should be. A dozen other little things.
Little separation between instance generators, type classes, and type constructors - This one is inherited from C, but it becomes more apparent during implementation just how broken the idea of using a single concept to describe 3 separate purposes.
No 'abstract type' for operations - In the OO world, there's the concept of abstract types. 'This type can do X, Y, and Z'. Functions lack an analogous concept. 'My parameters can do (or must be able to do) these ops.' Dynamic languages kinda get away with it by just doing the operations. Type inference models can do that via that path. Originally, operators were going to be members (ala Java) and thus an abstract class could imply what operators worked. Tangent unfortunately needed that concept, but it was not included in design.

And next week I am on a cruise. Some nice R&R time to consider what I'd like to work on. Maybe more Tangent, maybe a new 'with lessons learned' implementation, maybe another go around at my fantasy 4x game idea. We'll see then.

## Tangent: Syntax reworking

I'm looking to do a little reworking in the Tangent syntax, parser and compiler. Right now there's a little too many exceptional cases. The main candidates for removal are properties and indexers. They'll still exist, but will be done in the phrase style. No more special cases.

The first step is done. I took the degenerate case where a method has no parameters, and made that a simple getter:

public static    pi => decimal { return( 3.141592 ); }

Setters will look a little odd, but similar to other phrase definitions:

public class SetExample{    private int _foo;    public     Foo = (int value) => void{        _foo = value;    }}

The behavior of each isn't really going to change, just consolidating the syntax and compiler a bit.

It took a week of dirty dirty hacks, but I got that not example working from the last post. I'm sure I broke 2-3 things somewhere, and it runs like a dog, but it works. Oh, and it's far more ugly and verbose than I'd like. The final working test code:

public static generic(Exists P)		  not( P infix -> bool predicate ) => P infix -> bool{	return(   		(P arg) infix => bool{			return( not (predicate arg) );		}	);}public static	main()=>void{	print (5 not equals 4);}

Instead of defining equals and not equals we define not to take any binary predicate and modify it to be the inverse.

More work will be needed to make the generic referencing more robust, and some stuff to make the language more flexible about where generics can be used in declarations.

But this is the general idea behind some of the language features. Expressions behave more like linguistic expressions, where bits can be combined together, and modified in pieces rather than mathematical expressions which so poorly describe the problems faced by modern programmers.

## Tangent: Pattern Matching, Part 1

Yet more motivation yesterday, so more Tangent advances. That, and the example code today is pretty sexy so stick around!

The first minor fix was finishing off the last little bug with generics. The basic stuff (generic fields in a type, generic parameters inferred in a method, method referring to the type's generic params) now work.

The second addition was syntax to allow for explicit declaration of generic parameters for a method. The normal (quick) way to declare a generic method in Tangent is something like:

public foo( arg) => returnVal

The explicit syntax allows for a method modifier with the syntax:

generic(generic-declaration-list)

which then looks like:

public generic(Any T) foo(T arg) => returnVal

A little less typing if you're going to refer to T a lot. It also allows for a little easier use when you need not just T, like if you want to infer the parameter from List.

The third bit of work was making sure that the type inference code I have is smart enough to deal with non-trivial inference. It turns out that it was smart enough, with no changes. Thus you can get a basic sort of pattern matching now within the language.

Which leads us to the example code of the day. Printing the default type value is currently the best way to properly detect that the types were properly inferred.

public class    Printable{	public abstract 	ToString() => string;}public static 	default => T{	local T rtn;	return(rtn);}public static generic(Printable P, Printable R)		   TestInfer( P -> R  method ) => void{	print default;	print default;}public static 	main()=>void{	TestInfer( (int x)=>decimal{} );}

In the real world, this sort of thing will more often be used for method modifiers, such as this not modifier (that does not quite work yet):

public static generic(Exists P)		not( P -> bool  predicate ) => P -> bool{	return(   		(P arg)=>bool{			return( not predicate arg );		}	);}public static	main()=>void{	print (5 not equals 4);}

## Tangent: Pattern Matching, Part 1

Yet more motivation yesterday, so more Tangent advances. That, and the example code today is pretty sexy so stick around!

The first minor fix was finishing off the last little bug with generics. The basic stuff (generic fields in a type, generic parameters inferred in a method, method referring to the type's generic params) now work.

The second addition was syntax to allow for explicit declaration of generic parameters for a method. The normal (quick) way to declare a generic method in Tangent is something like:

public foo( arg) => returnVal

The explicit syntax allows for a method modifier with the syntax:

generic(generic-declaration-list)

which then looks like:

public generic(Any T) foo(T arg) => returnVal

A little less typing if you're going to refer to T a lot. It also allows for a little easier use when you need not just T, like if you want to infer the parameter from List.

The third bit of work was making sure that the type inference code I have is smart enough to deal with non-trivial inference. It turns out that it was smart enough, with no changes. Thus you can get a basic sort of pattern matching now within the language.

Which leads us to the example code of the day. Printing the default type value is currently the best way to properly detect that the types were properly inferred.

public class    Printable{	public abstract 	ToString() => string;}public static 	default => T{	local T rtn;	return(rtn);}public static generic(Printable P, Printable R)		   TestInfer( P -> R  method ) => void{	print default;	print default;}public static 	main()=>void{	TestInfer( (int x)=>decimal{} );}

In the real world, this sort of thing will more often be used for method modifiers, such as this theoretical not modifier:

public static generic(Exists P)		not( P -> bool  predicate ) => P -> bool{	return(   		(P arg)=>bool{			return( not predicate arg );		}	);}public static	main()=>void{	print (5 not equals 4);}

## Tangent: Generic Method Bodies, Part 2

Yet more time yesterday, so more work into generic methods. On the slate was type parameters, using generic references as the return type and resending the generic to another generic. The example code is fairly simple today; a in-source re-creation of the C# default keyword.

Remember that Exists is the super-type of everything and that Any is the super-type of everything but void. And local variables are default initialized out of the box.

public static    default => T{	local T rtn;	return(rtn);}public static    factory => T{	return(default);}public static    main()=>void{	default<void>;	print default<int>;	print default<decimal>;	print default;	print factory<int>;	//factory;	// error as expected.}

## Tangent: Generic Method Bodies, Part 1

Had some time yesterday, and more importantly some motivation. So continuing along towards reusable user-defined properties, I set into making generic methods work. The type inference had been done for a while (so I thought!) so this work mostly involved doing proper name resolution, and providing a runtime mechanism to retrieve what the generic was set to.

Alas, type inference was not quite done. The compiler did type inference properly, but the runtime didn't actually bind the runtime type to the generic type variable. So the compiler essentially guaranteed that it would run, but I ran into trouble once I tried to see what T really was.

So that fix was a bit awkward and annoying. The other mild problem was how the variable lookup was done. There is two parts to it really. The first is finding the generic itself so that the type-checker can reason about what it could possibly be. The second is actually looking up what runtime type it has so that it can be instantiated or otherwise worked with. Those are unfortunately a little awkward to do together. The end result was a little hackery to deal with inferred type parameters which were the main sticking point.

The test code of the day:

public class foo{	public abstract ToString()=>string;}public static print2( arg)=>void{        //local T tmp = arg;         // 55 - make sure that assignment/type checking works.	//local T tmp;               // 00 - make sure that the default initializer is smart about identifying the type.        local T tmp = new T;         // 00 - make sure that new T can be identified and dealt with	print tmp tmp;}public static main()=>void{	local foo aFoo = 5;	print2 aFoo;}

## Tangent: this-properties part 2.

I'm not sure what the deal is with my new car. It must look fast or something. I've gotten pulled over for speeding more times in the past week with the new car than I did in 9 years with the old. Warnings only thankfully. It's not as though I'm driving any differently. Seriously troopers, it's probably not the best use of your time to profile against station wagons; even sporty ones.

Anyways, the last example I posted on this-properties was not quite complete. If you tried to assign one to another, you'd get an ambiguity error since the compiler couldn't figure out if you wanted to assign the property or the contents. So I quickly implemented an Unassignable type, that essentially prevents a reference from being modified after it's been allocated. Useful for this exercise, and for a few others I'd imagine. It'll cause some weirdness once template methods are implemented, but I'll deal with that bridge when it comes along.

The test code for this change (pardon the poor naming):

public class   field: Unassignable{	private int x;	public int this{		get{			return(x);		}		set{			x = value;		}	}}public static main()=>void{	local field n;	local field p;	p=5;	n=p;	print n;}

Before, the 2nd to last line would cause error. Technically the declaration/initialization itself would produce error since it goes through and does proper assignment as well.

## Tangent: this-properties

Just a little work today. I implemented something mildly weird. For those familiar with C++, Tangent supports a form of operator(). It's something like:

public class foo{    public    this()=>void{        print "foo!";    }}public static    main()=>void{    local foo aFoo = new foo;    aFoo();}

And it's used for the normal sort of functor stuff that C++ uses it for.

What I implemented today was a similar beast for properties:

public class intProperty{    private int x;    public int this{        get{            return(x);        }        set{            x = value;        }    }}public static    main()=>void{   local intProperty x = new intProperty;   x = 5;   print x;}

Not very thrilling. Eventually though, I want to use this mechanism to create stuff like reusable properties that have an event that is triggered when the setter is called. It'll also be used for things often relegated to implicit conversions.

I need to figure out a good way to allow the programmer to disambiguate member access on the property rather than it's value member access, but that's not a huge priority. I don't expect that scenario to arise often.

## Tangent: Visibility, Part 2

Part 1

The change so that private fields get their own storage is finished. I need to review some of the other visibility related things (name lookup for standalone fields, method dispatch) to finish that snippet of changes. Then will be a release.

A nice side effect of this private field storage change was fixing the bug that prevented static member's initializers from working.

The test/target code for the change:

public class foo{	private int x = 4;	public   foox()=>void{		print x;	}}public class bar{	private int x = 9;	public   barx()=>void{		print x;	}}public class foobar:foo, bar{}public static	main()=>void{	local foobar tmp = new foobar;	tmp.foox();	tmp.barx();        // print tmp.x;       // properly does not compile.}

[edit: Also, some documentation regarding the algorithm used to infer the order of operations.]

## Tangent: Visibility

Sorry for the lack of updates. My car died the end of last week, so a lot of my time went to procuring and then enjoying a new car. To be honest, mostly to the enjoyment; buying it was quick and painless. 2009 Impreza 5-door (non-WRX) for those curious. It is a retired rental car, so I got it at a steep(~\$5k) discount in exchange for it having ~9000 miles on it.

Today though I started a new batch of work on Tangent. In previous revisions, the language pretty much ignored visibility (public, private, etc). So today's work was a start down the road towards correcting that. Unfortunately, that sort of thing touches code everywhere. Worse yet, looking back at some of the code and thinking about it, I realized that method groups weren't being dealt with properly with regards to visibility. So there's that.

The other thing that needed to be done was being more intelligent regarding variable storage. A co-worker of mine pointed out that private variables shouldn't cause trouble if inherited. They should be invisible with pretty much every regard to the inheriting class.

This makes sense, but was not something I had in mind when devising the type system for the language. Inheritance just takes the definitions within the base class and imports them up to the inheritor. Definitions with the same name got mixed to provide the superset of the two. That breaks down when the two definitions should be totally distinct values...

The work today is taking allowing that private variable isolation. It is mostly done, just some initializer quirks to clean up.

## Tangent: Refactoring, Part 1

The first part of my bulldozer refactoring is complete. The core library for Tangent was a mess because I just added things along as I went. Plus I'm lazy and don't have great practices even when I'm not lazy. As a result the core library was 7200 lines of code across a mere 8 files. Bleh! Even I had gotten tired of the big cluttered files.

So I went through and focused on simply reorganizing things. No major code changes, just cut/paste into a new solution. The thing at least looks vaguely professional at first glance now. The solution is up to 7700 lines in 120 code files, nicely organized into a sane directory structure.

The next step is renaming some of the things that are not-consistent across the project. After that will be additional comments and actual logical re-arrangement.

## Tangent: Using

After exceptions work, the next step is the using block. The syntax is slightly different from C#. You can't have two elements in a using block (I forgot/didn't know you could even do that). And you can't just put a single expression after the using bit. Only the actual block, or another using segment. I was lazy and didn't implement the null check in the generated code (bug #77), so don't using something and then set it to null or fubar the initialization.

Probably now I'll go for a little release to get basic Dictionary importing, exceptions and using available (even if no useful .NET disposable objects are available yet...). Then it's time for bulldozer refactoring. The organization of the code is absolute trash, even without considering all the places where it's just architected oddly.

[edit: and there you go, version 0.29]

Test code of the day:

public class foo{	public    Dispose()=>void{		print "disposing...\r\n";	}		public virtual    Moo()=>void{		print "moo.\r\n";	}	}public class bar:foo{	public    Moo()=>void{		print "MOO!\r\n";	}}public static main()=>void{	using( foo x = new foo )	using( foo y = new bar )	{		x.Moo();		y.Moo();	}}

## Tangent: Exceptions

Spent some time last night implementing exceptions. Nothing too special here. try/throw/catch/finally as they exist in C#. Under the hood, I just use the existing exception mechanism to do the delivery, and wrap exceptions so that the runtime can distinguish between exceptions raised in the runtime and exceptions raised by the runtime.

The two main differences are that you may throw any object, not just exceptions and that the catch parameters use Tangent types. So the catch uses structural subtyping to see if an exception matches it. Not big changes, just little things to keep the feel of the language consistent.

The example test code of the day:

public static	main()=>void{	try{		//throw "moo.";		//throw 42;		throw 3.14;	}catch(string speech){		Console.WriteLine(speech);	}catch(int count){		Console.WriteLine(count);	}finally{		Console.WriteLine("finally.");	}}

## Tangent: Dictionary Iteration

Dictionaries are now iterable in working code. .NET reflection is odd when it deals with stuff that is generic, but does not itself contain the generic parameters. It never really seems to refer to the source of the parameter, and once bound you can't really tell that one was there. This didn't interact well with how generic types are stored in Tangent.

Keys and Values still don't work, but general iteration works. Baby steps...

## Tangent: Dictionary

I think that I'm going to do a little polish/bugfixes for an intermediary release. Then there's going to be a pile of refactoring that needs to be done. After that I think that I might aim to get Tangent (optionally) compiling into C# source rather than the current hacky VM. The VM is dog slow, albeit mildly easier to debug.

Anyways, Dictionary now imports into Tangent mostly. The basics are there (Add, Remove, Count, Contains, the indexer), but KeyValuePair is odd in its reflection signature. The most notable impact is that AddRange doesn't import, and the Dictionary cannot be iterated through. (bug #76)

## Tangent: Code Examples

The documentation process continues. I went through a good number of the Rosetta Code entries, and created Tangent example code wiki entries for them. There's now about 8 times the example code entries for random programmers to view (even if most of them a simple, isolated feature examples).

## Tangent: Statement Documentation

Busy weekend. Took some time to update and flesh out the documentation, finishing out statements. As always, feedback from various levels of programmers is helpful.

## Tangent: v0.28

I took some time tonight to nuke the last showstopper bugs so that I could make a midpoint release of Tangent. There's still plenty of bugs, but it'd been too long since a release. Not that anyone is downloading it, but one less excuse.

- A tiny command line compiler to the package for the mono users.
- The new Arguments => ReturnType syntax for methods.
- Better support for phrases.
- Support for operator symbols in phrases.
- Auto identification of infix methods.
- Support for generators via the yields return type.
- Better support for generics (still mostly broken).
- Better support for .NET imports.
- The -> type operator to succinctly define delegate types.
- foreach.
- The 'to' generator for ranges of integers.
- Improved support for 'this' methods.
- Statics (still a little flakey)
- Enums (barely implemented)
- ... and the usual assortment of bugfixes and random improvements.

Documentation on the site is still old. I'll take care of that in the coming week or two. Error messages are still terrible, and debugging info non-existent. That'll take a bit longer.

## Tangent: Buggery 2

Not much Tangent work recently. Partly I've been demotivated by work demotivation. Partly I've been caught up in a game playing swing. Partly, I've been dreading a bug that I found. The reference code is a slightly modified version of the dice example app:

public class	RollOfDice{	public delegate		Roll()=>int;	public static		Random rng = new Random;	protected int minimumValue;	public int MinimumValue{		get{			return(minimumValue);		}	}	protected int maximumValue;	public int MaximumValue{		get{			return(maximumValue);		}	}	public static	Create( void -> int  rollProcedure, int min, int max) => RollOfDice {		local RollOfDice rtn = new RollOfDice;		rtn.Roll = rollProcedure;		rtn.minimumValue = min;		rtn.maximumValue = max;		return(rtn);	}	public static	CreateDie( int sides ) => RollOfDice{				return( 			// bug #67, 			//  static scoping needs full path.			RollOfDice.Create(				()=>int{ return( (RollOfDice.rng.Next(sides))+1 ); },				1,				sides			)		);	}}// bug #65, custom initializer is fubar'd//public RollOfDice d20 = RollOfDice.CreateDie(20);public RollOfDice d20;public RollOfDice d12;public RollOfDice d10;public RollOfDice d8;public RollOfDice d6;public RollOfDice d4;public static   create dice => void {	d20 = RollOfDice.CreateDie(20);	d12 = RollOfDice.CreateDie(12);	d10 = RollOfDice.CreateDie(10);	d8 = RollOfDice.CreateDie(8);	d6 = RollOfDice.CreateDie(6);	d4 = RollOfDice.CreateDie(4);}public static	roll (RollOfDice die) => int{	return(die.Roll());}public class	int{	public 	this (RollOfDice die) => RollOfDice{		local int min = die.MinimumValue * this;		local int max = die.MaximumValue * this;		local void -> int 	newDie = ()=>int{			local int rtn = 0;				foreach(int count in 0 to this - 1){				local int dieroll = roll die;				print "Rolling one " die.MaximumValue " sided die: " dieroll "\r\n";				rtn = rtn + dieroll;			}			return(rtn);		};		return(RollOfDice.Create(newDie,min,max));	}}public static	main()=>void{	create dice;		print roll 3d6 "\r\n\r\n";	print "10 d20:\r\n";	foreach( int x in 0 to 10 ){		print roll d20 "\r\n";	}}

The problem was that the VM wouldn't run both of the dice rolls in main... If I commented out the roll 3d6 line, the d20's would roll. If I commented out the d20's then the 3d6 would roll. If I left them in, then too the 3d6 was the only thing run.

The problem eventually turned out to be in the generator for n to m. The cursor was not being reset for the yieldy bit of code, so it could only be run once before it always reported done. The auto-generated GetEnumerator call now also triggers Reset so that doesn't happen. Yay.

I unfortunately don't expect too much more progress in the near future. Once motivated I want to really focus on cleaning everything up for a release. Something I can actually show off to people and garner more interesting feedback than "it doesn't work."

## Programmer Archetypes?

I recently got back into playing Magic: the Gathering after some years away. The game has improved greatly since my departure, so kudos there but not what prompts a journal entry today.

Something I found out about today was this article by one of the card designers that describes the three archetypes of magic players that they've identified and catered to. Very similar to the mud/mmo paper that I hope you're all familiar with.

There is though one notable difference. The heart/spade/diamond/club paper describes mostly what people like to do in the game. It focuses on needs that the game designer must fill. The magic archetype model focuses more on what tickles certain players' fancies since each player essentially follows the same process while playing a game.

And mulling it over today, I thought that the magic article describes a fairly good model for programmer archetypes. It makes sense, since the game was originally designed to help teach certain programming concepts. For those that didn't read the article, here's the quick and dirty:

Timmy: This player likes to make a splash. They don't look at downside so much, just powerful effects, solid creatures. Swarming and going overboard are good too. A memorable victory or particularly gruesome overkill will make up for a number of losses.

Johnny: This player likes to create things. They won't ever just take a deck from someone else or the internet. Taking the various cards and creating something unique or under some odd constraints is often more fun than the game. Getting a quirky deck to beat the 'consensus pick' will make up for a number of losses.

Spike: This player likes to win. They are happy playing the same deck over and over again, as long as it wins. They're also happy to take decks off the internet or just buy their way to victory. A win is a win to these players; losses are unacceptable.

And of course these things are just stereotypes. All players fit somewhere between these extremes, with a certain mixture of each.

Each trait lines up with programmer behaviors that are beneficial, and some that become vices when taken to the extreme. Looking at programmers I've known, they too tend to fall towards these categorizations.

Timmy: The programmer that likes power. They probably got into programming to 'make the computer do things' or show off to friends. They love automating things, making flashy apps (that maybe don't work quite right). Their vices are feature creep, over-engineering, and occasionally an unreasonable tendency towards low level programming. They provide vision for a project, and at their best are the team members who just extrude useful little tools and enhancements.

Johnny: The programmer that likes creation. They probably got into programming as an artistic outlet, or as a natural expression of problem solving. They love creating new things and solving weird problems. Their vices are 'not invented here' syndrome, being too 'clever' while programming, and occasionally a lack of attention to mundane or practical concerns. They provide solutions for a projects, and at their best are the ones who solve the hardest problems and create the most elegant solutions.

Spike: The programmer that likes production. They probably got into programming as a means to an end; perhaps to make a mod or automate some task. They love getting things done, now. Their vices are disregard of usability, cutting corners with testing/maintenance, and occasionally low technical knowledge. They provide drive to a project, and at their best are the team members who pump out code and keep the rest in line with their uncompromising demand for production.

The most interesting thing I at least noticed was that an ideal team requires a mix of these sort of archetypes to keep each other in balance. Another thing I noted was that none of the archetypes was the 'perfectionist' sort. Someone who desired quality over anything else. That would mean that programmers by default don't like testing, which I'm not sure is true. It might be that there's a style of programmer missing from here. In the original article, the sort who needs to tune and refine something to the best it can be falls into the Spike archetype. Perhaps then that should be split into the 'make stuff, be proud of it' and the 'hacker' archetypes.

I'm not sure. What do you think?