pico-8 cartridge // http://www.pico-8.com version 42 __lua__ -- vacuum gambit -- by kistaro windrider game = 1 win = 2 lose = 3 function usplit(str) return unpack(split(str)) end function csv(s) local ret=split(s,"\n") for i,v in ipairs(ret) do ret[i] = type(v) == "string" and split(v) or {v} end return ret end function const_fxn(x) return function() return x end end -- generate standard "overlay" -- constructor for type tt. -- if tt.init is defined, generated -- new calls tt.init(ret) after -- ret is definitely not nil, -- before calling setmetatable. -- use to initialize mutables. -- -- if there was a previous new, -- it is invoked on the new -- object *after* more, because -- this works better with the -- `more` impls i use. function mknew(tt) local mt,oldnew,more = {__index=tt},tt.new,rawget(tt, "init") tt.new=function(ret) if(not ret) ret = {} if(more) more(ret) if(oldnew) oldnew(ret) setmetatable(ret, mt) return ret end return tt end -- intrusive singly-linked list. -- cannot be nested! linked_list = mknew{ is_linked_list=true, init = function(x) x.next=nil x.tail=x end, } function linked_list:push_back(x) self.tail.next = x self.tail = x end function linked_list:push_front(x) if (not self.next) self.tail = x x.next = self.next self.next = x end -- vore eats another linked list -- by appending its contents. -- the ingested linked is empty. function linked_list:vore(x) if (not x.next) return self.tail.next = x.next self.tail = x.tail x.next = nil x.tail = x end -- strip calls f(x) for each -- node, removing each node for -- which f(x) returns true. it -- returns the new tail; nil -- if the list is now empty. function linked_list:strip(f) local p, n = self, self.next while n do if f(n) then p.next = n.next else p = n end n = n.next end self.tail = p return p end -- optimized special case - -- could be done with strip but -- this avoids extra function -- calls and comparisions since -- draw isn't allowed to kill -- the item function linked_list:draw() local n = self.next while n do n:draw() n = n.next end end function linked_list:pop_front() local ret = self.next if (not ret) return self.next = ret.next if (not ret.next) ret.tail = nil return ret end function _init() init_blip_pals() wipe_level() primary_ship.main_gun = zap_gun_p.new() -- redundant? load_level(example_level_csv) state = game pal(2,129) pal() end function once_next_frame(f) new_events:push_back{ move = function() f() return true end, } end -- health gradients for 1..5 hp -- exactly, then all "more". hpcols_lut = csv[[36 34, 136 34, 130, 136 34, 34, 130, 136 34, 34, 130, 130, 136]] -- call after any change to maxhp -- configures health gradient function init_hpcols() hpcols = hpcols_lut[min(primary_ship.maxhp,6)] end function wipe_level() primary_ship = player.new() init_hpcols() pships = linked_list.new() pships:push_back(primary_ship) eships = linked_list.new() pbullets = linked_list.new() ebullets = linked_list.new() intangibles_fg = linked_list.new() intangibles_bg = linked_list.new() events = linked_list.new() new_events = linked_list.new() end function _update60() updategame() end function call_f(x) return x:f() end function call_move(x) return x:move() end function updategame() leveldone = level_frame() events:vore(new_events) events:strip(call_move) for _, lst in ipairs{intangibles_bg, pships, eships, pbullets, ebullets} do lst:strip(call_move) end pships:strip( function(ps) local pbox, pded = hurtbox(ps), false eships:strip( function(es) if (not collides(pbox, hurtbox(es))) return pded = pded or ps:hitship(es) return es:hitship(ps) end ) return pded end ) pships:strip( function(ps) local pbox, pded = hurtbox(ps), false ebullets:strip( function(eb) if (not collides(pbox, hurtbox(eb))) return pded = pded or ps:hitbullet(eb) return eb:hitship(ps) end ) return pded end ) -- many bullets and many enemy ships; -- use bucket collider for efficiency local pbullet_collider = collider.new() local p, n = pbullets, pbullets.next while n do n.prev = p pbullet_collider:insert(n) p = n n = p.next end eships:strip( function(es) for pb in all(pbullet_collider:get_collisions(es)) do if pb:hitship(es) then pbullet_collider:hide(pb) pb.prev.next = pb.next if pb.next then pb.next.prev = pb.prev else pbullets.tail = pb.prev end end if (es:hitbullet(pb)) return true end end ) intangibles_fg:strip(call_move) if leveldone and not eships.next and not ebullets.next and not events.next then state = win end if (not pships.next) state = lose end function _draw() fillp(0) drawgame() if (state == game) fadelvl = -45 if (state == win) dropshadow("win",50,61,11) if (state == lose) dropshadow("fail",48,61,8) fadescreen() end fadetable = split"0,1.5,1025.5,1029.5,1285.5,1413.5,9605.5,9637.5,-23130.5,-23066.5,-18970.5,-18954.5,-2570.5,-2568.5,-520.5,-8.5,-0.5" function fadescreen() fadelvl += 0.25 if (fadelvl < 1) return local i = min(flr(fadelvl), #fadetable) fillp(fadetable[#fadetable+1-i]) rectfill(0,0,128,128,0) end -- puke emits a verbose string -- describing item, indented to -- the specified depth (0 by -- default). used for table -- debugging. table-type keys -- are not legible here function puke(item, indent, seen, hidekey) if (type(item) ~= "table") return tostr(item) seen = seen or {} if (seen[item]) return "<<...>>" seen[item] = true indent = indent or 0 local pfx = "\n" for _=1,indent do pfx ..= " " end local xpfx = pfx.." " if item.is_linked_list then local ret,n = "linked_list <",0 item:strip(function(x) n += 1 ret ..= xpfx..tostr(n)..": "..puke(x, indent+2, seen, "next") end) return ret..pfx..">" end local ret = "{" for k, v in pairs(item) do if (k ~= hidekey) ret ..= xpfx..tostr(k)..": "..puke(v, indent+2, seen) end return ret..pfx.."}" end -- convenience for debugging function puketh(item, ...) printh(puke(item), ...) end function pukeboard(item) puketh(item, "@clip") end function drawgame() clip(0,0,112,128) rectfill(0,0,112,128,0) for slist in all{intangibles_bg, pbullets, pships, eships, ebullets, intangibles_fg} do slist:draw() end clip(0,0,128,128) drawhud() end powcols=split"170,154,153,148,68" shlcols = split"204,220,221" function drawhud() -- 112-and-right is hud zone rectfill(112, 0, 127, 127,0x56) rect(112,0,127,127,7) line(127,1,127,127,5) line(113,127) draw_gun_info("❎",1,116,3,primary_ship.main_gun) draw_gun_info("🅾️",1,116,29,primary_ship.special_gun) inset(114,57,119,118) rectfill(119,57,124,58,13) inset(120,64,125,125) rectfill(114,124,120,125,7) print("XP",119,55,1) print("HP",114,122,1) fillp(0x5a5a) vertmeter(115,58,118,117,primary_ship.xp, primary_ship.xptarget, powcols) -- 59 px vertically local mxs, cs, mxh, ch = primary_ship.maxshield, primary_ship.shield, primary_ship.maxhp, primary_ship.hp if (mxs > 0) and (mxh > 0) then local split = 59 * (mxs / (mxs + mxh)) \ 1 + 64 line(121, split, 124, split, 0xba) vertmeter(121,65,124,split-1,cs, mxs,shlcols) vertmeter(121,split+1,124,124,ch, mxh, hpcols) elseif mxs > 0 then vertmeter(121,65,124,124,cs,mxs,shlcols) elseif mxh > 0 then vertmeter(121,65,124,124,ch,mxh,hpcols) else print("!", 122, 93, 9) print("!", 121, 92, 8) end fillp(0) end function draw_gun_info(lbl,fgc,x,y,gun) dropshadow(lbl,x,y,fgc) inset(114,y+7,125,y+18) inset(114,y+20,125,y+24) if(gun) then spr(gun.icon,116,y+9,1,1) --115 to 124 - ammo bar. round up if gun.ammo == nil then fillp(0xa5a5) rectfill(115,y+21,124,y+23,0xea) fillp(0) elseif gun.ammo > 0 then rectfill( 115,y+21, 115+flr(9*gun.ammo/gun.maxammo), y+23,10) else line(118, y+22, 121, y+22, 2) end end end function vertmeter(x0,y0,x1,y1,val,maxval,cols) if ((val <= 0) or (maxval <= 0)) return local h = y1-y0 local px = val/maxval * h \ 1 local ncols = #cols local firstcol = ((h-px)*ncols\h)+1 local lastbottom = y0+(h*firstcol\ncols) rectfill(x0, y1-px, x1, lastbottom, cols[firstcol]) for i=firstcol+1,ncols do local bottom = y0+h*i\ncols rectfill(x0,lastbottom,x1,bottom,cols[i]) lastbottom = bottom end end function inset(x0,y0,x1,y1) rectfill(x0,y0,x1,y1,0) -- use "wide colors" to draw -- monochrome regardless of -- fillp rect(x0,y0,x1,y1,119) line(x1,y0,x0,y0,85) line(x0,y1-1,85) end function dropshadow(str, x, y, col) print(str, x+1, y+1, 5) print(str, x, y, col) end -->8 --ship behavior scrollrate = 0.25 --in px/frame ship_m = mknew{ -- ships have no shield by default shield = 0, maxshield = 0, shieldcooldown = 0x0.003c,--1s shieldpenalty = 0x0.012c, --5s slip = true, -- most enemies slide xmomentum = 0, ymomentum = 0, -- xmin, xmax, ymin, ymax: -- movement constraints -- enforced by `constrain`. xmin = 0, xmax = 104, -- ymin, ymax default to nil -- pship needs more constraint } function ship_m:die() self.dead = true if (self.hp < 0) boom(self.x+self.size*4, self.y+self.size*4,12*self.size, self.boss) end function ship_m:calc_velocity(v0, t) v0 = mid(v0 + t, self.maxspd, -self.maxspd) return v0 - mid(self.drag, -self.drag, v0) end function ship_m:brake_dist(v0) local brake_max = self.thrust + self.drag local tri_frames = abs(v0\brake_max) local chunks = tri_frames * (tri_frames - 1) >> 1 local chunk_zone = chunks * brake_max local overage = abs(v0) - tri_frames * brake_max return (chunk_zone + overage * (tri_frames + 1)) * sgn(v0), (overage > 0) and tri_frames + 1 or tri_frames end function ship_m:constrain(p, dp, pmin, pmax, want) if (not pmin) return want local v1, bd, bf, bp function calc_targets() -- velocity after move v1 = self:calc_velocity(dp, want) -- brake distance and frames bd, bf = self:brake_dist(v1) -- brake point bp = p + bd + v1 end calc_targets() if bp < pmin then -- undershoot. max thrust, -- then treat as overshoot -- targeting minimum bound want, pmax = self.thrust, pmin calc_targets() end if (bp <= pmax) return want -- spread overshoot across frames want -= (bp - pmax)/max(bf,1) return max(want, -self.thrust) end function ship_m:move() self:refresh_shield() local dx, dy, shoot_spec, shoot_main = self:act() dx = self:constrain(self.x, self.xmomentum, self.xmin, self.xmax, dx) dy = self:constrain(self.y, self.ymomentum, self.ymin, self.ymax, dy) if (shoot_main) self:maybe_shoot(self.main_gun) if (shoot_spec) self:maybe_shoot(self.special_gun) if (dx ~= 0 or dy ~= 0) spark(self.sparks, self.x + 4*self.size, self.y + 4*self.size, dx*2.5, dy*2.5, self.sparkodds) self.xmomentum = self:calc_velocity(self.xmomentum, dx) self.ymomentum = self:calc_velocity(self.ymomentum, dy) self.x += self.xmomentum self.y += self.ymomentum -- "scrolling" behavior if self.slip then self.y += scrollrate if self.y >= 128 then self:die() return true end end return false end function ship_m:draw() if(self.fx_pal) pal(self.fx_pal) spr(self.sprite, self.x, self.y, self.size, self.size) pal() end function hurtbox(ship) local h = ship.hurt return { x=ship.x + h.x_off, y=ship.y + h.y_off, width=h.width, height=h.height } end function ship_m:maybe_shoot(gun) if (not gun) return return gun:shoot(self.x + self.fire_off_x, self.y + self.fire_off_y) end function ship_m:hitship(other) return self:hitsomething(1) end function ship_m:hitbullet(b) return self:hitsomething(b.damage) end function ship_m:hitsomething(dmg) if (dmg <= 0) return false self.shield_refresh_ready = lframe + self.shieldpenalty if self.shield >= dmg then self.shield -= dmg self:ow(true) return false end dmg -= self.shield self.shield = 0 self.hp -= dmg if self.hp < 0 then self:die() return true end self:ow(false) return false end function ship_m:ow(shielded) if (shielded) then blip(self,12,3) return end blip(self, 7, 3) end function ship_m:refresh_shield() if (self.shield >= self.maxshield) return if (lframe < self.shield_refresh_ready) return self.shield += 1 self.shield = min(self.shield, self.maxshield) self.shield_refresh_ready = lframe + self.shieldcooldown end -->8 -- bullet and gun behaviors function player_blt_cat() return pbullets end function enemy_blt_cat() return ebullets end -- x, y: position -- dx, dy: movement (linear) -- f: frames remaining; nil for no limit -- sprite: what sprite to draw -- hurt -- hurtbox -- width, height -- in sprites -- x_off, y_off -- how to -- initially position relative -- to firing point. weird -- details, check impl -- damage -- damage to do to -- a ship that gets hit -- category -- function that -- returns which bullet list -- to spawn onto -- hitship -- event handler, -- takes ship as argument. -- default: die, return true. -- returns whether to delete -- the bullet -- die -- on-removal event, -- default no-op bullet_base = mknew{ } gun_base = mknew{ shoot_ready = -32768, icon = 20 } function bullet_base:hitship(_) self:die() return true end function bullet_base:die() end function bullet_base:move() self.x += self.dx self.y += self.dy if (self.f) self.f -= 1 if (self.y > 128) or (self.y < -8 * self.height) or (self.f and self.f < 0) then self:die() return true end return false end function bullet_base:draw() spr(self.sprite, self.x, self.y, self.width, self.height) end -- An `actually_shoot` factory -- for trivial guns function spawn_one(t) return function(gun, x, y) t.new{}:spawn_at(x, y) end end function bullet_base:spawn_at(x, y) self.x = x - self.x_off self.y = y - self.y_off self.category():push_back(self) end function gun_base:shoot(x, y) if (lframe < self.shoot_ready) return false if self.ammo then if (self.ammo <= 0) return false self.ammo -= 1 end self.shoot_ready = lframe + self.cooldown self:actually_shoot(x, y) return true end -->8 -- bullets and guns zap_e = mknew(bullet_base.new{ --shape sprite = 9, --index of ammo sprite width = 1, --in 8x8 blocks height = 1, hurt = { -- hurtbox - where this ship can be hit x_off = 0, -- upper left corner y_off = 0, -- relative to sprite width = 2, height = 8 }, x_off = 1, -- how to position by ship y_off = 8, damage = 1, dx = 0, -- px/frame dy = 4, hitship = const_fxn(true), category = enemy_blt_cat, }) zap_p = mknew(zap_e.new{ sprite = 8, dy = -8, y_off = 0, category = player_blt_cat, }) zap_gun_e = mknew(gun_base.new{ cooldown = 0x0.000a, -- frames between shots ammo = nil, -- unlimited ammo - main gun actually_shoot = spawn_one(zap_e), }) zap_gun_p = mknew(zap_gun_e.new{ actually_shoot = spawn_one(zap_p), }) blast = mknew(bullet_base.new{ --shape sprite = 12, --index of player ammo sprite width = 1, --in 8x8 blocks height = 1, hurt = { -- hurtbox - where this ship can be hit x_off = 1, -- upper left corner y_off = 1, -- relative to sprite width = 6, height = 6 }, x_off = 4, -- how to position by ship y_off = 0, damage = 4, dx = 0, -- px/frame dy = -2, awaitcancel = false, -- disable damage for 2 frames -- when hitting something hitship = function(self, _) if self.damage > 0 and not self.awaitcancel then self.awaitcancel = true once_next_frame(function() new_events:push_back{ wait = 2, obj = self, saved_dmg = self.damage, move = function(self) self.wait -= 1 if self.wait <= 0 then self.obj.damage = self.saved_dmg return true end end, } self.damage = 0 self.awaitcancel = false end) end end, category=player_blt_cat }) blast_gun = mknew(gun_base.new{ icon = 13, cooldown = 0x0.0020, -- frames between shots ammo = 5, maxammo = 5, actually_shoot = spawn_one(blast), }) protron_e = mknew(bullet_base.new{ --shape sprite = 24, width = 1, --in 8x8 blocks height = 1, hurt = { -- hurtbox - where this ship can be hit x_off = 1, -- upper left corner y_off = 1, -- relative to sprite width = 2, height = 2 }, x_off = 1, -- how to position by ship y_off = 4, damage = 1, dym = 0.5, -- gun sets dy; -- this is mult category = enemy_blt_cat, }) protron_p = mknew(protron_e.new{ sprite=23, dym = -1, y_off = 0, category=player_blt_cat, }) protron_gun_e = mknew(gun_base.new{ icon = 25, cooldown = 0x0.000f, -- frames between shots ammo = nil, maxammo = nil, munition = protron_e }) function protron_gun_e:actually_shoot(x, y) local m = self.munition.dym for i=1,3 do local b = self.munition.new{ dx = i*m, dy = (4-i)*m, } b:spawn_at(x,y) local b2 = self.munition.new{ dx = -i*m, dy = (4-i)*m, } b2:spawn_at(x,y) end local bup = self.munition.new{ dx=0, dy=4*m, } bup:spawn_at(x,y) end protron_gun_p = mknew(protron_gun_e.new{ munition = protron_p, }) vulcan_e = mknew(bullet_base.new{ --shape sprite = 21, width = 1, --in 8x8 blocks height = 1, hurt = { -- hurtbox - where this ship can be hit x_off = 0, -- upper left corner y_off = 0, -- relative to sprite width = 1, height = 4 }, x_off = 0.5, -- how to position by ship y_off = 0, damage = 0.5, -- dx from gun dy = 2, category=enemy_blt_cat }) vulcan_p = mknew(vulcan_e.new{ sprite=22, y_off = 4, dy = -4, category=player_blt_cat }) vulcan_gun_e = mknew(gun_base.new{ icon = 37, enemy = false, cooldown = 0x0.0002, -- frames between shots ammo = nil, maxammo = nil, munition=vulcan_e, dxs = {0.35, -0.35, -0.7, 0.7, 0.35, -0.35}, xoffs = {1, 0, -1, 1, 0, -1}, dxidx = 1, actually_shoot = function(self, x, y) local b = self.munition.new{ dx = self.dxs[self.dxidx], } b:spawn_at(self.xoffs[self.dxidx]+x,y) self.dxidx += 1 if (self.dxidx > #self.dxs) self.dxidx = 1 end }) vulcan_gun_p = mknew(vulcan_gun_e.new{ munition=vulcan_p, }) -->8 --ships, including player firespark = split"9, 8, 2, 5, 1" smokespark = split"13, 13, 5, 5" player = mknew(ship_m.new{ --shape sprite = 1, --index of ship sprite size = 1, --all ships are square; how many 8x8 sprites? hurt = { -- hurtbox - where this ship can be hit x_off = 3, -- upper left corner y_off = 2, -- relative to ship ulc width = 1, height = 3 }, sparks = firespark, -- see tab 9 sparkodds = 2, boss = true, -- dramatic special effects -- health hp = 3, -- current health, non-regenerating maxhp = 3, -- player only; other ships never heal shield = 2, -- regenerates maxshield = 2, -- xp, increments of 0x0.01 xp = 0, xptarget = 0x0.05, level = 1, -- gun main_gun = nil, -- assign at spawn time special_gun = nil, fire_off_x = 4, -- offset where bullets come from fire_off_y = 0, -- position x=52, -- x and y are for upper left corner y=96, xmomentum = 0, ymomentum = 0, maxspd = 2.5, -- momentum cap thrust = 0.25, -- momentum added from button ymin = 0, ymax = 120, -- stay on screen drag = 0.125, -- momentum lost per frame slip = false, -- does not slide down screen act = function(self) -- fetch buttons local b,th = btn(),self.thrust local blr = b&0x3 if blr == 1 then self.sprite=17 elseif blr==2 then self.sprite=18 else self.sprite=1 end --dx, dy, shoot_spec, shoot_main return (((b&0x2)>>1) - (b&0x1)) * th, (((b&0x8)>>3) - ((b&0x4)>>2)) * th, (b&0x10) > 0, (b&0x20) > 0 end, init = function(p) p.main_gun = zap_gun_p.new() -- ONE HIT MODE -- -- p.hp = 0 -- p.maxhp = 0 -- p.shield = 0 -- p.maxshield = 0 end, }) frownie = mknew(ship_m.new{ --shape sprite = 3, --index of ship sprite size = 1, --all ships are square; how many 8x8 sprites? hurt = { -- hurtbox - where this ship can be hit x_off = 0, -- upper left corner y_off = 1, -- relative to ship ulc width = 8, height = 6 }, sparks = smokespark, sparkodds = 8, -- health hp = 0.5, -- enemy ships need no max hp -- position x=60, -- x and y are for upper left corner y=8, xmomentum = 0, ymomentum = 0, maxspd = 2, -- momentum cap thrust = 0.12, -- momentum added from button drag = 0.07, -- momentum lost per frame slip = true, act = function(self) local tstate,dx = (1 + flr(4*t() + 0.5)) % 6,0 if (tstate==1 or tstate==2) dx=-self.thrust if (tstate>=4) dx=self.thrust return dx,0,false,false end, }) blocky = mknew(frownie.new{ sprite = 10, hp = 1.5, hurt = { x_off = 0, y_off = 0, width = 8, height = 7 }, ow = function(self) if self.hp < 1 then self.sprite = 11 else self.sprite = 10 end ship_m.ow(self) end }) spewy = mknew(frownie.new{ sprite=26, hurt = { x_off=0, y_off=1, width=8, height=5 }, hp=0.5, fire_off_x=4, fire_off_y=7, act=function(self) local dx,dy,shoot_spec=frownie.act(self) return dx, dy, shoot_spec, self.y > 10 end, init = function(ship) ship.main_gun=ship.main_gun or protron_gun_e.new{} end }) chasey = mknew(ship_m.new{ sprite = 5, size = 1, hurt = { x_off = 1, y_off = 2, width = 6, height = 5, }, sparks = smokespark, sparkodds = 8, hp = 1.5, shield = 1, maxshield = 1, fire_off_x = 4, fire_off_y = 7, maxspd = 2, thrust = 0.2, drag = 0.075, slip = true, init = function(ship) ship.main_gun=ship.main_gun or zap_gun_e.new{} end }) function chasey:act() self.xmin = max(primary_ship.x-8, 0) self.xmax = min(primary_ship.x + 8, 112 - 8*self.size) return 0, 0, false, self.y > 10 and self.x - 16 < primary_ship.x and self.x + 16 > primary_ship.x end xl_chasey=mknew(chasey.new{ size=2, maxspd=1.25, hurt = { x_off = 2, y_off = 4, width = 12, height = 10 }, fire_off_x = 8, fire_off_y = 15, hp = 19.5, shield = 5, boss = true, slip = false, act = function(self) local dx,dy,shoot_spec,shoot_main = chasey.act(self) if (self.y < 4) dy=self.thrust return dx,dy,shoot_spec,shoot_main end, draw = function(self) if(self.fx_pal) pal(self.fx_pal) sspr(40, 0, 8, 8, self.x, self.y, 16, 16) pal() end, init = function(ship) ship.main_gun=ship.main_gun or zap_gun_e.new{} end, }) -->8 -- collisions -- box: x, y, width, height function collides(box1, box2) return not ( box1.x>box2.x+box2.width or box1.y>box2.y+box2.height or box1.x+box1.width8 -- level and event system -- a level is a map from -- effective frame number to -- a list of actions for that -- frame. an action is a -- method name and its args. -- effective frame number stops -- when freeze count is nonzero -- a level is won when it hits -- the end-of-level sentinel -- and there are no more -- tracked enemies. -- lost when there are no -- player ships left. -- effective frame distance = 0 -- actual frame count since -- start of level times 0x0.0001 lframe = 0 -- do not advance distance when -- nonzero freeze = 0 eol = {} function load_level(levelfile) distance = 0 lframe = 0 freeze = 0 leveldone = false current_level = {} local found_eol = false if (type(levelfile)=="string") levelfile = csv(levelfile) for row in all(levelfile) do local x = current_level[row[1]] if row[2] == "eol" then found_eol = true assert(x==nil, "events on eol frame") current_level[row[1]] = eol else row.next = x current_level[row[1]]=row end end assert(found_eol) end function level_frame() lframe += 0x0.0001 if (current_level == nil) return true if freeze == 0 then distance += 1 local cbs = current_level[distance] if cbs ~= nil then if cbs == eol then current_level = nil return true else while cbs do assert(cbs[1] == distance) local f = _ENV[cbs[2]] assert(type(f) == "function", cbs[2].." at "..distance.." is not a function") f(unpack(cbs, 3)) cbs=cbs.next end end end end return false end -->8 -- example level function spawn_blocking_rnd_x(typ) freeze += 1 s = typ.new{ x = rnd(104), y = -7, ice = 1, orig_die = typ.die, die = function(self) freeze -= self.ice self.ice = 0 self:orig_die() end, } eships:push_back(s) return s end function spawn_frownie() return spawn_rnd(frownie) end function spawn_blocking_frownie() spawn_blocking_rnd_x(frownie) end function spawn_blocky() spawn_rnd(blocky) end function spawn_blocking_blocky() spawn_rnd(blocky, 1) end function spawn_spewy() return spawn_rnd(spewy) end function spawn_chasey() return spawn_rnd(chasey) end function spawn_blocking_spewy() freeze += 1 local s = spawn_spewy() s.ice = 1 s.die = function(self) freeze -= self.ice self.ice = 0 frownie.die(self) end end function spawn_bonus_frownie() local f = spawn_frownie() f.sprite = 7 f.die = function(self) spawn_repair_at(self.x+4, self.y+4) frownie.die(self) end end function spawn_bonus_vulcan_chasey() local c = spawn_chasey() c.main_gun=vulcan_gun_e.new{enemy=true} c.die = function(self) spawn_main_gun_at(self.x-1, self.y-1, vulcan_gun_p) chasey.die(self) end c.sprite=4 return c end function spawn_bonus_shield_chasey() local c = spawn_chasey() c.die = function(self) spawn_shield_upgrade_at(self.x-1, self.y-1) chasey.die(self) end c.sprite=4 return c end helpers = { spawn_frownie, spawn_frownie, spawn_frownie, spawn_blocky, spawn_blocky, spawn_chasey, spawn_spewy, } function spawn_blocking_boss_chasey() local c = spawn_rnd(xl_chasey, 1) local nextspawn = lframe + 0x0.0080 events:push_back{move=function() if lframe >= nextspawn then helpers[flr(rnd(#helpers))+1]() nextspawn += 0x0.0040 end return c.dead end} return c end function std_spawn(tnm, n, blocking, goodie,altspr) local typ = _ENV[tnm] assert(typ and typ.new, tostr(tnm).." not a class") for i=1,(n or 1) do spawn_rnd(typ, blocking, goodie,altspr) end end -- blocking: 1 or 0 function spawn_rnd(typ, blocking, goodie,altspr) blocking = blocking or 0 freeze += blocking s = typ.new{ x = rnd(104), y = -(typ.size * 8 - 1), ice=blocking, die=function(self) freeze -= self.ice self.ice=0 typ.die(self) spawn_goodie(goodie, self.x, self.y, self.size) end, } if (altspr) s.spr = altspr eships:push_back(s) return s end -- TODO: spawn_goodie compatible versions of gun drops -- TODO: goodie table function spawn_goodie(goodie_name, x, y, sz) if (not goodie_name or #goodie_name == 0) return local sh = sz and sz/2 or 0 _ENV[goodie_name].new{}:spawn_at(x+sh,y+sh) end function multi(times, interval, fnm, ...) local f,irm,vargs = _ENV[fnm],interval,pack(...) assert(type(f) == "function", fnm.." not a function") f(...) events:push_back{move=function() irm-=1 if irm <= 0 then irm=interval times-=1 f(unpack(vargs)) return times <= 1 end end} end -- then convert sample_level to csv. -- spawn_spec_gun_at and spawn_main_gun_at will need parsed forms. -- the boss also needs to be reachable, but one-off is fine. -- each row of level csv is offset,event,event-args... -- where offset,eol is a special case. example_level_csv=[[1,spawn_frownie 60,spawn_bonus_vulcan_chasey 61,spawn_blocky 85,spawn_spewy 100,spawn_spewy 115,spawn_spewy 130,spawn_bonus_frownie 145,spawn_spewy 200,spawn_bonus_shield_chasey 250,spawn_blocking_blocky 285,spawn_spec_gun_at,35,-11,blast_gun 310,spawn_blocking_blocky 310,spawn_blocking_blocky 310,spawn_blocking_blocky 311,spawn_frownie 350,spawn_main_gun_at,70,-11,protron_gun_p 401,spawn_frownie 420,spawn_blocking_frownie 430,spawn_bonus_vulcan_chasey 450,spawn_frownie 465,spawn_bonus_frownie 480,spawn_chasey 500,multi,20,12,spawn_blocking_blocky 501,spawn_bonus_frownie 620,spawn_blocking_blocky 630,spawn_bonus_shield_chasey 720,spawn_blocking_boss_chasey 721,eol]] -->8 -- standard events blip_fx = mknew{ cancel=false } function blip_fx:move() if (self.cancel) return true self.frames -= 1 if self.frames < 0 then self.obj.fx_pal = nil return true end return false end function blip_fx:abort() self.cancel=true end blip_pals = {} function init_blip_pals() for i=0,15 do local pp = {[0]=0} for j=1,15 do pp[j] = i end blip_pals[i]=pp end end function blip(obj, col, frames) obj.fx_pal = blip_pals[col] if (obj.___fx_pal_event) obj.___fx_pal_event:abort() events:push_back(blip_fx.new{frames=frames, obj=obj}) end bossspark = split"7,7,10,10,9,9,9,8,8,8,2,2,5,5" function boom(x,y,boominess,is_boss) local sp = firespark if is_boss then boominess *= 10 sp = bossspark end local boombase = min(0.023 * boominess, 0.25) local boombonus = min(0.05 * boominess, 1.25) for _=1,boominess do local angle = rnd(1) spark(sp,x+4,y+4,cos(angle), sin(angle),boombase+rnd(boombonus),1, true) end return end spark_particle=mknew{} function spark_particle:move() if (rnd(4) < 1) self.sidx += 1 if (self.sidx > #self.sprs) return true self.x += self.dx self.y += self.dy self.dx -= mid(0.05,-0.05, self.dx) self.dy -= mid(0.05,-0.05, self.dy) end function spark_particle:draw() pset(self.x,self.y,self.sprs[self.sidx]) end function spark(sprs, x, y, dx, dy, odds, fg) if (sprs==nil or flr(rnd(odds)) ~= 0) return local target = fg and intangibles_fg or intangibles_bg target:push_back(spark_particle.new{ x = x + rnd(4) - 2, y = y + rnd(4) - 2, sprs = sprs, sidx = 1, dx = dx + rnd(2) - 1, dy = dy + rnd(2) - 1, }) end -->8 -- powerups powerup = mknew(bullet_base.new{ -- animated sprite array: "sprites" -- to draw under or over anim, -- override draw, draw the -- under-part, call into -- powerup.draw(self), then -- draw the over-part width = 1, height = 1, -- note: make hurtboxes larger -- than sprite by 2px per side -- since ship hitbox is tiny -- but powerups should feel -- easy to pick up dx = 0, dy = 1.5, -- 0.75 after enemyspd category = enemy_blt_cat, -- collides with player ship damage = 0, anim_speed = 2, loop_pause = 30 -- affected by animspeed }) -- sprite indexes for "sheen" animation sheen8x8 = split"2,54,55,56,57,58,59,60,61" -- todo: draw two sprites -- on top of each other here -- so all powerups can share -- the "sheen" animation? function powerup:draw() spr(self.sprites[max(1, ((lframe<<16)\self.anim_speed) %(#self.sprites+self.loop_pause) -self.loop_pause +1)], self.x, self.y, self.width, self.height) end repair = mknew(powerup.new{ hurt = { x_off = -2, y_off = -2, width = 12, height = 12 }, x_off = 4, y_off = 0, sprites = sheen8x8, icon = 53, hitship = function(self, ship) if (ship ~= primary_ship) return false primary_ship.hp = min(primary_ship.maxhp, primary_ship.hp + 1) return true end, draw = function(self) spr(self.icon, self.x, self.y, self.width, self.height) powerup.draw(self) end }) function spawn_repair_at(x, y) repair.new():spawn_at(x, y) end shield_upgrade = mknew(repair.new{ icon=52 }) function shield_upgrade:hitship(ship) if (ship ~= primary_ship) return false primary_ship.maxshield += 1 return true end function spawn_shield_upgrade_at(x, y) shield_upgrade.new():spawn_at(x,y) end gun_swap = mknew(powerup.new{ hurt = { x_off = -2, y_off = -2, width = 16, height = 16 }, -- gun = gun_type.new{} x_off = 6, y_off = 0, width = 2, height = 2, sprites = {64, 66, 68, 70, 72, 74, 76, 78}, hitship = function(self, ship) if (ship ~= primary_ship) return false ship.main_gun = self.gun return true end, draw = function(self) powerup.draw(self) spr(self.gun.icon, self.x+2, self.y+2, 1, 1) end }) function spawn_main_gun_at(x, y, gunt) if (type(gunt)=="string") gunt=_ENV[gunt] local gun_p = gun_swap.new{ gun = gunt.new() } gun_p:spawn_at(x, y) end spec_gun_pl = { [1] = 2, [14] = 6, [2] = 14 } function spawn_spec_gun_at(x, y, gunt) if (type(gunt)=="string") gunt=_ENV[gunt] local gun_p = gun_swap.new{ gun = gunt.new(), hitship = function(self, ship) if (ship ~= primary_ship) return false ship.special_gun = self.gun return true end, draw = function(self) pal(spec_gun_pl) powerup.draw(self) pal() spr(self.gun.icon, self.x+2, self.y+2, 1, 1) end } gun_p:spawn_at(x, y) end __gfx__ 00000000000650000000000000000000bb0b50b59909209200cc0c00000000003b00000082000000e00e8002e00e800200333300002222000000000000000000 00000000006765000000000000cccc00b50b3055920940220c0000c000bbbb0037000000a2000000e0e8880240e8480403bbbb30028888200000000000000000 00700700006d6500000000000cddddd00b33335009444420c00c000c0b333330b7000000a8000000e88e2882e48e24823bbaabb3288aa8820000000000000000 00077000067c665000000000cdd10cd10b3dd350094dd42000c0000cb3350b35b7000000a8000000e88e2882484e24423ba77ab328a77a820000000000000000 00077000067d665000000000cd10cdd100b3350000944200c0000000b350b335b7000000a8000000e88e2882e84e28823ba77ab328a77a820000000000000000 0070070065666765000000000ddddd100b33355009444220c000000c03333350b7000000a800000008888820048488203bbaabb3288aa8820000000000000000 000000006506506500000000001111000b0b5050090920200c0000c00055550037000000a2000000008882000048420003bbbb30028888200000000000000000 00000000650000650000000000000000000b50000009200000c0cc00000000003b00000082000000000820000008200000333300002222000000000000000000 00000000000650000006500000000000b000000b80000000700000000bb0000008800000000000000009200000000000cccccccd000650000000000000000000 0000000000675000000765000000000000bbbb0080000000b0000000b76300008a920000000000009009200200000000c111111d006765000000000000000000 00000000006d6500006d6500000000000b0000b09000000030000000b663000089920000000550009994444200000000c111111d006d65000000000000000000 00000000067c6650067c6650000000000b0bb0b0a000000030000000033000000220000000576d009446544200000000c111111d067c66500000000000000000 00000000067d6650067d6650000000000b0bb0b00000000000000000000000000000000000566d009244442200000000c111111d067d66500000000000000000 000000005666657576667650000000000b0000b000000000000000000000000000000000000dd0009092220200000000c111111d656667650000000000000000 0000000056565066665656500000000000bbbb0000000000000000000000000000000000000000000090020000000000c111111d650650650000000000000000 00000000565000566500065000000000b000000b000000000000000000000000000000000000000000a00a0000000000cddddddd650000650000000000000000 000000000000000000000000000000000000000000a0008000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000090008000000000000000000000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000800a0000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000080090000000000000000000000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000a080000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000009080000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000cccccccccccccccc77000000007700000000770000000077000000000000000000000000000000000000000000000000 00000000000000000000000000000000c116611dc11ee11d70000000077000000007700000000770000000070000000000000000000000000000000000000000 00000000000000000000000000000000c1611c1dc11ee11d00000000770000000077000000007700000000770000000700000000000000000000000000000000 00000000000000000000000000000000c61111cdceeeeeed00000000700000000770000000077000000007700000007700000000000000000000000000000000 00000000000000000000000000000000c6111bcdceeeeeed00000000000000007700000000770000000077000000077000000007000000000000000000000000 00000000000000000000000000000000c161bbbdc11ee11d00000000000000007000000007700000000770000000770000000077000000000000000000000000 00000000000000000000000000000000c11ccb1dc11ee11d00000000000000000000000077000000007700000007700000000770000000070000000000000000 00000000000000000000000000000000cdddddddcddddddd00000000000000000000000070000000077000000077000000007700000000770000000000000000 cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000 c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000ceee2222eeed0000c2221111222d0000 c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c1ee2222ee1d0000ce22111122ed0000c2111111112d0000 c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c11e2222e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000 c1111111111d0000c1111111111d0000c1111ee1111d0000c11ee22ee11d0000c1e221122e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000 c1111111111d0000c1111ee1111d0000c111e22e111d0000c11e2112e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000 c1111111111d0000c1111ee1111d0000c111e22e111d0000c11e2112e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000 c1111111111d0000c1111111111d0000c1111ee1111d0000c11ee22ee11d0000c1e221122e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000 c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c11e2222e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000 c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c1ee2222ee1d0000ce22111122ed0000c2111111112d0000 c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000ceee2222eeed0000c2221111222d0000 cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000 __label__ 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007777777777777777 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666611111666665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666115151166665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666111611156665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666115161156665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666611111556665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666665555566665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007655555555555565 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007650b000000b0765 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765000bbbb000765 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000076500b0000b00765 00000000000000000000000000000000000000000000e00e800200000000000000000000000000000000000000000000000000000000000076500b0bb0b00765 00000000000000000000000050000000000000000000e0e8880200000000000000000000000000000000000000000000000000000000000076500b0bb0b00765 00000000000000000000000000000000000000000000e88e288200000000000000000000000000000000000000000000000000000000000076500b0000b00765 00000000000000000000000000000000000000000d00e88e2882000000000000000000000000000000000000000000000000000000000000765000bbbb000765 00000000000000000000000000000000000000000000e88e28820000000000000000000000000000000000000000000000000000000000007650b000000b0765 00000000000000000000000000000000000000000000088888200000000000000000000000000000000000000000000000000000000000007650000000000765 00000000000000000000000000000000000000000000008882000000000000000000000000000000000000000000000000000000000000007657777777777765 00000000000000000000000000000000000000000000000820000000000000000000000000000000000000000000000000000000000000007666666666666665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007655555555555565 0000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000765aeaeaeaeae765 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765eaeaeaeaea765 0000000000000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000765aeaeaeaeae765 00000000000000000000000000000000000000000000000000000009909209200000000000000000000000000000000000000000000000007657777777777765 00000000000000000000000000000000000000000000000000000009209402200000000000000000000000000000000000000000000000007666666666666665 00000000000000000000000000000000000000000000000000000000944442000000000000000000000000000000000000000000000000007666666666666665 0000000000000000000000000000000000000000000000000000000094dd42000000000000000000000000000000000000000000000000007666666666666665 00000000000000000000000000000000000000000000000000000000094420000000000000000000000000000000000000000000000000007666622222666665 00000000000000000000000000000000000000000000000000500000944422000000000000000000000000000000000000000000000000007666225552266665 00000000000000000000000000000000000000000000000000000000909202000000000000000000000000000000000000000000000000007666225262256665 00000000000000000000000000000000000000000000000000000000009200000000000000000000000000000000000000000000000000007666225652256665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666622222556665 00000000000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666665555566665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007655555555555565 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 00000000000000000005000000009200000000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 00000000050000000000000009009200200000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 000000000000000000d0000009994444200000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 00000000000000000000000009446544200000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 00000000000000000000000009244442200000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 0000000000000000000000000909222020000000000000000bbbb000000000000000000000000000000000000000000000000000000000007650000000000765 000000000000000000000000000900200000000000000000b3333300000000000000000000000000000000000000000000000000000000007650000000000765 000000000000000000000000000a00a0000000000000000b3350b350000000000000000000000000000000000000000000000000000000007650000000000765 00000000000000000000000000000000000000000000000b350b3350000000000000000000000000000000000000000000000000000000007657777777777765 00000000000000000000000000000000000000000000000033333500000000000000000000000000000000000000000000000000000000007666666666666665 00000000000000000000000000000000088000000000000005555000000000000000000000000000000000000000000000000000000000007655555555555565 000000000000000000000000000000058a9200000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 00000000000000000000000000000000899200000000000000000000008200000000000000000000000000000000000000000000000000007650000000000765 0000000000000000000000000000000002200000000000000000000000a200000000000000000000000000000000000000000000000000007650000000000765 0000000000000000000500000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007657777777777765 0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665 0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665 0000000000000000000000000088000000000050000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665 20000000000000000000000008a9200000000000000000000000000000a200000000000000000000000000000000000000000000000000007611161616111665 20000000000000000000000008992000000000000000000000000000008200000000000000000000000000000000000000000000000000007615151515151565 000000000000000000000000002200000d0000000000000000000000000000000000000000000000000000000000000000000000000000007611151515116565 0000000000000000000000000000000000000e00e800200000000000e00e80020000000000000000000000000000000000000000000000007615551115151665 0000000000000000000000000000000d00000e0e8880200000000000e0e888020000000000000000000000000000000000000000000000007615661115151565 0000000000000000000000000000000000d00e88e2882000000d0000e88e28820000000000000000000000000000000000000000000000007665666555656565 0000088000000000000880000000000000000e88e288200000000000e88e28820000000000000000000000000000000000000000000000007666666666666665 00008a9200000000008a92000000000000000e88e288200000000000e88e28820000000000000000000000000000000000000000000000007655555555555565 0000899200000000008992000000000000000088888200000000000008888820000000000000000000000000000000000000000000000000765aaaaaaaaaa765 0000022000000000000220000000000000000008882000000000000000888200000000000000000000000000000000000000000000000000765aaaaaaaaaa765 0000000000000000000000000000000000000000820000000000000000082000000000000000000000000000000000000000000000000000765aaaaaaaaaa765 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765aaaaaaaaaa765 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765a9a9a9a9a9765 00000000000088000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659a9a9a9a9a765 000000000008a920000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765a9a9a9a9a9765 00000000000899200000000000000000000008800000000000000000000000000000000000000000000000000000000000000000000000007659a9a9a9a9a765 0000000000002200000000000000000000008a92000000000000000000000000000000000000000000000000000000000000000000000000765a9a9a9a9a9765 00000000000000000000000000000000000089920000000000000000000000000000000000000000000000000000000000000000000000007659999999999765 00000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000000000000007659999999999765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659999999999765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659999999999765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659999999999765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654949494949765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659494949494765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654949494949765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659494949494765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654949494949765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654444444444765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654444444444765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654444444444765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654444444444765 00000000000000000000000000000000000000000000000000000000008200000000000000000000000000000000000000000000000000007654444444444765 0000000000000000000000000000000000000000000000000000000000a200000000000000000000000000000000000000000000000000007654444444444765 0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007657777777777765 0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665 0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665 0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665 0000000000000000000000000000000000000000000000000000000000a200000000000000000000000000000000000000000000000000007666666666666665 00000000000000000000000000000000000000000000000000000000008200000000000000000000000000000000000000000000000000007616166666611665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007615156666165565 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007611156666111665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007615156666651565 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007615156666116565 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007665656666655665 00000000000000000000000000000000000000000000000000000000000000000008800000000000000000000000000000000000000000007666666666666665 0000000000000000000000000000000000000000000000000000000000000000008a920000000000000000000000000000000000000000007655555555555565 00000000000000000000000000000000000000000000000000000000000000000089920000000000000000000000000000000000000000007652222750000765 00000000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000007652222750000765 00000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000007652222750000765 00000000000000000000000000000000000000000000000000000000067650000000000000000000000000000000000000000000000000007652222750000765 0000000000000000000000000000000000000000000000000000000006d650000000000000000000000000000000000000000000000000007652222750000765 0000000000000000000000000000000000000000000000000000000067c665000000000000000000000000000000000000000000000000007652222750000765 0000000000000000000000000000000000000000000000000000000067d665000000000000000000000000000000000000000000000000007652828750000765 00000000000000000000000000000000000000000000000000000006566676500000000000000000000000000000000000000000000000007658282750000765 00000000000000000000000000000000000000000000000000000006506506500000000000000000000000000000000000000000000000007652828750000765 00000000000000000000000000000000000000000000000000000006500006500000000000000000000000000000000000000000000000007658282750000765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007652828750000765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658282750000765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007657777757777765 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007555555555555555