335 lines
9.7 KiB
Lua
335 lines
9.7 KiB
Lua
|
if not init then
|
||
|
rom.best_player = nil
|
||
|
init = true
|
||
|
depth = 4; -- how many moves does random player evaluate
|
||
|
error_rate = 0.25; -- players make wrong decision with this probability
|
||
|
generation = 10; -- how many times we repeat
|
||
|
steps = 100; -- how many steps each generation
|
||
|
bestc = 0; -- how many times was new best player picked
|
||
|
mode = 2; -- 1, evolution!, 2 play game
|
||
|
|
||
|
-- game pay offs
|
||
|
rules = {
|
||
|
{
|
||
|
{2., 2.}, -- first player cooperate, second player cooperate: +2,+2
|
||
|
{-1, 3}, -- first player cooperate, second player cheat: -1,+3
|
||
|
},
|
||
|
{
|
||
|
{3,-1}, -- first player cheats, second player cooperate
|
||
|
{0,0}, -- first player cheats, second player cheat
|
||
|
}
|
||
|
};
|
||
|
|
||
|
copytable = function(tab)
|
||
|
if type(tab)~="table" then return tab end
|
||
|
local ret = {};
|
||
|
for k,v in pairs(tab) do ret[k] = copytable(v) end return
|
||
|
ret
|
||
|
end
|
||
|
|
||
|
copycat = {
|
||
|
rules = {
|
||
|
{0}, -- initial: 0 = cooperate
|
||
|
{0,1}, -- after one move : cooperate if other cooperated last turn, cheat if other cheated last turn
|
||
|
},
|
||
|
-- encode decision sequence in binary: 01110 = dec 8+4+2=14
|
||
|
memory = 1, -- how many moves back player consideres?
|
||
|
moves ={}, -- opponent moves
|
||
|
mood = 0, -- probability that player will cheat if he had intention to cooperate
|
||
|
moody = 0.5, -- by how much cheat/cooperate change mood
|
||
|
points = 0,
|
||
|
name = "copycat"
|
||
|
}
|
||
|
|
||
|
cheater = {
|
||
|
rules = {
|
||
|
{1},
|
||
|
},
|
||
|
memory = 0, -- how many moves back player consideres?
|
||
|
moves ={}, -- opponent moves
|
||
|
mood = 0,
|
||
|
moody = 0.5,
|
||
|
points = 0,
|
||
|
name = "cheater"
|
||
|
}
|
||
|
|
||
|
realplayer = {
|
||
|
rules = {
|
||
|
{0},
|
||
|
},
|
||
|
memory = 0, -- how many moves back player consideres?
|
||
|
moves ={}, -- opponent moves
|
||
|
mood = 0,
|
||
|
moody = 0.,
|
||
|
points = 0,
|
||
|
name = "real player",
|
||
|
real = true,
|
||
|
out = 0
|
||
|
}
|
||
|
|
||
|
|
||
|
create_random_player = function(memory)
|
||
|
local rules = {};
|
||
|
for i = 1, memory+1 do
|
||
|
rules[i] = {};
|
||
|
for j = 1,2^(i-1) do
|
||
|
rules[i][j] = math.random(2)-1
|
||
|
end
|
||
|
end
|
||
|
return {rules = rules, memory = memory, moves = {}, mood = 0, moody = math.random(), points = 0, name = "randomplayer"}
|
||
|
end
|
||
|
|
||
|
|
||
|
-- player makes next move according to his memory of moves so far
|
||
|
play = function(player)
|
||
|
if player.real then return player.out end -- real player
|
||
|
local moves = player.moves;
|
||
|
local n = #moves;
|
||
|
if n > player.memory then n = player.memory end -- there are many moves, examine only what we can
|
||
|
local rules = player.rules[n+1]
|
||
|
local state = bin2dec(player.moves,n); -- examine last n moves
|
||
|
--say("n " .. n .. " state " .. state)
|
||
|
return rules[state+1]
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
group_play = function(playrs) -- each randomplayer plays with every other player in lower group
|
||
|
local n = #playrs;
|
||
|
local m = 10; -- play m games with each opponent, random pair order
|
||
|
for i = 1,10 do
|
||
|
for j = 11,20 do -- i plays with j, randomized order
|
||
|
playrs[i].moves = {}; playrs[j].moves = {}; -- reset remembered moves before paired match!
|
||
|
playrs[i].mood = 0;
|
||
|
for k = 1,m do
|
||
|
if math.random(2) == 1 then
|
||
|
interact(playrs[i],playrs[j])
|
||
|
else
|
||
|
interact(playrs[j],playrs[i])
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
sort_players = function(pl)
|
||
|
table.sort(pl,
|
||
|
function(p1,p2) return p1.points<p2.points end
|
||
|
) -- sorts in ascending order of points
|
||
|
end
|
||
|
|
||
|
genetics = function(playrs)
|
||
|
local population = {}
|
||
|
for i = 11,20 do -- pick out only "randomplayer"s
|
||
|
population[#population+1] = playrs[i] --copytable()
|
||
|
end
|
||
|
|
||
|
sort_players(population); -- sort from worst to best
|
||
|
for i = 1,5 do -- replace 5 worst players with new random players
|
||
|
local points2 = population[#population-i+1].points; -- points of i-th best player
|
||
|
if points2 then
|
||
|
local points1 = population[i].points;
|
||
|
if points1<points2 or (points1==points2 and math.random(2) == 1) then --
|
||
|
|
||
|
population[i] = create_random_player(depth);
|
||
|
--if i == 1 then say(serialize(population[i].rules)) end
|
||
|
population[i].points = 0 --points2;
|
||
|
population[i].name = "randomplayer"
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
--say("best random points " .. population[#population].points)
|
||
|
for i = 11,20 do -- pick out only "randomplayer"s
|
||
|
playrs[i] = population[i-10]
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
-- paired interaction: player1 plays with player2, then player2 plays with player1
|
||
|
interact = function(player1,player2)
|
||
|
local res1 = play(player1);
|
||
|
local mood = player1.mood;
|
||
|
|
||
|
if not player1.real and math.random(1000)<=1000*error_rate then res1=1-res1 end
|
||
|
if res1 == 0 and math.random()<= mood then res1 = 1 end
|
||
|
|
||
|
local moves = player2.moves; moves[#moves+1 ] = res1;
|
||
|
--say("1 moves : " .. serialize(moves) .. " res1 " .. res1)
|
||
|
local res2 = play(player2);
|
||
|
|
||
|
mood = player2.mood;
|
||
|
if math.random(1000)<=1000*error_rate then res2=1-res2 end
|
||
|
if res2 == 0 and math.random()<= mood then res2 = 1 end
|
||
|
|
||
|
moves = player1.moves; moves[#moves+1] = res2;
|
||
|
--say("2 moves : " .. serialize(moves) .. " res2 " .. res2)
|
||
|
local res = rules[res1+1][res2+1];
|
||
|
player1.points = player1.points + res[1];
|
||
|
player2.points = player2.points + res[2];
|
||
|
|
||
|
if res2 == 0 then -- player2 cooperated, mood change
|
||
|
if res1==1 then
|
||
|
mood = mood + player2.moody;
|
||
|
else
|
||
|
mood = mood - player2.moody;
|
||
|
end
|
||
|
if mood>1 then mood = 1 elseif mood<0 then mood = 0 end
|
||
|
player2.mood = mood
|
||
|
end
|
||
|
|
||
|
if res1 == 0 then -- mood change for player1
|
||
|
mood = player1.mood;
|
||
|
if res2==1 then
|
||
|
mood = mood + player1.moody;
|
||
|
else
|
||
|
mood = mood - player1.moody;
|
||
|
end
|
||
|
if mood>1 then mood = 1 elseif mood<0 then mood = 0 end
|
||
|
player1.mood = mood
|
||
|
end
|
||
|
end
|
||
|
|
||
|
dec2bin = function(input)
|
||
|
local ret = {};
|
||
|
if input == 0 then return {0} end
|
||
|
while input~=0 do
|
||
|
local r=input%2; input = (input -r)/2
|
||
|
ret[#ret+1] = r;
|
||
|
end
|
||
|
local n = #ret;
|
||
|
local rret = {}
|
||
|
for i = 1, n do
|
||
|
rret[i]=ret[n-i+1]
|
||
|
end
|
||
|
return rret
|
||
|
end
|
||
|
|
||
|
bin2dec = function(bin, length) -- length= how many last elements we take
|
||
|
if length == 0 then return 0 end
|
||
|
if not length then length = #bin end
|
||
|
local offset = #bin - length; if offset<0 then offset = 0 end
|
||
|
local ret = 0;
|
||
|
for i = 1,#bin-offset do
|
||
|
ret = 2*ret + bin[i+offset]
|
||
|
end
|
||
|
return ret
|
||
|
end
|
||
|
|
||
|
get_results = function(players)
|
||
|
local ret = {} for i=1,#players do ret[i] = players[i].name .. " " .. players[i].points .. "M " .. players[i].mood .. "(" .. players[i].moody .. ")" end return table.concat(ret,"\n")
|
||
|
end
|
||
|
|
||
|
|
||
|
players = {} -- start with 5 cheaters, 5 copycats and 10 randomplayers
|
||
|
|
||
|
if mode == 1 then
|
||
|
for i = 1,5 do players[i] = copytable(cheater) end
|
||
|
for i = 1,5 do players[5+i] = copytable(copycat) end
|
||
|
|
||
|
for i = 1,10 do players[10+i] = create_random_player(depth) end -- last 10 players are random
|
||
|
|
||
|
|
||
|
age = 0
|
||
|
rom.best_player = nil;
|
||
|
elseif mode == 2 then
|
||
|
players = {copytable(realplayer), copytable( create_random_player(4) ) } ;
|
||
|
self.listen(1)
|
||
|
end
|
||
|
--players[20] = copytable(rom.best_player) or create_random_player(depth) -- add best player from before
|
||
|
--players[20].name = "randomplayer"
|
||
|
|
||
|
|
||
|
end
|
||
|
|
||
|
|
||
|
if mode == 1 then
|
||
|
|
||
|
local bestpoints = 0
|
||
|
for k = 1, generation do -- repeat experiment generation*
|
||
|
|
||
|
--rom.best_player = nil
|
||
|
for i = 1,#players do players[i].points = 0 end
|
||
|
|
||
|
|
||
|
for j = 1, steps do -- several steps to see who is best long term on average
|
||
|
--for i = 1,#players do players[i].points = 0 end
|
||
|
group_play(players)
|
||
|
end
|
||
|
|
||
|
bestpoints = 0
|
||
|
|
||
|
genetics(players) -- remove 5 worst randomplayers & replace them by new randoms
|
||
|
|
||
|
|
||
|
local population = {}
|
||
|
bestrandom = 0
|
||
|
for i = 11,20 do
|
||
|
if players[i].points >= bestrandom then bestrandom = players[i].points end
|
||
|
population[#population+1] = players[i]
|
||
|
end
|
||
|
--say("randomplayer population size " .. #population)
|
||
|
sort_players(population);
|
||
|
if rom.best_player then
|
||
|
lastbest = rom.best_player.points
|
||
|
if bestrandom >= lastbest then -- PICK NEW BEST
|
||
|
bestc = bestc+1
|
||
|
rom.best_player = copytable(population[#population]); -- remember best randomplayer for next experiment
|
||
|
end
|
||
|
else
|
||
|
rom.best_player = copytable(population[#population]); -- remember best randomplayer for next experiment
|
||
|
end
|
||
|
|
||
|
|
||
|
end
|
||
|
|
||
|
age = age + generation
|
||
|
|
||
|
--display results!
|
||
|
msg = ""
|
||
|
msg = msg .. "Planet Earth (galactical name Halfwits), age " .. age .. ", error_rate " .. error_rate .. ", steps " .. steps .. ", generations " .. generation .. ":\n"
|
||
|
msg = msg .."\nlast round\n"
|
||
|
--sort_players(players)
|
||
|
bestpoints = 0
|
||
|
for i =1,#players do
|
||
|
if players[i].points>bestpoints then bestpoints = players[i].points end
|
||
|
msg = msg .. players[i].name .. ": " .. players[i].points .. " M " .. players[i].mood .. "(" .. players[i].moody .. ")\n"
|
||
|
end
|
||
|
local rules = rom.best_player.rules;
|
||
|
msg = msg .. "BEST: " .. bestpoints .. "\n\n## alltime best random player no. " .. bestc .. ", points " .. rom.best_player.points .. " moody " .. rom.best_player.moody ..
|
||
|
"\ncurrent best random player/max current score: ".. math.floor(100*bestrandom/bestpoints*100)/100 .. "% \nrules " .. serialize(rules) .. " )\n";
|
||
|
|
||
|
local msg1 = "{" .. rules[1][1] .. "}\n"
|
||
|
for i = 2,#rules do
|
||
|
local rule = rules[i];
|
||
|
msg1 = msg1.. "{"
|
||
|
for j = 1,#rule do
|
||
|
local rule_string = table.concat(dec2bin(j-1),"");
|
||
|
rule_string = string.rep("0",i-string.len(rule_string)-1) .. rule_string;
|
||
|
msg1 = msg1 .. rule_string .."="..rule[j]..","
|
||
|
end
|
||
|
msg1 = msg1 .. "}\n"
|
||
|
end
|
||
|
|
||
|
self.label(msg .. msg1)
|
||
|
|
||
|
elseif mode == 2 then -- play vs player
|
||
|
|
||
|
speaker,msg = self.listen_msg();
|
||
|
if msg then
|
||
|
if msg == "0" or msg == "1" then
|
||
|
local nextmove = tonumber(msg);
|
||
|
if nextmove == 1 and players[1].out == 1 then
|
||
|
say("look buddy, we dont like cheaters around here. dont cheat twice in a row")
|
||
|
else
|
||
|
players[1].out = nextmove
|
||
|
interact(players[1], players[2])
|
||
|
say("input " .. players[1].out .. ", BOT MOVES: " .. serialize(players[1].moves) .. " BOT MOOD " .. players[2].mood .. "(" .. players[2].moody .. ") SCORE: you " .. players[1].points .. " bot " .. players[2].points)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
end
|