General Question

gorillapaws's avatar

Programmers: Thoughts on this AI game where you fight via code?

Asked by gorillapaws (30865points) September 26th, 2015

I’ve stumbled upon this cool game (didn’t work in Safari, but did in Firefox) that’s in development. Essentially you write the code for the AI and then your “bots” fight the computer’s bots. You have no control over the game once you run the simulation.

I’m teaching myself a bit of JavaScript so I can mess around with it in the browser. Anyone willing to give it a go and share their code/thoughts?

Observing members: 0 Composing members: 0

49 Answers

Zaku's avatar

Hmm. Cool, but so far I’m stuck on the tutorial with some sort of error:

> Game.mothership.moveTo(1,0)

undefined

Zaku's avatar

Ok, that wasn’t an error. It’s just that 1 is a very small distance.

However, now I’m trying the example at the point where you program your drone, and it’s giving me an annoying error expecting a ”:” for no reason, when I try to do the step that adds a random chance.

Zaku's avatar

Ok… I figured that out. I’d somehow made a typo.

Zaku's avatar

This is rather cool, though I don’t think I have time to really get into it.

gorillapaws's avatar

@Zaku Well if you find the time to mess with it some more, post back here. I don’t really know JavaScript so I’m pretty terrible. The best I can manage is a stalemate sometimes on level 1. It could be fun to build a team-Fluther AI :)

Zaku's avatar

Ok, but I keep running into annoying syntax errors. Can you show me an example of an AI with more than one handler? I entered what should be a good Harvester AI, but it tells me it’s expecting a closing } to match the { on line 3, when I try to start my second handler:

(Sadly, Fluther seems to have no way to format an entire block of code, so bye-bye indenting…)

// Harvester.js

var _droneController = {

onTick: function() {
if (!this.harvesting && Math.random() < 0.05) {
var direction = 2 * Math.PI * Math.random();
this.drone.moveInDirection(direction);
}
}

onMineralEntersVision: function(mineral) {
if (!this.harvesting) {
this.harvesting = true;
this.drone.moveTo(mineral);
}
}

onArrivesAtMineral: function(mineral) {
this.drone.harvest(mineral);
this.drone.moveTo(Game.mothership);
}

onArrivesAtDrone: function(drone) {
if (drone == Game.mothership)
{
this.drone.giveMineralsTo(drone);
this.harvesting = false;
}
}

};

gorillapaws's avatar

@Zaku you need ”,” after each handler block ( after the closing ”}” ). I’ll post my files in a sec (they have problems of their own lol).

Zaku's avatar

Thanks! Got it. I never would’ve guessed that.

gorillapaws's avatar

Yeah I got hung-up on the same problem.

Here’s what I’ve got thus far:
Mothership.js
var _droneController = {
harvesterCount: 0,
droneCount: 0,

onTick: function() {
// your code goes here
// you can access your drone via this.drone
var drones = this.drone.dronesInSight;
var i = 0;
for (i; i < drones.length; i++) {
if (drones[i].isEnemy && this.drone.isInMissileRange(drones[i])) {
this.drone.fireMissilesAt(drones[0]);
}
}

if (!this.drone.isConstructing && this.harvesterCount < 3) {
this.drone.buildDrone(‘Harvester1’, {
storageModules: 2
});
this.harvesterCount++;
this.droneCount++;
} else {

if (!this.drone.isConstructing && this.droneCount % 2 === 0 && this.droneCount < 7) {
this.drone.buildDrone(‘Hunter1’, {
missileBatteries: 2
});
this.droneCount++;
} else if (!this.drone.isConstructing && this.droneCount % 2 === 0) {
this.drone.buildDrone(‘Elite1’, {
missileBatteries: 2,
shieldGenerators: 2
});
this.droneCount++;
} else if (!this.drone.isConstructing && this.droneCount % 2 === 1) {
this.drone.buildDrone(‘Harvester1’, {
storageModules: 2
});
this.harvesterCount++;
this.droneCount++;
}
}
},
};

