-- Arctic API v1. Manual wall counters and goal match the original module.
local settings={goal=0,own_walls=10,enemy_walls=10,show_route=true}
local function sub(a,b)return{x=a.x-b.x,y=a.y-b.y,z=a.z-b.z}end
local function dot(a,b)return a.x*b.x+a.y*b.y+a.z*b.z end
local function round(x)return math.floor(x+.5)end
local function same(a,b)return a.x==b.x and a.y==b.y end
local function inside(c)return c.x>=0 and c.y>=0 and c.x<9 and c.y<9 end
local dirs={{x=0,y=-1},{x=0,y=1},{x=-1,y=0},{x=1,y=0}}
local function endcell(c,g)return g==0 and c.y==0 or g==1 and c.y==8 or g==2 and c.x==0 or g==3 and c.x==8 end
local function connected(b,a,c)
 if not inside(a) or not inside(c) then return false end
 for _,w in ipairs(b.walls) do
  if w.h and a.x==c.x and math.min(a.y,c.y)==w.y and a.x>=w.x and a.x<=w.x+1 then return false end
  if not w.h and a.y==c.y and math.min(a.x,c.x)==w.x and a.y>=w.y and a.y<=w.y+1 then return false end
 end
 return true
end
local function moves(b,who,from,ignore)
 local out={};local other=b.pawns[3-who].cell
 local function add(c)if ignore or not same(c,other) then out[#out+1]=c end end
 for _,d in ipairs(dirs) do
  local n={x=from.x+d.x,y=from.y+d.y}
  if connected(b,from,n) then
   if ignore or not same(n,other) then add(n)
   else
    local j={x=n.x+d.x,y=n.y+d.y}
    if connected(b,n,j) then add(j) else
     for _,s in ipairs({-1,1}) do local side={x=n.x+d.y*s,y=n.y+d.x*s};if connected(b,n,side) then add(side) end end
    end
   end
  end
 end
 return out
end
local function path(b,who,ignore)
 local start=b.pawns[who].cell;local queue={start};local head=1;local parent={};local function id(c)return c.y*9+c.x+1 end
 parent[id(start)]=start
 while head<=#queue do
  local c=queue[head];head=head+1
  if endcell(c,b.pawns[who].goal) then local out={c};while not same(c,start) do c=parent[id(c)];table.insert(out,1,c) end;return out end
  for _,n in ipairs(moves(b,who,c,ignore)) do if not parent[id(n)] then parent[id(n)]=c;queue[#queue+1]=n end end
 end
 return {}
end
local function fits(b,w)
 if w.x<0 or w.y<0 or w.x>7 or w.y>7 then return false end
 for _,o in ipairs(b.walls) do
  if w.x==o.x and w.y==o.y then return false end
  if w.h==o.h and (w.h and w.y==o.y and math.abs(w.x-o.x)<2 or not w.h and w.x==o.x and math.abs(w.y-o.y)<2) then return false end
 end
 return true
end
local function evaluate(b)
 if endcell(b.pawns[1].cell,b.pawns[1].goal)then return 10000 end
 if endcell(b.pawns[2].cell,b.pawns[2].goal)then return -10000 end
 local a,c=path(b,1,false),path(b,2,false)
 if #a==0 or #c==0 then return -100000 end
 return 10*(#c-#a)+.35*(b.pawns[1].walls-b.pawns[2].walls)
end
local function apply(b,who,a)
 local n={walls={},pawns={}}
 for _,w in ipairs(b.walls)do n.walls[#n.walls+1]=w end
 for i,p in ipairs(b.pawns)do n.pawns[i]={cell=p.cell,goal=p.goal,walls=p.walls}end
 if a.wall then n.walls[#n.walls+1]=a.wall;n.pawns[who].walls=n.pawns[who].walls-1 else n.pawns[who].cell=a.cell end
 return n
end
local function candidates(b,who)
 local out={}
 for _,c in ipairs(moves(b,who,b.pawns[who].cell,false))do out[#out+1]={cell=c}end
 if b.pawns[who].walls>0 then for y=0,7 do for x=0,7 do for _,h in ipairs({false,true})do local w={x=x,y=y,h=h};if fits(b,w)then out[#out+1]={wall=w}end end end end end
 return out
end
local function legal(b,a,who)
 if not a.wall then return true end
 local n=apply(b,who,a);return #path(n,1,true)>0 and #path(n,2,true)>0
end
local remembered_goal,remembered_arena=nil,nil
local function sample()
 local ps=arctic.players();local me=ps[1] and ps[1].position
 if not me then return nil,'Waiting for local pawn' end
 local arena,board,base,arena_name
 for _,a in ipairs(arctic.children(arctic.root()))do
  local ai=arctic.info(a)
  if ai.class=='Model' or ai.class=='Folder' then
   local children=arctic.children(a);local bid,bpart
   for _,id in ipairs(children)do local i=arctic.info(id);if i.name=='Board'then bid=id elseif i.name=='BoardPart'then bpart=i end end
   if bid and bpart and bpart.position and bpart.size then local delta=sub(me,bpart.position)
    if math.abs(dot(delta,bpart.right))<=bpart.size.x*.5 and math.abs(dot(delta,bpart.forward))<=bpart.size.z*.5 and delta.y>=0 and delta.y<8 then arena=children;board=bid;base=bpart;arena_name=ai.name;break end
   end
  end
 end
 if not board then return nil,'Stand on a Build A Barrier board' end
 local tiles={};for _,id in ipairs(arctic.children(board))do local i=arctic.info(id);if i.position and i.size then tiles[#tiles+1]=i end end
 if #tiles~=81 then return nil,'Waiting for 81 board tiles' end
 local seed=tiles[1];local ax,ay=seed.right,seed.forward;local step=1e9
 for _,t in ipairs(tiles)do local d=sub(t.position,seed.position);local x,y=math.abs(dot(d,ax)),math.abs(dot(d,ay));if y<.1 and x>seed.size.x*.9 then step=math.min(step,x)end;if x<.1 and y>seed.size.x*.9 then step=math.min(step,y)end end
 if step<seed.size.x*.95 or step>seed.size.x*1.5 then return nil,'Unrecognized tile spacing' end
 local minx,miny=1e9,1e9
 for _,t in ipairs(tiles)do local d=sub(t.position,seed.position);minx=math.min(minx,dot(d,ax));miny=math.min(miny,dot(d,ay)) end
 local origin={x=seed.position.x+ax.x*minx+ay.x*miny,y=seed.position.y+seed.size.y*.5+.06,z=seed.position.z+ax.z*minx+ay.z*miny}
 local function cell(p)
  local d=sub(p,origin);local x,y=dot(d,ax)/step,dot(d,ay)/step;local c={x=round(x),y=round(y)}
  if inside(c)and math.abs(x-c.x)<.32 and math.abs(y-c.y)<.32 and d.y>=-.5 and d.y<7 then return c end
 end
 local occupied={};for _,t in ipairs(tiles)do local c=cell({x=t.position.x,y=origin.y,z=t.position.z});if not c then return nil,'Invalid grid' end;occupied[c.y*9+c.x]=true end
 local count=0;for _ in pairs(occupied)do count=count+1 end;if count~=81 then return nil,'Incomplete grid' end
 local mine=cell(me);if not mine then return nil,'Pawn moving; waiting' end
 local enemies={};for i=2,#ps do if ps[i].position then local c=cell(ps[i].position);if c then enemies[#enemies+1]=c end end end
 if #enemies~=1 then return nil,'Waiting for exactly two pawns' end
 if remembered_arena~=arena_name then remembered_goal=nil;remembered_arena=arena_name end
 local enemy=enemies[1];local goal=settings.goal-1
 if goal<0 then goal=remembered_goal end
 if not goal then
  if mine.y==8 and enemy.y==0 then goal=0 elseif mine.y==0 and enemy.y==8 then goal=1 elseif mine.x==8 and enemy.x==0 then goal=2 elseif mine.x==0 and enemy.x==8 then goal=3 end
 end
 if not goal or goal<0 then return nil,'Enable at round start or set goal 1..4 in script' end
 remembered_goal=goal
 local b={pawns={{cell=mine,goal=goal,walls=settings.own_walls},{cell=enemy,goal=goal~1,walls=settings.enemy_walls}},walls={},origin=origin,ax=ax,ay=ay,step=step}
 local seen={}
 for _,id in ipairs(arena)do local item=arctic.info(id);local suffix=string.sub(item.name,-2)
  if item.class=='Model'and(suffix=='_H'or suffix=='_V')then
   local queue={id};local head=1
   while head<=#queue and head<=256 do local n=queue[head];head=head+1;local p=arctic.info(n)
    if p.position and p.size and p.size.y>=step*.3 then
     local d=sub(p.position,origin);local x,y=dot(d,ax)/step,dot(d,ay)/step
     if x>=-.5 and y>=-.5 and x<=8.5 and y<=8.5 and d.y>=-.1 and d.y<=step*1.5 then
      local sx=(math.abs(dot(p.right,ax))*p.size.x+math.abs(dot(p.forward,ax))*p.size.z)/step
      local sy=(math.abs(dot(p.right,ay))*p.size.x+math.abs(dot(p.forward,ay))*p.size.z)/step
      local h=sx>1.5 and sx<2.3 and sy<.5;local v=sy>1.5 and sy<2.3 and sx<.5
      local wx,wy=round(x-.5),round(y-.5)
      if(not h and not v)or math.abs(x-wx-.5)>.15 or math.abs(y-wy-.5)>.15 then return nil,'Unrecognized wall geometry; advice withheld' end
      local key=wx..':'..wy..':'..tostring(h);if not seen[key]then local w={x=wx,y=wy,h=h};if not fits(b,w)then return nil,'Invalid wall arrangement' end;seen[key]=true;b.walls[#b.walls+1]=w end
     end
    end
    for _,child in ipairs(arctic.children(n))do if #queue<256 then queue[#queue+1]=child end end
   end
  end
 end
 if same(mine,enemy) or #path(b,1,true)==0 or #path(b,2,true)==0 then return nil,'Invalid board state' end
 return b
end
local search=nil
local function signature(b)
 local a,c=b.pawns[1].cell,b.pawns[2].cell;local s=remembered_arena..':'..a.x..','..a.y..':'..c.x..','..c.y..':'..b.pawns[1].goal
 for _,w in ipairs(b.walls)do s=s..':'..w.x..','..w.y..tostring(w.h)end
 return s
end
local function world(b,c)return{x=b.origin.x+b.ax.x*c.x*b.step+b.ay.x*c.y*b.step,y=b.origin.y,z=b.origin.z+b.ax.z*c.x*b.step+b.ay.z*c.y*b.step}end
function on_update()
 local b,message=sample()
 if not b then search=nil;arctic.label(24,160,'Barrier: '..message,0xffd080ff);return end
 if endcell(b.pawns[1].cell,b.pawns[1].goal)or endcell(b.pawns[2].cell,b.pawns[2].goal)then search=nil;arctic.label(24,160,'Barrier: round finished',0xffd080ff);return end
 local key=signature(b)
 if not search or search.key~=key then search={key=key,items=candidates(b,1),index=1,best=nil,score=-1e9,ranked={},candidate=1,reply_index=1,minimax=-1e9}end
 -- First-ply ranking followed by all replies to the best twelve actions.
 -- Work is spread over updates; a changed board cancels stale analysis.
 for _=1,4 do
  local a=search.items[search.index]
  if a then
   search.index=search.index+1
   if legal(b,a,1)then local score=evaluate(apply(b,1,a));a.score=score;search.ranked[#search.ranked+1]=a;if score>search.score then search.score=score;search.best=a end end
  else
   if not search.sorted then table.sort(search.ranked,function(x,y)return x.score>y.score end);search.sorted=true end
   local candidate=search.ranked[search.candidate]
   if not candidate or search.candidate>12 then break end
   local n=apply(b,1,candidate)
   if endcell(n.pawns[1].cell,n.pawns[1].goal)then search.best=candidate;break end
   if not search.replies then search.replies=candidates(n,2);search.reply_index=1;search.worst=1e9 end
   local reply=search.replies[search.reply_index]
   if reply then
    search.reply_index=search.reply_index+1
    if legal(n,reply,2)then search.worst=math.min(search.worst,evaluate(apply(n,2,reply)))end
   else
    local score=search.worst+.001*candidate.score
    if score>search.minimax then search.minimax=score;search.best=candidate end
    search.candidate=search.candidate+1;search.replies=nil
   end
  end
 end
 arctic.label(24,160,'Barrier | '..math.min(search.index-1,#search.items)..'/'..#search.items..' candidates | replies '..math.min(search.candidate-1,12)..'/12 | walls: manual',0xadc8e0ff)
 local a=search.best;if not a then return end
 if a.wall then local w=a.wall;local p,q
  if w.h then p=world(b,{x=w.x-.5,y=w.y+.5});q=world(b,{x=w.x+1.5,y=w.y+.5})else p=world(b,{x=w.x+.5,y=w.y-.5});q=world(b,{x=w.x+.5,y=w.y+1.5})end
  arctic.line(p.x,p.y,p.z,q.x,q.y,q.z,0xffa850ff);arctic.text(p.x,p.y,p.z,'BUILD HERE',0xffd080ff)
 else local p=world(b,a.cell);arctic.text(p.x,p.y,p.z,'MOVE HERE',0x70ffb0ff)end
 if settings.show_route then local route=path(apply(b,1,a),1,false);for i=2,#route do local p,q=world(b,route[i-1]),world(b,route[i]);arctic.line(p.x,p.y,p.z,q.x,q.y,q.z,0x70cfffff)end end
end
