makeclass('player')
makeclass('army')
makeclass('orchard')

setmetatable(player,army)

--
-- HORPS game: Walk around the board collecting armies and buffs
-- Captured armies eat each turn
-- Attacking armies select order (random by default)
-- Defending armies are stronger
-- Command: Tame Creature Stack
-- Command: Free Creature Stack
-- 

--
-- Old Data Structure
--   Army.Count[kind]=number	Where kind is r,p,s
--   Player.Count[kind]=number	Where kind is r,p,s
--
-- New Data Structure:
--   Player.Stack={ 1=kind, 2=qty, 3=kind, 4=qty,... }
--   Player.Food[kind]=Number
--   orchard.crop='r'
--   orchard.size=integer       -- Max size it can grow to
--   orchard.rate=integer       -- How many grow per unit time()
--   orchard.pick=integer	-- time() it was last harvested
--

function mapable(a)
  if isa(a,login) then return false end
  return not isa(a,player)
  end

function orchard:available()
  return math.min(self.size,self.rate*(time()-self.pick))
  end

function bound(a,b,c)
  if b<a then return b elseif b>c then return c else return b end
  end

function lerp(a,b,c,d,e)
  return d+(e-d)*(a-b)/(c-b)
  end

function orchard:harvest(n)
  local a=self:available()
  if n>=a then self.pick=time() return a end
  self.pick=lerp(n,0,a,self.pick,time())
  return n
  end

function player.interface(actor, place)
    gui.menu_item("cb_north"          ,"Go North")
    gui.menu_item("cb_south"          ,"Go South")
    gui.menu_item("cb_east"           ,"Go East")
    gui.menu_item("cb_west"           ,"Go West")
    gui.menu_item("cb_map"            ,"Show the Map")
    gui.menu_item("cb_conjurerock"    ,"Conjure Rock")
    gui.menu_item("cb_conjurepaper"   ,"Conjure Paper")
    gui.menu_item("cb_conjurescissor" ,"Conjure Scissor")
    gui.menu_item("cb_droprock"       ,"Drop Rock")
    gui.menu_item("cb_droppaper"      ,"Drop Paper")
    gui.menu_item("cb_dropscissor"    ,"Drop Scissor")
    end


function player:cb_conjurerock()    self:conjure('r',1) end
function player:cb_conjurepaper()   self:conjure('p',1) end
function player:cb_conjurescissor() self:conjure('s',1) end