Harvester1.js
var _droneController =
{
currentHP: 0,

goHome: function()
{
this.drone.moveTo( Game.mothership );
},

onMineralEntersVision: function( mineral )
{
if( !this.harvesting )
{
this.harvesting = true;
setInterval( this.goHome, 25000); // This isn’t working for me
this.drone.moveTo(mineral);
}
},

onArrivesAtMineral: function( mineral )
{
this.drone.harvest( mineral );
this.drone.moveTo( Game.mothership );
},

onArrivesAtDrone: function( drone )
{
this.currentHP = this.drone.hitPoints;
this.drone.giveMineralsTo( drone );

var direction = 2 * Math.PI * Math.random();
this.drone.moveInDirection(direction);

this.harvesting = false;

},

onTick: function()
{
if( this.drone.hitPoints < this.currentHP )
{
this.goHome();
}
},

onDeath: function()
{
Game.mothership.harvesterCount—;
}
};

Hunter1.js & Elite1.js
var _droneController =
{
onTick: function()
{
var drones = this.drone.dronesInSight;
var i = 0;
for( i; i < drones.length; i++ )
{
if( this.drone.isInMissileRange( drones[i] ) && drones[i].isEnemy )
{
this.drone.fireMissilesAt( drones[i] );
}
}
},

onDroneEntersVision: function( drone )
{

var drones = this.drone.dronesInSight;
var i = 0;
for( i; i < drones.length; i++ )
{
if( drones[i].isEnemy )
{
this.drone.moveTo( drones[i] );
}
}
}
};

Zaku's avatar

I have no idea how/why people who write APIs these days decide to give so little documentation. I don’t see any API reference except for the “Cheat Sheet” tab in-game, which doesn’t exactly describe what the functions do or what they refer to. Is OnSpawn() for the object itself, or for any object in the game, or… ?

Zaku's avatar

Ok, I’m gonna steal/use your harvesterCount: 0,
droneCount: 0 naming so we’ll be semi-compatible.

gorillapaws's avatar

@Zaku Yeah, I know what you mean lol. It’s still in early development by one dude, so I think he’s chugging along slowly.

Zaku's avatar

Does your harvesterCount work? It didn’t work for me between classes. I ended up using Game.mothership.harvesterCount in both to get it to work.

gorillapaws's avatar

I works when I call it from within the Mothership class. Wasn’t sure if it was working from when the other classes call it. I tried to add a “harvesterDied()” method in the Mothership class that other classes could call, but that was failing.

Also my harvesters seem to get stuck in corners (not sure why), so I added a “goHome()” method that was supposed to fire off every 30 sec just to reset them. That doesn’t seem to work though.

Zaku's avatar

I’ve got the build queue working, but my drones are not firing at enemies. Does dronesInSight not update every tick?

gorillapaws's avatar

@Zaku Not sure, but “onDroneEntersVision()” seems to work pretty well.

Hopefully we get others joining in.

Zaku's avatar

Ok. I think it was, as usual with JavaScript, a hard-to-see syntax error. length versus size.

JavaScript is always saying “Yeah, sure you can check the non-existent property of that list. LOL.” etc.

gorillapaws's avatar

@Zaku I can’t say I’m crazy about JavaScript. I’ve been programming in Swift lately and in that language, the compiler is ridiculously anal. It can be a major pain in ass sometimes, but it helps catch your stupid mistakes which is nice.

Zaku's avatar

Yeah, I prefer a fairly high amount of anality to apathy. I prefer C++.

I’m not sure what’s wrong now. The dronesInSight seems to update often enough, but my isEnemy tests don’t seem to work, or my list iteration isn’t working, or something. Do your hunters actually fire?

Zaku's avatar

Ah, I see they do. And I see what I was missing. I had just used this. when I needed this.drone. ...

Zaku's avatar

Ok, so I finally made a semi-functional AI. It won once. ;-)

Semi-nice things it does:
* Builds 4 harvesters at first. Will only switch to rebuilding harvesters if they drop to 2. Even so, that might be a bit high for the last tutorial scenario.
* Then builds 2 or 3 Hunters.
* Then builds up to 3 elites.
* The hunters will stay near the mothership until an Elite is in service.
* If an enemy comes in sight of the mothership, all the Hunters will move to attack it unless they are currently attacking something.

