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 bc 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