Rock-Paper-Scissors AI

So one existing issue is that bots like “count”

function count(t)
	local r,p,s=0,0,0
	local function f(x)
		if x==0 then r=r+1 elseif x==1 then p=p+1 else s=s+1 end
	end
	for i=1,#t do f(t[i])end
	return function()
		if #t~=0 then f(t[#t])else return math.random(3)-1 end
		if r>p and r>s then return 1
		elseif p>s and p>r then return 2
		elseif s>p and s>r then return 0
		elseif p>s and p==r then return math.random(2)-1
		elseif s>r and s==p then return math.random(2)
		elseif r>p and r==s then return(math.random(2)+1)%3
		else return math.random(3)-1
		end
	end
end

have that bit of memory going on that gets reset every time the main function is called, which is every time there is a new server. Not only new servers, but any time two different servers simulate games between it and another bot, say one server plays a batch of games and then a different server does and then the first one does again, it will have different things going on in that area. This bot “count” re-simulates every game from the start when it’s called (which would be impractical and laggy for advanced bots), but that still doesn’t solve the issue of its r,p,s variables saying different things between servers. I’m not sure what to do about that, or what even can be done.

Here’s how the battling system works right now:
Retrieve list of all existing bot names
Parse list and sandbox and load source of each bot, storing main functions
Load game history of each pair of bots ((n^2-n)/2 calls to GetAsync), cache the history
Find the two bots that have played the least games together
Check if they’re busy, if they’re not, store os.time() indicating to other servers that this pair is busy
Re-load their history
Play games for 15 seconds
Update the stored history with every game played during that simulation added to the end of it

Problems with this mainly concern the bot’s memory. I don’t know what to do about that. A bot that does this

return"example",function()
	local last=0
	return function()last=(last+1)%3 return last end
end

will not only play 1,2,0,1,2,0,… because it will forget “last” or have inconsistent values for it across servers. If it plays on one server and ends on 1 and then starts again on a different server, it might play 1 again if that server previously ended on 0. In general this program would have a slight tendency to play 1 because that would be its default whenever its main function is called again.

Wait, are the tables t and t2 saved across rounds?

They’re persistent across rounds, yes. The game simulates like 400 rounds in 15 seconds (depends on how much processing the bots are doing) and the 2 tables are always the same table in the same server. They might not be necessarily logically continuous with the mechanics of the bot, though, since two servers can write to the history one after another and one might not start where the other would have left off. If there’s only one server though then everything works normally until it ends and a new one starts.

I’m wondering what the best way to load round history is. To calculate the leaderboard you need all of the win rates between every bot, which means all of that information needs to be loaded, which is (n^2-n)/2 calls to DataStore:GetAsync that need to happen before the leaderboard can be properly calculated. I just switched it to load a bunch of random pairs as fast as possible while playing more games and re-loading the history of a pair right before they start playing. This makes the number of played games shown on the leaderboard go up, but it doesn’t have information about each bot against each other bot, only against some and not others, which makes it inaccurate. If Rock vs Scissors has loaded and Scissors vs Paper has loaded but Rock vs Paper hasn’t loaded yet, this puts Rock a lot higher on the leaderboard until Rock vs Paper does load. Is this better than not displaying anything and forcing you to wait however many minutes until each pair (there are 21 bots now, so 210 pairs) loads? That would mean each time a server starts, nothing actually happens and you can’t see anything until those hundreds of datastore calls go through, but then you have the correct scores.

I’m actually wondering if it’s even necessary to store round history at all. Do I need to have the last fifty thousand games between every pair of bots? Is a few hundred not enough? I can just simulate that much from scratch in every server, really. I’m not sure.

I changed it so it doesn’t record things across servers. All simulations start over every server. The game now simulates a maximum of 1000 games between every pair of bots and the leaderboard is updated automatically about every second. I also switched to hastebin links because pastebin has a submission limit.

The only problem now it seems is bots that potentially crash the server. I would like to detect when a bot either crashes the server or is just hogging way too much power and remove it from the pool so it doesn’t crash other servers. The testing phase is the only filter for bots that can crash. How can I detect that a particular bot caused a crash?

here’s the current bot battle logic

local pcache={}
function programs(a,b)
	local a,b,n=bots[a],bots[b],a..','..b
	local p,c,x,y=pcache[n],gethistory(n)
	if p then
		x,y=unpack(p)
	else
		setmetatable(c[1],{__newindex=function()end})
		setmetatable(c[2],{__newindex=function()end})
		x,y=a(c[2],c[1]),b(c[1],c[2])
		pcache[n]={x,y}
	end
	return x,y,c
end
local list
function loadlist()
	list={}
	for a,t in pairs(score)do
		for b,n in pairs(t)do
			local x,y,h=programs(a,b)
			local hA,hB=h[1],h[2]
			local g,clock=0,0
			local function f()
				g=g+1
				local t0=tick()
				local i,j=tonumber(x()),tonumber(y())
				if(i==0 or i==1 or i==2)and(j==0 or j==1 or j==2)then
					rawset(hA,#hA+1,i)
					rawset(hB,#hB+1,j)
					if i==(j+1)%3 then
						n[1]=n[1]+1
					elseif i==j then
						n[2]=n[2]+1
					else
						n[3]=n[3]+1
					end
				end
				if g==10 then
					list[f]=nil
				else
					clock=clock+tick()-t0
					list[f]=clock
				end
			end
			list[f]=0
		end
	end
end
loadlist()

local avg=1
local dt=0
while true do
	local t0=tick()
	local sum,tot=0,0
	if not next(list)then loadlist()end
	for f,t in pairs(list)do
		if t<=avg then
			f()
		end
		sum,tot=sum+t,tot+1
	end
	avg=sum/tot
	dt=dt+tick()-t0
	if dt>1 then
		dt=dt-1
		wait()
		game.ReplicatedStorage.Leaderboard:FireAllClients(GetInfo(names))
	end
end

I just added a GUI for ranking all of the other bots against one by clicking on its name. I think the game is pretty secure and developed now. Is there anything else I should add? I’m still not too sure about adding player vs bot/pvp because the bots aren’t designed to beat people, they’re designed to beat other bots. people aren’t doing ten million computations worth of statistical analysis and pattern recognition per second to decide their next move, they’re just mashing buttons in a somewhat predictable fashion.