Things that would help to add:
* The mothership doesn’t seem to be able to do much else while it is building, so it probably shouldn’t start building until it has enough resources to fully build whatever it’s about to try to build. Yes it may slow down production, but at least it won’t get paralyzed in build mode.
* The mothership should detect when its harvesters are dead, and if so, go searching itself. Actually, the first point above should take care of that, since it’s default is to wander around (though it never does thanks to the build mandate).
* The fighting ships shouldn’t just wander randomly when on the attack. It’s not even a good search method, and there is strength in numbers. They should move in groups of at least 2, and plot longer courses over the map looking for targets, and/or call in others when they find an enemy.
* The harvesters also should at least not get stuck ramming into map edges or each other.
* The harvesters should run away from armed enemies, and let any bored hunters know when they see unarmed enemies.

// Mothership

var _droneController = {

onSpawn ()
{
Game.mothership.harvesterCount = 0;
Game.mothership.hunterCount = 0;
Game.mothership.eliteCount = 0;
Game.mothership.attackerX = null;
Game.mothership.attackerY = null;
},

// this function will be called on every timestep
onTick () {

// Scan for enemy drones:
this.attacking = false;
var drones = this.drone.dronesInSight;
var i = 0;
Game.mothership.attackerX = null;
Game.mothership.attackerY = null;
for( i; i < drones.length; i++ )
{
if (drones[i].isEnemy)
{
Game.mothership.attackerX = drones[i].position.x;
Game.mothership.attackerY = drones[i].position.y;
if( this.drone.isInMissileRange( drones[i] ) )
{
this.attacking = true;
this.drone.fireMissilesAt( drones[i] );
}
}
}

if (Math.random() < 0.01) {
// Random wandering:
var x = Math.random() * 1800 – 900;
var y = Math.random() * 1800 – 900;
this.drone.moveTo(x, y);
}
if (!this.attacking && !this.drone.isConstructing) {
if (Game.mothership.harvesterCount < 3)
{
this.drone.buildDrone(‘Harvester1’, {storageModules: 2});
}
else if (Game.mothership.hunterCount < 2)
{
this.drone.buildDrone(‘Hunter1’, {missileBatteries: 2});
}
else if (Game.mothership.eliteCount < 3)
{
this.drone.buildDrone(‘Elite1’, {missileBatteries: 2, shieldGenerators: 2});
}

}
},

};

// Harvester.js

var _droneController = {

onTick: function() {
if (!this.harvesting && Math.random() < 0.05) {
var direction = 2 * Math.PI * Math.random();
this.drone.moveInDirection(direction);
}
},

onMineralEntersVision: function(mineral) {
if (!this.harvesting) {
this.harvesting = true;
this.drone.moveTo(mineral);
}
},

onArrivesAtMineral: function(mineral) {
this.drone.harvest(mineral);
this.drone.moveTo(Game.mothership);
},

onArrivesAtDrone: function(drone) {
if (drone == Game.mothership)
{
this.drone.giveMineralsTo(drone);
this.harvesting = false;
}
},

onSpawn: function()
{
Game.mothership.harvesterCount = Game.mothership.harvesterCount +1;
// alert(“Now at ” + Game.mothership.harvesterCount + ” harvesters.”)
},

onDeath: function()
{
Game.mothership.harvesterCount—;
}

};

Hunter:

var _droneController = {
onTick: function() {

// Scan for enemy drones:
this.attacking = false;
var drones = this.drone.dronesInSight;
var i = 0;
for( i; i < drones.length; i++ )
{
if( this.drone.isInMissileRange( drones[i] ) && drones[i].isEnemy )
{
this.attacking = true;
this.drone.fireMissilesAt( drones[i] );
}
}

if (!this.attacking && Game.mothership.attackerX !== null)
{
// Defend the mothership!
this.drone.moveTo(Game.mothership.attackerX, Game.mothership.attackerY);
}
else if (!this.attacking && Math.random() < 0.05) {
// If we have no Elites, stay near mothership:
if (Game.mothership.eliteCount < 1)
{
var x = Game.mothership.position.x;
var y = Game.mothership.position.y;
if (Math.random() > 0.67)
{
x += 300;
y -= 300;
}
else if (Math.random() > 0.5)
{
x -= 400;
}
else
{
y += 400;
}
this.drone.moveTo(x, y);
}
else
{
var direction = 2 * Math.PI * Math.random();
this.drone.moveInDirection(direction);
}
}

},

onSpawn: function()
{
Game.mothership.hunterCount++;
},

onDeath: function()
{
Game.mothership.hunterCount—;
}
};

// Elite:

