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 -- readme.md --[[ main loop sequence ================== 1. level_frame 2. events 3. merge new_events into events 4. update bg intangibles 5. move ships (player first) 6. move bullets (player first) 7. calculate collisions 1. pship on eship 2. ebullet on pship 3. pbullet on eship 8. update fg intangibles 9. check for end of level draw order ---------- bottom to top: 1. intangibles_bg 2. player bullets 3. player ships 4. enemy ships 5. enemy bullets 6. intangibles_fg notes ----- intangibles_fg move()s after all collisions and other moves are processed. if an intangible is added to the list as a result of a collision or move, it will itself be move()d before it is drawn. data-driven items ================= guns and bullets both allow the most common behaviors to be expressed with data alone. ships only need a movement algorithm expressed. guns ---- * t - metatable for bullet type. fired once in the bullet's default direction per shot. * enemy - if true, fired bullets are flagged as enemy bullets. * icon - sprite index of an 8x8 sprite to display in the hud when the player has this gun. default is 20, a generic crosshair bullseye thing. * cooldown - min frames between shots. * ammo, maxammo - permitted number of shots. 0 is empty and unfireable. maxammo = 0 will cause a divide by zero so don't do that. if nil, ammo is infinite. default guns manage ammo and cooldown in shoot, then call actually_shoot to create the projectile. override only actually_shoot to change projectile logic while keeping cooldown and ammo logic. bullets ------- * dx, dy - movement per frame. player bullets use -dy instead. * enemyspd - multiplier for dx and dy on enemy bullets. default is 0.5, making enemy shots much easier to dodge * damage - damage per hit; used by ships * sprite - sprite index. * x_off, y_off - renamed for the next two vars. may revert * center_off_x - the horizontal centerpoint of the bullet, for positioning when firing. assume a pixel's coordinates refer to the upper left corner of the pixel; the center of a 2-width bullet with an upper left corner at 0 is 1, not 0.5. * top_off_y, bottom_off_y - also for positioning when firing. positive distance from top or bottom edge to image. top_off_y will usually be 0, bottom_off_y will not be when bullets are smaller than the sprite box. * width, height - measured in full sprites (8x8 boxes), not pixels. used for drawing. bullets despawn when above or below the screen (player or enemy bullets, respectively). by default, bullets despawn when they hit something. override hitship to change this. ships ____ ships move by calculating momentum, then offsetting their position by that momentum, then clamping their position to the screen (horizontally only for ships that autoscroll). ships that autoscroll (slip==true) then slide down by scrollspeed. fractional coordinates are ok. after movement, ships lose momentum (ship.drag along each axis). abs(momentum) can't exceed ship.maxspeed. ships gain momentum by acting like a player pushing buttons. the player ship actually reads buttons for this. act -- returns new acceleration: dx, dy, shoot_spec, shoot_main. dx and dy are change in momentum in px/frame. this is controls only -- friction is handled in ship:move (`drag` value). ships hitting another ship take 1 damage per frame of overlap. ships hitting a bullet check bullet.damage to find out how much damage they take. damage is applied to shields, then hp. damaged ships flash briefly - blue (12) if all damage was shielded, white (7) if hp was damaged. a ship that then has 0 or less hp calls self:die() and tells the main game loop to remove it. shieldcooldown is the interval between restoring shield points. shieldpenalty is the delay before restoring points after any damage, reset to this value on every damaging hit (whether it is absorbed by the shield or not) -- shield behaves like halo and other shooters in its heritage, where it recovers if you avoid damage for a while. not that there is any safe cover in this kind of game. ships do not repair hp on their own. negative-damage bullets are treated as 0, but a bullet can choose to repair the ship it hits in its own hitship method, or otherwise edit it (changing weapons, refilling weapon ammo). powerups are therefore a kind of bullet. levels ====== a level is a table mapping effective frame number to functions. when a level starts, it sets lframe ("level frame") and distance to 0. every frame, level_frame increments lframe by 0x0.0001. then if the level is not frozen, it increments distance by 1.0 and runs the function in the level table for exactly that frame number (if any). distance is therefore "nonfrozen frames", and is used to trigger level progress. lframe always increments. ships are encouraged to use lframe to control animation and movement, and may use distance to react to level progress separately from overall time. remember to multiply lframe-related stuff by 0x0001. a special sentinel value, eol, marks the end of the level. (the level engine doesn't know when it's out of events, so without eol, the level will simply have no events forever.) when it finds eol, level_frame throws away the current level and tells the main loop that it might be done. the main loop agrees the level is over and the player has won when the level has reached eol and there are no more enemy ships, enemy bullets, or background events remaining. player ships, player bullets, and intangibles are not counted. level freezing -------------- the level is frozen when the global value freeze > 0. generally, something intending to block level progress (a miniboss, a minigame, etc.) increments freeze and prepares some means of decrementing it when it no longer wants to block level progress. most commonly, we want to block until some specific ship or group of ships has died. for these ships, override ship:die to decrement freeze. make sure to set ship.dead in any new ship:die method so anything else looking at it can recognize the ship as dead. for anything else, you probably want an event to figure out when to unfreeze. levels start at 1 ----------------- distance is initialized to 0 but gets incremented before the first time the engine looks for events. therefore, the first frame of the level executes level[1]. since levelframe executes before anything else, level[1] sets up the first frame drawn in the level. the player does not see a blank world before level[1] runs. level[1] can therefore be used to reconfigure the player ship, set up backgrounds, start music, kick off some kind of fade-in animation, etc. events ====== the global list "events" stores 0-argument functions which are called every frame. if they return true, they are removed from the list and not run again; if they return false, they stay and will be called in later frames. the level does not end while the events table is nonempty. events are most commonly used to set up something for later (for example, blip uses an event to remove the fx_pallete from the flashing ship when the blip expires), but can also be used to implement a "level within a level" that does something complicated until it's done. if you froze the level when creating the event, remember to thaw it (freeze -= 1) on all paths that return true. to do complex stuff in events, use a closure or a metatable that specifies __call. to avoid editing the events list while it is being iterated, events that create new events must add those events to new_events rather than events. new_events is only valid during the "event execution" stage, so events created at any other time must go directly on events without using new_events. intangibles =========== the intangibles_fg and intangibles_bg lists contain items with :move and :draw. like ships and bullets, they move during _update60 and draw during _draw. they are not checked for collisions. intangibles_bg moves/draws before anything else moves or draws. intangibles_fg moves/draws last. this controls whether your intangible object draws in front of or behind other stuff. you probably want intangibles_bg for decorative elements and intangibles_fg for explosions, score popups, etc. there's no scrolling background engine but intangibles_bg could be used to create one, including using the map (otherwise unused in this engine) for the purpose. intangibles do not prevent the level from ending. like bullets and ships, if :move returns true, they are dropped. ]] -->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