trust/js/sims/Tournament.js

696 lines
16 KiB
JavaScript

Tournament.resetGlobalVariables = function(){
Tournament.SELECTION = 5;
Tournament.NUM_TURNS = 10;
Tournament.INITIAL_AGENTS = [
{strategy:"tft", count:3},
{strategy:"all_d", count:3},
{strategy:"all_c", count:3},
{strategy:"grudge", count:3},
{strategy:"prober", count:3},
{strategy:"tf2t", count:3},
{strategy:"pavlov", count:3},
{strategy:"random", count:4}
];
Tournament.FLOWER_CONNECTIONS = false;
publish("pd/defaultPayoffs");
PD.NOISE = 0;
};
Tournament.resetGlobalVariables();
subscribe("rules/evolution",function(value){
Tournament.SELECTION = value;
});
subscribe("rules/turns",function(value){
Tournament.NUM_TURNS = value;
});
// OH THAT'S SO COOL. Mostly C: Pavlov wins, Mostly D: tit for two tats wins (with 5% mistake!)
// ALSO, NOISE: tft vs all_d. no random: tft wins. low random: tf2t wins. high random: all_d wins. totally random: nobody wins
//////////////////////////////////////////////
//////////////////////////////////////////////
// REGULAR LOAD
Loader.addToManifest(Loader.manifest,{
tournament_peep: "assets/tournament/tournament_peep.json",
connection_flower: "assets/tournament/connection_flower.json",
// SFX
squeak: "assets/sounds/squeak.mp3",
bonk: "assets/sounds/bonk.mp3"
});
function Tournament(config){
var self = this;
self.id = config.id;
// APP
var app = new PIXI.Application(500, 500, {transparent:true, resolution:2});
self.dom = app.view;
// DOM
self.dom.className = "object";
self.dom.style.width = 500;
self.dom.style.height = 500;
self.dom.style.left = config.x+"px";
self.dom.style.top = config.y+"px";
//self.dom.style.border = "1px solid rgba(0,0,0,0.2)";
var _convertCountToArray = function(countList){
var array = [];
for(var i=0; i<AGENTS.length; i++){
var A = AGENTS[i];
var strategy = A.strategy;
var count = A.count;
for(var j=0; j<count; j++){
array.push(strategy);
}
}
return array;
};
self.agents = [];
self.connections = [];
self.networkContainer = new PIXI.Container();
self.agentsContainer = new PIXI.Container();
app.stage.addChild(self.networkContainer);
app.stage.addChild(self.agentsContainer);
self.populateAgents = function(){
// Clear EVERYTHING
while(self.agents.length>0) self.agents[0].kill();
// Convert to an array
self.agents = _convertCountToArray(AGENTS);
// Put 'em in a ring
var count = 0;
for(var i=0; i<self.agents.length; i++){
// Angle
var angle = (i/self.agents.length)*Math.TAU - Math.TAU/4;
// What kind of agent?
var strategy = self.agents[i];
var agent = new TournamentAgent({angle:angle, strategy:strategy, tournament:self});
self.agentsContainer.addChild(agent.graphics);
// Remember me!
self.agents[i] = agent;
}
// (sort agents by depth)
self.sortAgentsByDepth();
};
self.sortAgentsByDepth = function(){
self.agentsContainer.children.sort(function(a,b){
return a.y - b.y;
});
};
self.createNetwork = function(){
// Clear EVERYTHING
while(self.connections.length>0) self.connections[0].kill();
// Connect all of 'em
for(var i=0; i<self.agents.length; i++){
var playerA = self.agents[i];
var flip = false;
for(var j=i+1; j<self.agents.length; j++){
var playerB = self.agents[j];
var connection = new TournamentConnection({
tournament: self,
from: playerA,
to: playerB,
flower_flip: flip
});
self.networkContainer.addChild(connection.graphics);
self.connections.push(connection);
flip = !flip;
}
}
};
self.actuallyRemoveConnection = function(connection){
var index = self.connections.indexOf(connection);
self.connections.splice(index,1);
};
///////////////////////
// RESET //////////////
///////////////////////
var AGENTS;
self.reset = function(){
// Agents & Network...
AGENTS = JSON.parse(JSON.stringify(Tournament.INITIAL_AGENTS));
self.populateAgents();
self.createNetwork();
// Animation...
self.STAGE = STAGE_REST;
_playIndex = 0;
_tweenTimer = 0;
// Stop autoplay!
publish("tournament/autoplay/stop");
_step = 0;
};
listen(self, "tournament/reset", self.reset);
self.reset();
////////////////////////////////////
// SHOW MATCHES ////////////////////
////////////////////////////////////
self.playMatch = function(number){
// GET OUR MATCH
var matches = [];
for(var a=0; a<self.agents.length; a++){
for(var b=a+1; b<self.agents.length; b++){
matches.push([self.agents[a], self.agents[b]]);
}
}
var match = matches[number];
// Highlight match
self.dehighlightAllConnections();
var connections = match[0].connections;
var connection = connections.filter(function(c){
if(c.from==match[0] && c.to==match[1]) return true;
if(c.from==match[1] && c.to==match[0]) return true;
return false;
})[0];
connection.highlight();
// Actually PLAY the game -- HACK: HARD-CODE 10 ROUNDS
var scores = PD.playRepeatedGame(match[0], match[1], 10);
// Return ALL this data...
return {
charA: match[0].strategyName,
charB: match[1].strategyName,
scoreA: scores.totalA,
scoreB: scores.totalB,
payoffs: scores.payoffs
}
};
self.dehighlightAllConnections = function(){
for(var i=0; i<self.connections.length; i++) self.connections[i].dehighlight();
};
////////////////////////////////////
// EVOLUTION ///////////////////////
////////////////////////////////////
// Play one tournament
self.agentsSorted = null;
self.playOneTournament = function(){
PD.playOneTournament(self.agents, Tournament.NUM_TURNS);
self.agentsSorted = self.agents.slice();
self.agentsSorted.sort(function(a,b){
if(a.coins==b.coins) return (Math.random()<0.5); // if equal, random
return a.coins-b.coins; // otherwise, sort as per usual
});
};
// Get rid of X worst
self.eliminateBottom = function(X){
// The worst X
var worst = self.agentsSorted.slice(0,X);
// For each one, subtract from AGENTS count, and KILL.
for(var i=0; i<worst.length; i++){
var badAgent = worst[i];
var config = AGENTS.find(function(config){
return config.strategy==badAgent.strategyName;
});
config.count--; // remove one
badAgent.eliminate(); // ELIMINATE
}
};
self.actuallyRemoveAgent = function(agent){
var index = self.agents.indexOf(agent);
self.agents.splice(index,1);
};
// Reproduce the top X
self.reproduceTop = function(X){
// The top X
var best = self.agentsSorted.slice(self.agentsSorted.length-X, self.agentsSorted.length);
// For each one, add to AGENTS count
for(var i=0; i<best.length; i++){
var goodAgent = best[i];
var config = AGENTS.find(function(config){
return config.strategy==goodAgent.strategyName;
});
config.count++; // ADD one
}
// ADD agents, splicing right AFTER
for(var i=0; i<best.length; i++){
// Properties...
var goodAgent = best[i];
var angle = goodAgent.angle + 0.1;
var strategy = goodAgent.strategyName;
// Create agent!
var agent = new TournamentAgent({angle:angle, strategy:strategy, tournament:self});
self.agentsContainer.addChild(agent.graphics);
// Splice RIGHT AFTER
var index = self.agents.indexOf(goodAgent);
self.agents.splice(index, 0, agent);
}
// What are the agents' GO-TO angles?
for(var i=0; i<self.agents.length; i++){
var agent = self.agents[i];
var angle = (i/self.agents.length)*Math.TAU - Math.TAU/4;
agent.gotoAngle = angle;
}
// ADD connections
self.createNetwork();
};
// ANIMATE the PLAYING, ELIMINATING, or REPRODUCING
var STAGE_REST = 0;
var STAGE_PLAY = 1;
var STAGE_ELIMINATE = 2;
var STAGE_REPRODUCE = 3;
self.STAGE = STAGE_REST;
// AUTOPLAY
self.isAutoPlaying = false;
var _step = 0;
var _nextStep = function(){
if(self.STAGE!=STAGE_REST) return;
if(_step==0) publish("tournament/play");
if(_step==1) publish("tournament/eliminate");
if(_step==2) publish("tournament/reproduce");
_step = (_step+1)%3;
};
var _startAutoPlay = function(){
self.isAutoPlaying = true;
_nextStep();
setTimeout(function(){
if(self.isAutoPlaying) _startAutoPlay();
},150);
};
var _stopAutoPlay = function(){
self.isAutoPlaying = false;
};
listen(self, "tournament/autoplay/start", _startAutoPlay);
listen(self, "tournament/autoplay/stop", _stopAutoPlay);
listen(self, "tournament/step", function(){
publish("tournament/autoplay/stop");
_nextStep();
});
// ANIMATE
var _playIndex = 0;
var _tweenTimer = 0;
var _tick = function(delta){
// Tick
Tween.tick();
// PLAY!
if(self.STAGE == STAGE_PLAY){
/*if(self.isAutoPlaying){
self.playOneTournament(); // FOR REAL, NOW.
_playIndex = 0;
_tweenTimer = 0;
self.STAGE = STAGE_REST;
publish("tournament/step/completed", ["play"]);
}else{*/
if(_playIndex>0 && _playIndex<self.agents.length+1) self.agents[_playIndex-1].dehighlightConnections();
if(_playIndex>1 && _playIndex<self.agents.length+2) self.agents[_playIndex-2].dehighlightConnections();
if(_playIndex<self.agents.length){
self.agents[_playIndex].highlightConnections();
_playIndex += self.isAutoPlaying ? 2 : 1;
}else{
self.playOneTournament(); // FOR REAL, NOW.
_playIndex = 0;
_tweenTimer = 0;
self.STAGE = STAGE_REST;
publish("tournament/step/completed", ["play"]);
}
//}
}
// ELIMINATE!
if(self.STAGE == STAGE_ELIMINATE){
self.eliminateBottom(Tournament.SELECTION);
_tweenTimer++;
if(_tweenTimer==_s(0.3) || self.isAutoPlaying){
_tweenTimer = 0;
self.STAGE = STAGE_REST;
publish("tournament/step/completed", ["eliminate"]);
}
}
// REPRODUCE!
if(self.STAGE == STAGE_REPRODUCE){
// Start
if(_tweenTimer==0){
self.reproduceTop(Tournament.SELECTION);
}
// Middle...
_tweenTimer += self.isAutoPlaying ? 0.15 : 0.05;
if(_tweenTimer>1) _tweenTimer=1;
for(var i=0;i<self.agents.length;i++){
var a = self.agents[i];
a.tweenAngle(_tweenTimer);
a.updatePosition();
}
self.sortAgentsByDepth();
for(var i=0;i<self.connections.length;i++) self.connections[i].updateGraphics();
// End
if(_tweenTimer>=1){
_tweenTimer = 0;
self.STAGE = STAGE_REST;
publish("tournament/step/completed", ["reproduce"]);
}
}
};
app.ticker.add(_tick);
// PLAY A TOURNAMENT
self._startPlay = function(){
if(!self.isAutoPlaying){
Loader.sounds.coin_get.volume(0.1).play();
}
self.STAGE=STAGE_PLAY;
};
listen(self, "tournament/play", self._startPlay);
self._startEliminate = function(){
if(!self.isAutoPlaying){
Loader.sounds.squeak.volume(0.4).play();
}
self.STAGE=STAGE_ELIMINATE;
};
listen(self, "tournament/eliminate", self._startEliminate);
self._startReproduce = function(){
if(!self.isAutoPlaying){
Loader.sounds.bonk.volume(0.3).play();
}
self.STAGE=STAGE_REPRODUCE;
};
listen(self, "tournament/reproduce", self._startReproduce);
// Add...
self.add = function(){
_add(self);
};
// Remove...
self.remove = function(){
_stopAutoPlay();
for(var i=0; i<self.agents.length; i++) unlisten(self.agents[i]);
unlisten(self);
app.destroy();
_remove(self);
};
}
///////////////////////////////////////////////////////
///////////////////////////////////////////////////////
///////////////////////////////////////////////////////
function TournamentConnection(config){
var self = this;
self.config = config;
self.tournament = config.tournament;
// Connect from & to
self.from = config.from;
self.to = config.to;
self.from.connections.push(self);
self.to.connections.push(self);
// Graphics!
var g;
if(Tournament.FLOWER_CONNECTIONS){
g = _makeMovieClip("connection_flower");
g.anchor.x = 0;
g.anchor.y = 0;
g.scale.set(0.5);
}else{
g = _makeMovieClip("connection");
g.anchor.x = 0;
g.anchor.y = 0.5;
g.height = 1;
}
self.graphics = g;
var _flowerLong = false;
var _updateFlower = function(highlight){
var frame = 0;
if(highlight) frame+=2;
if(_flowerLong) frame+=1;
g.gotoAndStop(frame);
};
if(config.flower_flip){
g.scale.y *= -1;
}
// Highlight or no?
self.highlight = function(){
if(Tournament.FLOWER_CONNECTIONS){
_updateFlower(true);
}else{
g.height = 3;
g.gotoAndStop(1);
}
};
self.dehighlight = function(){
if(Tournament.FLOWER_CONNECTIONS){
_updateFlower(false);
}else{
g.height = 1;
g.gotoAndStop(0);
}
};
self.dehighlight();
// Stretch dat bad boy
self.updateGraphics = function(){
var f = self.from.graphics;
var t = self.to.graphics;
var dx = t.x-f.x;
var dy = t.y-f.y;
var a = Math.atan2(dy,dx);
var dist = Math.sqrt(dx*dx+dy*dy);
g.x = f.x;
g.y = f.y;
if(Tournament.FLOWER_CONNECTIONS){
if(dist<250){
_flowerLong = false;
if(config.flower_flip){
g.rotation = a+Math.TAU/10;
}else{
g.rotation = a-Math.TAU/10;
}
}else{
_flowerLong = true;
if(config.flower_flip){
g.rotation = a+Math.TAU/5;
}else{
g.rotation = a-Math.TAU/5;
}
}
_updateFlower();
}else{
g.rotation = a;
g.width = dist;
}
};
self.updateGraphics();
// KILL
self.IS_DEAD = false;
self.kill = function(){
if(self.IS_DEAD) return;
self.IS_DEAD = true;
self.graphics.parent.removeChild(self.graphics); // remove self's graphics
self.tournament.actuallyRemoveConnection(self);
};
};
///////////////////////////////////////////////////////
///////////////////////////////////////////////////////
///////////////////////////////////////////////////////
function TournamentAgent(config){
var self = this;
self.strategyName = config.strategy;
self.tournament = config.tournament;
self.angle = config.angle;
self.gotoAngle = self.angle;
// Connections
self.connections = [];
self.highlightConnections = function(){
for(var i=0;i<self.connections.length;i++) self.connections[i].highlight();
};
self.dehighlightConnections = function(){
for(var i=0;i<self.connections.length;i++) self.connections[i].dehighlight();
};
self.clearConnections = function(){
for(var i=0;i<self.connections.length;i++){
self.connections[i].kill();
}
self.connections = [];
};
// Number of coins
self.coins = 0;
self.addPayoff = function(payoff){
self.coins += payoff;
self.updateScore();
};
// What's the image?
var g = new PIXI.Container();
self.graphics = g;
// Body!
var body = _makeMovieClip("tournament_peep");
body.gotoAndStop(PEEP_METADATA[config.strategy].frame);
body.scale.set(0.5);
body.anchor.x = 0.5;
body.anchor.y = 0.75;
g.addChild(body);
// Score!
var textStyle = new PIXI.TextStyle({
fontFamily: "FuturaHandwritten",
fontSize: 16,
fill: "#444"
});
var scoreText = new PIXI.Text("", textStyle);
scoreText.anchor.x = 0.5;
g.addChild(scoreText);
self.updateScore = function(){
scoreText.visible = true;
scoreText.text = self.coins;
};
self.updateScore();
scoreText.visible = false;
listen(self, "tournament/reproduce",function(){
scoreText.visible = false;
});
// What's the play logic?
var LogicClass = window["Logic_"+self.strategyName];
self.logic = new LogicClass();
self.play = function(){
return self.logic.play();
};
self.remember = function(own, other){
self.logic.remember(own, other);
};
// Reset!
self.resetCoins = function(){
self.coins = 0; // reset coins;
self.updateScore();
}
self.resetLogic = function(){
self.logic = new LogicClass(); // reset logic
};
// Tween angle...
self.tweenAngle = function(t){
self.angle = self.gotoAngle*t + self.angle*(1-t);
};
self.updatePosition = function(){
g.x = Math.cos(self.angle)*200 + 250;
g.y = Math.sin(self.angle)*200 + 265;
scoreText.x = -Math.cos(self.angle)*40;
scoreText.y = -Math.sin(self.angle)*48 - 22;
body.scale.x = Math.abs(body.scale.x) * ((Math.cos(self.angle)<0) ? 1 : -1);
};
self.updatePosition();
// ELIMINATE
self.eliminate = function(){
// INSTA-KILL ALL CONNECTIONS
self.clearConnections();
scoreText.visible = false;
// Tween -- DIE!
var duration = self.tournament.isAutoPlaying ? 0.13 : 0.3;
Tween_get(g).to({
alpha: 0,
x: g.x+Math.random()*20-10,
y: g.y+Math.random()*20-10,
rotation: Math.random()*0.5-0.25
}, _s(duration), Ease.circOut).call(self.kill);
};
// KILL (actually insta-remove)
self.kill = function(){
// Remove ANY tweens
Tween.removeTweens(g);
// NOW remove graphics.
g.parent.removeChild(g);
// AND remove self from tournament
self.tournament.actuallyRemoveAgent(self);
// Unsub
unlisten(self);
};
}