var _droneController = {
onTick: function() {

// Scan for enemy drones:
this.attacking = false;
var drones = this.drone.dronesInSight;
var i = 0;
for( i; i < drones.length; i++ )
{
if( this.drone.isInMissileRange( drones[i] ) && drones[i].isEnemy )
{
this.attacking = true;
this.drone.fireMissilesAt( drones[i] );
}
}

if (!this.attacking && Math.random() < 0.05) {
var direction = 2 * Math.PI * Math.random();
this.drone.moveInDirection(direction);
}

},

onSpawn: function()
{
Game.mothership.eliteCount++;
},

onDeath: function()
{
Game.mothership.eliteCount—;
}
};

gorillapaws's avatar

When I copy/paste and run your code, The mothership just sits there and doesn’t do anything. The code looks right. I’ll keep messing with it.

Zaku's avatar

It might be Fluther did something to it in formatting. For instance, it converts the minusminus to —. ...

gorillapaws's avatar

@Zaku good point.

What’s the deal with your declarations in mothership?
I was declaring them as foo: function(){ ... } and it seems you were doing this for your functions in the other classes, but you used a different technique in your mothership class: just foo(){ ... }.

Zaku's avatar

It can beat single player scenario 1, but not 2. 2 may be very hard without a way to detect which asteroids are small enough to mine, but unfortunately, this.drone.spec.storageModules returns undefined, although mineral.size gives the actual size. (Well, I guess you could hard-code the test value, but that’s no fun. ;-) ) I posted a comment to the dev about this.

gorillapaws's avatar

Got your version working. It was a Fluther issue. It was doing smart quotes instead of dumb quotes. I’ve noticed that when a harvester begins to harvest a node and it is killed, the node becomes unavailable to other harvesters which keep trying to harvest it but aren’t allowed because it’s “in use.”

Zaku's avatar

I made some improvements. At least for scenario 2, where the resources are concentrated in packs with a lot of dead space around. I made it so harvesters remember if they’ve seen a resource they left behind, and immediately go back for it after dropping their cargo.

Scenario 2 is killer though. The AI sends out fast scouts on long-range legs, then tells its killer ships (3 missiles, shields, engines) where it saw you. Those killer ships also retreat to recharge shields if damaged. So you’d need to keep your mothership moving, or move once spotted by the enemy. To win, you might need to make fast bait/decoy ships to lead the enemy ships away from your base.

gorillapaws's avatar

Here is the strategy I’m working on:
1 Maintain 4 harvesters
2. Build a couple cheap Hunters, but keep them near mothership, will chase enemy if visible, but shouldn’t wander
3. (maybe) Build 1 or 2 fast scouts (maybe shield + engine) returns to mothership on taking damage. If they detect enemy mothership (via introspecting enemy modules, looking for a construction module) they store that location in my mothership.
4. Begin building elites, keep them all near mothership.
5. When sufficient # of elites exist, Zerg for enemy position located by scouts (if known) else move as a pack to hunt.

gorillapaws's avatar

Have you been able to figure out how to test for a drone’s spec?
e.g. “if( drones[i].spec === ??? )

I’ve tried everything I can think of and have had no luck.

gorillapaws's avatar

Ok, to test a spec (harvester in this case) this seems to work:

if( drones[i].spec.toString() === ‘DroneSpec(2,0,0,0,0,0)’ )

ideally you could test for a specific module, but I can’t seem to make that work: e.g. all of these fail:
if( drones[i].spec[0] > 0 )
if( drones[i].spec[‘storageModules’] > 0 )
if( drones[i].spec.modules[0] > 0 )
if( drones[i].spec.modules[‘storageModules’] > 0 )

LostInParadise's avatar

I love the idea of this. I wish the programs were in Python. I wonder if this idea can be applied to teaching students to program. Another approach might be to devise a scenario where programs have to cooperate with each other to achieve a certain objective.

I am reminded of a famous set of computer tournaments. I can’t find a link that gives a brief description, so I will just give a quick overview. You can get the details on the Web. There is a type of game in game theory known as Prisoner’s Dilemma. It offers two options, cooperating or cheating. You get the largest payoff if you cheat and your opponent cooperates but still get a positive result if you both cooperate. For a single round of the game, the best strategy is to cheat, which gives the largest payoff regardless of what your opponent does. The idea of the tournament was to have computer programs play multiple rounds of PD with each other and see which did the best. The most successful programs were variations of a strategy called Tit For Tat – start by cooperating and then do whatever your opponent did in the previous round.