function player:conjure(k,c)
  for i=1,#self.stack,2 do if self.stack[i]==k then self.stack[i+1]=self.stack[i+1]+c self:cb_map() return end end
  self.stack[#self.stack+1]=k
  self.stack[#self.stack+1]=c
  self:cb_map()
  end
  



function army.interface(actor,place)
    if place.owner==actor then gui_menu_item("cb_recruit","Recruit");
    else                       gui.menu_item("cb_fight"  ,"Fight");
    end end

-- Rock dulls Scissors            3
-- Rock cripples Lizard           2
-- Paper disproves Spock	  3
-- Paper covers Rock              2
-- Scissors decapitates Lizard    3
-- Scissors cuts Paper            2
-- Lizard eats Paper              3
-- Lizard poisons Spock           2
-- Spock vaporizes Rock           3
-- Spock dismantles Scissors      2
armytypes={'r','p','s'}
armynames={r="Rock Golem" ,p="Paper Dragon",s="Scissor Beast",l="Fire Lizzard",v="Mr. Spock"}
foodtypes={'r','p','s','l','v'}
foodnames={r="Raspberry"  ,p="Pomegranite" ,s="Strawberry"   ,l="Lemon"       ,v="Mango"  }
--advantage={ r={ r=1,   p=1/2, s=3,   l=2,   v=1/3 },
--            p={ r=2,   p=1,   s=1/2, l=1/3, v=3   },
--	      s={ r=1/3, p=2,   s=1,   l=3,   v=1/2 },
--	      l={ r=1/2, p=3,   s=1/3, l=1,   v=2   },
--	      v={ r=3,   p=1/3, s=2,   l=1/2, v=1   } }
advantage={ r={ r=1,   p=1/2, s=2   },
            p={ r=2,   p=1,   s=1/2 },
	    s={ r=1/2, p=2,   s=1   } }

function shuffle(t)
    local s={}
    for i=1, #t do s[i]=t[i] end
    for i=#t,2,-1 do
        local j=math.random(i)
        s[i],s[j]=s[j],s[i]
        end
    return s
    end

function MakeMap()
  local rad=6
  for x=-rad,rad do for y=-rad,rad do
    itemkind=math.random(1,7)
    if itemkind==1 then
      local nt=tangible.build{class='army',x=x,y=y,z=0,plane='main',graphic='army'}
      local stack={}
      local shuf=shuffle(armytypes)
      for i=1,#shuf do if math.random(1,2)==1 then
        stack[1+#stack]=shuf[i]
        stack[1+#stack]=math.random(1,3)
        end end
      nt.stack=stack
    elseif itemkind==2 then
      local nt=tangible.build{class='orchard',x=x,y=y,z=0,plane='main',graphic='orchard'}
      nt.crop=foodtypes[math.random(1,#foodtypes)]
      nt.size=10
      nt.rate=0.1
      nt.pick=time()
      end
    end end
  end



--
-- For each creature type, select the optimal target. Select randomly among identical targets.
--
function fight0(ak,ac,dk,dc,tweekdefender) -- returns number of attacker casualties, defender casualties
  local adv0=advantage[ak][dk]
  local adv1=advantage[dk][ak]
  if not tweekdefender then tweekdefender=function(adv) return adv end end
  adv1=tweekdefender(adv1)
  local losstotal=(ac+dc)/2
  local power0=ac*adv0
  local power1=dc*adv1
  local powertotal=power0+power1
  local loss0=math.min(ac,math.floor(losstotal*power1/powertotal))
  local loss1=math.min(dc,math.floor(losstotal*power0/powertotal))
  if loss0+loss1==0 then loss0=loss0+1 end
  return loss0,loss1
  end


function player:near_iter(rad,kind)
  local lis=tangible.near(self,rad,true,true)
  local i=1
  return function()
    while i<=#lis do
      i=i+1
      if not kind or isa(lis[i-1],kind) then return lis[i-1] end end end
  end

function stack_iter(t1)
  local i=0
  return function()
    if i+2>#t1 then return end
    i=i+2
    return t1[i-1],t1[i]
    end end

function fight_iter(t1,t2) -- Returns quads: AttackerKind,AttackerCount,DefenderKind,DefenderCount
  local i=0
  return function()
    if i+2>#t1 or i+2>#t2 then return end
    i=i+2
    return t1[i-1],t1[i],t2[i-1],t2[i]
    end end

function compactstack(s)
  local index=1
  while index<=#s do
    if s[index+1]==0 then table.remove(s,index) table.remove(s,index)
      else index=index+2 end
    end
  end

function army:counttroops()
  local rval=0
  for k,c in stack_iter(self.stack) do rval=rval+c end
  return rval
  end

function army:die()
  print("You defeat the enemy!")
  tangible.delete(self)
  end

function army:fight(enemy)
  for i=1,math.min(#self.stack,#enemy.stack),2 do
    loss1,loss2=fight0(self.stack[i],self.stack[i+1],enemy.stack[i],enemy.stack[i+1])
    print("When "..self.stack[i+1].." "..self.stack[i].." fight "..enemy.stack[i+1].." "..enemy.stack[i].." they kill "..loss2.." and suffer "..loss1)
    self.stack[i+1]=self.stack[i+1]-loss1
    enemy.stack[i+1]=enemy.stack[i+1]-loss2
    end
  compactstack(self.stack)
  compactstack(enemy.stack)
  if enemy:counttroops()==0 then enemy:die() return false
  else return true end
  end
  
function player:cb_droprock(actor) player:droparmy(actor,'r') end
function player:cb_droppaper(actor) player:droparmy(actor,'p') end
function player:cb_dropscissor(actor) player:droparmy(actor,'s') end

function player:droparmy(actor,kind)
  if actor.Count[kind]<=0 then print("No armies of type "..kind.." available") return end
  local lis=tangible.near(actor,0,true,true)
  if #lis>1 then print("Multiple Tangibles Nearby") return end
  local t=lis[1]
  if t==nil then t=player:buildarmy(actor) end
  t.Count[kind]=t.Count[kind]+1
  end

function player:pickfruit(ft)
  local kind=ft.crop
  local qty=ft:harvest(ft:available())
  self.food[kind]=self.food[kind]+qty
  print("You harvest "..qty.." "..foodnames[kind])
  end

function player:newlocation() -- return false if the player should bounce back, else return true
  local lis=tangible.near(self,0,true,true)
  local count={} setmetatable(count,NilIsZero)
  local count0=0
  local bounce=false
  for _,t in ipairs(lis) do
    if     isa(t,army)    then bounce=bounce or self:fight(t)
    elseif isa(t,orchard) then self:pickfruit(t) end
    end
  return bounce
  end

function player:printanimstate()
  local graphic,plane,x,y,z,facing = tangible.animstate(self)
  print("Resulting state: ", graphic, plane, x, y, z, facing)
  end

function player:walk(dx,dy)
  tangible.animate(self,{action='walk',dx=dx,dy=dy})
  if self:newlocation() then tangible.animate(self,{action='walk',dx=-dx,dy=-dy}) end
  self:cb_map()
  end
  
function player:cb_north()
  self:walk(0,1)
  end

function player:cb_south()
  self:walk(0,-1)
  end

function player:cb_east()
  self:walk(1,0)
  end

function player:cb_west()
  self:walk(-1,0)
  end


function player:buildarmy(actor)
  local _,pl,ax,ay=tangible.animstate(actor)
  t={class='army',x=ax,y=ay,z=0,plane=pl,graphic='army'}
  local nt=tangible.build(t)
  nt.Count={}
  setmetatable(nt.Count,{__index=function(t,k) return 0 end,__newindex=function(t,k,v) if v~=nil and v~=0 then rawset(t,k,v) end end})
  return nt
  end


makeclass('army')

function seq(a,b,c) return a<=b and b<=c or false end
function num2(a) if a<=9 then return " "..a else return a end end

function where()
  local x,y,z=tangible.xyz(tangible.actor())
  print("You are at "..x.." "..y)
  end


function mapcelltext(lis)
  if lis==nil then return '      ' end
  if #lis>1 then return '++++++' end
  local rval=""
  local prefix=""
  local suffix=""
  if isa(lis[1],player) then prefix="\27[91;7m" suffix="\27[0m" end
  if isa(lis[1],army) then
    for k,c in stack_iter(lis[1].stack) do rval=rval..k..(c>9 and '+' or tostring(c)) end
    rval=rval..string.sub('      ',1,6-string.len(rval))
  elseif isa(lis[1],orchard) then
    prefix="\27[32;1m" suffix="\27[0m"
    rval=rval..lis[1].crop..string.format("%-5d",lis[1]:available())
    end
  return prefix..rval..suffix
  end


function player.cb_map(actor,place,dialog)
  print("\27[s\27[0;0f")
  local rad=4
  local ax,ay=tangible.xyz(actor)
  scratch={}
  local lis=tangible.near(actor,1.5*rad,true,false)
  for _,t in pairs(lis) do
    local c=tangible.getclass(t)
    local dx,dy=tangible.xyz(t)
    local offset=(-dy+ay+rad)*(rad*2+1)+dx-ax+rad
    local cl=tangible.getclass(t)
    if seq(ax-rad,dx,ax+rad) and seq(ay-rad,dy,ay+rad) and (cl=='player' or cl=='army' or cl=='orchard') then
      if not scratch[offset] then scratch[offset]={} end
      scratch[offset][1+#scratch[offset]]=t
      end end
  for dy=-rad,rad do
    local lbuf=""
    for dx=-rad,rad do
      local offset=(dy+rad)*(rad*2+1)+dx+rad
      lbuf=lbuf..'|'..mapcelltext(scratch[offset])
      end
    lbuf=lbuf..'|'
    print(lbuf)
    end
  print("\27[u")
  end
  