Zaku's avatar

@gorillapaws Good job getting it to do toString(). I didn’t get that far, and assumed it was broken when spec.storageModules showed undefined. I don’t know why your tests failed – I think it’s just some subtle JavaScript array syntax thing. That would be quite helpful to get to work.

@LostInParadise This is cooperative between your own programs. You need to build your own agents with different abilities and programs and get them to cooperate effectively. And yes, it’s a great way to get students into programming. I would never have been into programming if it were not for the clear use for games.

gorillapaws's avatar

I’ve decided to upload as a .zip now since Fluther keeps messing with the formatting. Some stuff is still behaving unexpectedly and I’m sure I’ve got some bugs in there. It usually does pretty well vs. the level1 AI.

@LostInParadise You should join in the fun. I agree that a Python version would be awesome. I don’t really know JavaScript, I’m just goofing around.

gorillapaws's avatar

I’m having an issue with the following code:

if( drones[i].spec.toString() === ‘DroneSpec(3,2,3,2,0,0)’ )
{
Game.mothership.sarahConnorX = drones[i].position.x;
Game.mothership.sarahConnorY = drones[i].position.y;
alert( ‘Enemy Mothership Location is (’ + drones[i].position.x + ’,’ + drones[i].position.y + ’)’ )
alert( ‘Enemy Mothership Location is (’ + Game.mothership.saraConnorX + ’,’ + Game.mothership.saraConnorY + ’)’ )
}

The first alert is displaying the correct position, but the second alert is showing ”(undefined, undefined)”. Any ideas on why it won’t let me assign to Game.mothership.sarahConnorX & Game.mothership.sarahConnorY?

Zaku's avatar

As written, you’ve got a typo in the second one. sara vs. sarah

gorillapaws's avatar

My scouts are setting Game.mothership.sarahConnorX and Game.mothership.sarahConnorY to the correct position. When I ready to zerg, I’m sending my attack drones to the same position, only they’re trying to go to (0,0) which is what I initialized those variables to in the mothership’s onSpawn: method. Could there be separate instances of Game.mothership.sarahConnorX and Game.mothership.sarahConnorY for each unit that tires to access them?

Zaku's avatar

Only if they’re spelled differently. I successfully do the same sort of thing to have my mothership tell all the hunters to attack any enemy the mothership detects near itself.

gorillapaws's avatar

I’ll keep looking at it. You don’t think it could be because in your case your mothership is updating the property on itself, whereas my scouts are trying to set the value on the mothership and the hunters are trying to access it?

Zaku's avatar

I don’t think so, no. Because it’s referring to Game. , which is hopefully successfully referring to that global object.

However, I do think maybe at least in the code you posted, that you have another error that may be causing it. That is, the Elite1 refers to Game.mothership.enemyMothershipX in a test, and that isn’t referred to anywhere else.

gorillapaws's avatar

I’ve got that all sorted now. Here’s the latest. It seems to work pretty well against level 1 AI. The biggest issues are improving the harvester efficiency, getting my mothership to follow elites, and I need to get the mothership to mine minerals if harvesters get too beat up. Turtling up seems to work pretty well, especially with a fast shielded scout that draws enemy hunters back to the nest for extermination.

Zaku's avatar

Yeah, I don’t see a way to have the mothership not just sit around if it’s started building something but doesn’t have enough resources to finish. So it looked to me like the mothership can’t mine for itself and is stuck in one place if it started building without enough resources, and the harvesters die.

So I think we’d need a way to read the mineral supply state from the mothership, and only building things she has enough to complete, though it is also a potential bottleneck that the mothership typically only has 3 storage units, so if it stops building, harvesters won’t be able to drop off supplies.

I made some tweaks to the harvesters themselves, though they should get more tweaks. I’m trying:
* 3 storage, 1 engine, fleet of 2 fast higher-capacity harvesters.
* Remembering minerals that were seen but not harvested yet – this would be even better if we stored a bunch and went to the nearest one and avoided having two harvesters go for the same one.
* Having harvesters flee from (armed) enemies.

Zaku's avatar

Oh, also, to have the random course search strategy cover more distance and waste less time, we can turn down the chance of a random course change each tick (makes the random blob tend to have larger scale), and have it notice when it’s right on the edge, and if so, head back more towards the middle.

gorillapaws's avatar

The big problem with getting the mothership to only begin building when it has sufficient resources to complete the drone is that it really limits the kinds of drones you can build. Being limited (and unable to upgrade) to just 3 storage units really hampers construction.

Regarding the faster/higher-capacity drones, I wonder if the extra time spent constructing them, offsets the efficiency gain, maybe so on level 2 (I haven’t spent much time with it yet). Also, how are you getting references to the minerals for your harvesters? The only documented function I saw was using the onMineralEnteredVision( mineral ) and using the parameter. Is there a way to detect/iterate over the minerals?

I do agree with all of your points. Right now my harvesters flee from all enemies, and that means enemy harvesters chase off mine, contributing to loosing the resource war.

gorillapaws's avatar

Have you been able to call methods on other objects? I declared a method in the mothership class for testing:

addition: function( first, second )
{
return first + second;
},

I’m guessing the function is private to the mothership, but I haven’t been able to figure out how to make it public (using “prototype” doesn’t seem to work, and the syntax is different for this code vs. other javascript tutorials).

I call it from one of the other drones like so:

if( Game.mothership.addition( 3, 3 ) === 6 )
{
alert( “success” );
}

I get a TypeError exception:
“Game.mothership.addition is not a function”

Zaku's avatar

The big problem with getting the mothership to only begin building when it has sufficient resources to complete the drone is that it really limits the kinds of drones you can build. Being limited (and unable to upgrade) to just 3 storage units really hampers construction.

The build sequence can be the same, but there will be delays when the mothership isn’t ready to build anything. That state though is needed to allow the mothership to do other things, I think, unless I’m wrong that there is a way to get it to move under it’s own power while there is something partly built. (Well… you could make an AI that will ram it to push it around… ;-) )

Ya I switched to bigger harvesters on level 2 because there are larger minerals on level 2, and then there are greater distances to them too, so speed seemed worthwhile but ya it comes at a cost. I tried making them even larger, but the mothership could only take a delivery of size 3.

In level 2 you need to test the minerals’ size or else your harvesters waste their time going for size-4+ minerals. I just guessed that there was a mineral.size property and their is, for the parameter to the function when a drone sees a mineral. The iteration is done by the movement of the drone resulting in that function being called.

However, if I were to try to solve level 2 further, I would start building a collection class to map the minerals and record what’s been discovered and mined. Then I’d iterate over that to compute nearest known mineral from an empty harvester’s current position. I’d also get my fleet organized to move the mothership to the middle of a resource cluster until it was mined, to cut out the long travel time to retrieve distant minerals.

As for calling functions on other objects, no, though I haven’t really tried. I think the way the code we add in the windows may be limited. Not sure. It might just be we need a slightly different syntax. I wish there was more documentation or examples.

Maybe I’m wrong, but I think there is also only one local copy of data members per handler, too, meaning if we have multiple drones using the same handler and we need them to have their own data, that may be tricky. Clearly they have individual data (cargo contents, position, move target, damage level…) defined by the system – that might just need to be inserted into the drone object?

gorillapaws's avatar

@Zaku I figured out what’s going on with the syntax. It’s using JavaScript’s object literal syntax, and it’s basically a dictionary of key/value pairs. I found this nugget that explains my exception:
“Unlike public functions, which can appear anywhere in your code, including after lines using your function, methods declared using object literal notation do not exist until execution of that section of the script.” (source: http://www.standardista.com/javascript/javascript-object-literals-simplified/).

I’m really starting to hate JavaScript, haha.

Zaku's avatar

Oh great work tracking that down! Yeah, JavaScript can be a real pain, though mainly because of the environment (client-side web browsers and poor feedback).

Zaku's avatar

The developer posted:
“Thanks for taking the time to comment, this kind of feedback is really valuable. I’ve pushed a fix and drone.spec should now work as expected. As it happens, the next big feature I am planning to add is multiplayer. This will take at least a few more weeks though.”

Answer this question

Login

or

Join

to answer.

This question is in the General Section. Responses must be helpful and on-topic.

Your answer will be saved while you login or join.

Have a question? Ask Fluther!

What do you know more about?
or
Knowledge Networking @ Fluther