pico-8 cartridge // http://www.pico-8.com version 41 __lua__ -- generic shmup -- by kistaro windrider -- dev docs in tab 8 game = 1 win = 2 lose = 3 debug = {} 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 -- generate standard "overlay" -- constructor for type tt. -- if more is defined, generated -- new calls more(ret) after -- ret is definitely not nil -- before calling setmetatable. -- use to initialize mutables. function mknew(tt, more) local mt = {__index=tt} -- check "more" only once ever if more then tt.new = function(ret) if (not ret) ret = {} more(ret) setmetatable(ret, mt) return ret end else tt.new=function(ret) if (not ret) ret = {} setmetatable(ret, mt) return ret end end end linked_list = {} mknew(linked_list, 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 -- list is no longer valid. function linked_list:vore(x) if (not x.next) return self.tail.next = x.next self.tail = x.tail 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_bullet_mt() init_powerup_mt() init_ship_mt() wipe_level() primary_ship.main_gun = new_gun_of(zap_gun_t) load_level(example_level) state = game pal(2,129) pal() 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 = new_p1() 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() 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() new_events = linked_list.new() events:strip(call_move) events:vore(new_events) 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 = new_collider() 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() drawgame() draw_debug() if (state == game) fadelvl = -45 if (state == win) dropshadow("win",50,61,11) if (state == lose) dropshadow("fail",48,61,8) fadescreen() end fadetable=csv[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 1,1,129,129,129,129,129,129,129,129,0,0,0,0,0 2,2,2,130,130,130,130,130,128,128,128,128,128,0,0 3,3,3,131,131,131,131,129,129,129,129,129,0,0,0 4,4,132,132,132,132,132,132,130,128,128,128,128,0,0 5,5,133,133,133,133,130,130,128,128,128,128,128,0,0 6,6,134,13,13,13,141,5,5,5,133,130,128,128,0 7,6,6,6,134,134,134,134,5,5,5,133,130,128,0 8,8,136,136,136,136,132,132,132,130,128,128,128,128,0 9,9,9,4,4,4,4,132,132,132,128,128,128,128,0 10,10,138,138,138,4,4,4,132,132,133,128,128,128,0 11,139,139,139,139,3,3,3,3,129,129,129,0,0,0 12,12,12,140,140,140,140,131,131,131,1,129,129,129,0 13,13,141,141,5,5,5,133,133,130,129,129,128,128,0 14,14,14,134,134,141,141,2,2,133,130,130,128,128,0 15,143,143,134,134,134,134,5,5,5,133,133,128,128,0]] function fadescreen() fadelvl += 0.25 if fadelvl < 0 then pal() return end local i = flr(fadelvl) for c=0,15 do if i+1>=16 then pal(c,0,1) else pal(c,fadetable[c+1][i+1],1) end end end function draw_debug() cursor(0,0,7) -- uncomment the followingh -- to display object counts --[[ print("es "..tostr(#eships)) print("eb "..tostr(#ebullets)) print("pb "..tostr(#pbullets)) print("ib "..tostr(#intangibles_bg)) print("if "..tostr(#intangibles_fg)) print("v "..tostr(#events)) ]] if (#debug==0) return foreach(debug,print) debug={} end function drawgame() cls() clip(0,0,112,128) 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" 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("🅾️",2,116,31,primary_ship.special_gun) dropshadow("pwr",114,59,1) inset(114,66,125,92) fillp(0x5a5a) vertmeter(115,67,124,91,primary_ship.power, primary_ship.max_power, powcols) dropshadow("h s",114,97,1) inset(114,104,125,125) line(119,105,119,124,119) line(120,105,120,125,85) vertmeter(115,105,118,124,primary_ship.hp, primary_ship.maxhp, hpcols) vertmeter(121,105,124,124,primary_ship.shield, primary_ship.maxshield,{204,220,221}) 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) return local h = y1-y0 local px = -flr(-(h*val)\maxval) 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,85) end function dropshadow(str, x, y, col) print(str, x+1, y+1, 5) print(str, x, y, col) end function grab_p1_butts() if state ~= game then local r = {0,0,0,0,0} r[0] = 0 return r end local b = btn() return { [0]=b&0x1, [1]=(b&0x2)>>1, [2]=(b&0x4)>>2, [3]=(b&0x8)>>3, [4]=(b&0x10)>>4, [5]=(b&0x20)>>5 } end -->8 --ships, including player -- XXX BOOKMARK XXX function init_ship_mt() setmetatable(player, ship_t) setmetatable(frownie, ship_t) setmetatable(blocky, frownie_t) setmetatable(chasey, ship_t) end firespark = split"9, 8, 2, 5, 1" smokespark = split"13, 13, 5, 5" player = { --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 and power hp = 3, -- current health, non-regenerating maxhp = 3, -- player only; other ships never heal shield = 2, -- regenerates, using power maxshield = 2, shieldcost = 300, -- power cost to refill shield generator = 1.5, -- 1 feels too slow -- 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 drag = 0.125, -- momentum lost per frame slip = false, -- does not slide down screen grab_butts = function(self) -- fetch buttons local butts = grab_p1_butts() if butts[0] == butts[1] then self.sprite = 1 elseif butts[0] > 0 then self.sprite = 17 else self.sprite = 18 end return butts end } player_t = { __index = player } function new_p1() p = { main_gun = new_gun_of(zap_gun_t, false) } setmetatable(p, player_t) return p end frownie = { --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 and power hp = 1, -- 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, grab_butts = function(discard_self) -- buttons are effectively analog -- and negative buttons work just fine! local butts = {} local tstate = (1 + flr(4*t() + 0.5)) % 6 butts[0] = ((tstate==1 or tstate==2) and 1) or 0 butts[1] = ((tstate==4 or tstate==5) and 1) or 0 for b=2, 5 do butts[b]=0 end return butts end, -- button fetch algorithm } frownie_t = { __index = frownie } blocky = { sprite = 10, hp = 2, 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_t.__index.ow(self) end } blocky_t = { __index = blocky } function spawn_spewy_at(x, y) local spewy = { x = x, y = y, sprite = 26, power = -20, hurt = { x_off = 0, y_off = 1, width = 8, height = 5 }, hp = 1, maxpower = 70, generator = 0.5, main_gun = new_gun_of(protron_gun_t, true), fire_off_x = 4, fire_off_y = 7, grab_butts = function() local butts = frownie.grab_butts() butts[5] = 1 return butts end, } setmetatable(spewy, frownie_t) eships:push_back(spewy) return spewy end chasey = { sprite = 5, size = 1, hurt = { x_off = 1, y_off = 2, width = 6, height = 5, }, sparks = smokespark, sparkodds = 8, hp = 2, shield = 1, maxshield = 1, shieldcost = 180, fire_off_x = 4, fire_off_y = 7, maxspd = 2, thrust = 0.2, drag = 0.075, slip = true, grab_butts = function(self) local butts = {0,0,0,0,0} butts[0] = 0 if (self.x < primary_ship.x) butts[1] = 1 if (self.x > primary_ship.x) butts[0] = 1 if (self.x - 16 < primary_ship.x and self.x + 16 > primary_ship.x) butts[5] = 1 return butts end, } chasey_t = { __index = chasey } function spawn_chasey_at(x, y) local c = { x = x, y = y, main_gun = new_gun_of(zap_gun_t, true) } setmetatable(c, chasey_t) eships:push_back(c) return c end function spawn_xl_chasey_at(x, y) local c = { x = x, y = y, size = 2, maxspd = 1.25, hurt = { x_off = 2, y_off = 4, width = 12, height = 10 }, hp = 20, shield = 5, boss = true, slip = false, main_gun = new_gun_of(zap_gun_t, true), grab_butts = function(self) local butts = chasey.grab_butts(self) if (self.y < 4) butts[3] = 1 return butts 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, } setmetatable(c, chasey_t) eships:push_back(c) return c end -->8 --ship behavior scrollrate = 0.25 --in px/frame ship_m = { -- ships have no shield by default shield = 0, maxshield = 0, shieldcost = 32767.9, shieldcooldown = 180, -- default generator behavior: -- 10 seconds for a full charge max_power = 600, power = 600, generator = 1, -- power gen per frame invincible_until = -32768, slip = true, -- most enemies slide xmomentum = 0, ymomentum = 0, } ship_t = { __index = ship_m, } 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:move() self:refresh_shield() self.power = min(self.max_power, self.power + self.generator) butt = self:grab_butts() if (butt[5] > 0) self:maybe_shoot(self.main_gun) if (butt[4] > 0) self:maybe_shoot(self.special_gun) if (butt[0]-butt[1] ~= 0 or butt[2]-butt[3] ~= 0) spark(self.sparks, self.x + 4*self.size, self.y + 4*self.size, butt, self.thrust, self.sparkodds) self.xmomentum += (self.thrust * butt[1]) - (self.thrust * butt[0]) self.ymomentum += (self.thrust * butt[3]) - (self.thrust * butt[2]) self.xmomentum = mid(-self.maxspd, self.maxspd, self.xmomentum) self.ymomentum = mid(-self.maxspd, self.maxspd, self.ymomentum) self.x += self.xmomentum self.y += self.ymomentum if self == primary_self then self.x = mid(0, 112 - 8 * self.size, self.x) self.y = mid(0, 128 - 8 * self.size, self.y) end --friction local d = self.drag self.xmomentum -= mid(d, -d, self.xmomentum) self.ymomentum -= mid(d, -d, 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 if (self.power < gun.power) return if (not gun:shoot(self.x + self.fire_off_x, self.y + self.fire_off_y)) return self.power -= gun.power 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 if (lframe < self.invincible_until) return false self.shield_refresh_ready = lframe + self.shieldcooldown 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 if (self.power < self.shieldcost) return self.shield += 1 self.power -= self.shieldcost self.shield_refresh_ready = lframe + self.shieldcooldown 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 -- callback for that frame. -- 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 lframe = 0 -- do not advance distance when -- nonzero freeze = 0 eol = {} function load_level(lvltbl) distance = 0 lframe = 0 freeze = 0 current_level = {} -- copy the level so we can -- modify it dynamically -- without losing the original for frame, cb in pairs(lvltbl) do current_level[frame] = cb end leveldone = false end function level_frame() lframe += 1 if (current_level == nil) return true if freeze == 0 then distance += 1 local cb = current_level[distance] if cb ~= nil then if cb == eol then current_level = nil return true else cb() end end end return false end -->8 -- example level function spawn_rnd_x(mt) s = { x = rnd(104), y = -(mt.__index.size * 8 - 1) } setmetatable(s, mt) eships:push_back(s) return s end function spawn_blocking_rnd_x(mt) freeze += 1 s = { x = rnd(104), y = -7, ice = 1, die = function(self) freeze -= self.ice self.ice = 0 mt.__index.die(self) end } setmetatable(s, mt) eships:push_back(s) return s end function spawn_frownie() return spawn_rnd_x(frownie_t) end function spawn_blocking_frownie() spawn_blocking_rnd_x(frownie_t) end function spawn_blocky() spawn_rnd_x(blocky_t) end function spawn_blocking_blocky() spawn_blocking_rnd_x(blocky_t) end function spawn_spewy() return spawn_spewy_at(rnd(104), -7) end function spawn_chasey() return spawn_chasey_at(rnd(104), -7) 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_t.__index.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_t.__index.die(self) end end function spawn_bonus_vulcan_chasey() local c = spawn_chasey() c.main_gun=new_gun_of(vulcan_gun_t, true) c.die = function(self) spawn_main_gun_at(self.x-1, self.y-1, vulcan_gun_t) 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() freeze += 1 local c = spawn_xl_chasey_at(44, -15) c.ice = 1 c.die = function(self) freeze -= self.ice self.ice = 0 chasey_t.__index.die(self) end local nextspawn = lframe + 120 events:push_back{move=function() if lframe >= nextspawn then helpers[flr(rnd(#helpers))+1]() nextspawn += 60 end return c.dead end} return c end example_level = { [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_chasey, [250]=spawn_blocking_blocky, [285]=function() spawn_spec_gun_at(35, -11, blast_gun_t) end, [310]=function() spawn_blocking_blocky() spawn_blocking_blocky() spawn_blocking_blocky() end, [311]=spawn_frownie, [350]=function() spawn_main_gun_at(70, -11, protron_gun_t) end, [401]=spawn_frownie, [420]=spawn_blocking_frownie, [430]=spawn_bonus_vulcan_chasey, [450]=spawn_frownie, [465]=spawn_bonus_frownie, [480]=spawn_chasey, [500]=function() local tnext = lframe local remain = 20 events:push_back{move=function() if (lframe < tnext) return false spawn_blocking_blocky() tnext = lframe + 12 remain -= 1 return (remain <= 0) end} end, [501]=spawn_bonus_frownie, [620]=spawn_blocking_blocky, [700]=spawn_blocking_boss_chasey, [701]=eol } -->8 -- bullets and guns function init_bullet_mt() setmetatable(zap, bullet_t) setmetatable(zap_gun, gun_t) setmetatable(blast, bullet_t) setmetatable(blast_gun, gun_t) setmetatable(protron, bullet_t) setmetatable(protron_gun, gun_t) setmetatable(vulcan, bullet_t) setmetatable(vulcan_gun, gun_t) end function new_gun_of(mt, is_enemy) local g = { enemy = is_enemy } setmetatable(g, mt) return g end zap = { --shape psprite = 8, --index of player ammo sprite esprite = 9, -- index of enemy 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 }, center_x_off = 1, -- how to position by ship bottom_y_off = 0, top_y_off = 0, damage = 1, dx = 0, -- px/frame dy = 8, hitship = function(_, _) return true end } zap_t = { __index = zap } zap_gun = { enemy = false, power = 20, -- power consumed per shot cooldown = 10, -- frames between shots ammo = nil, -- unlimited ammo - main gun t = zap_t -- metatable of bullet to fire } zap_gun_t = { __index = zap_gun } blast = { --shape psprite = 12, --index of player ammo sprite esprite = 3, -- index of enemy 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 }, center_x_off = 4, -- how to position by ship bottom_y_off = 0, top_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 events:push_back{move = 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 return true end} end end } blast_t = { __index = blast } blast_gun = { icon = 13, enemy = false, power = 0, -- ammo, not power cooldown = 30, -- frames between shots ammo = 5, maxammo = 5, t = blast_t -- metatable of bullet to fire } blast_gun_t = { __index = blast_gun } protron = { --shape psprite = 23, --index of player ammo sprite esprite = 24, -- index of enemy 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 = 2, height = 2 }, center_x_off = 1, -- how to position by ship bottom_y_off = 4, top_y_off = 0, damage = 1, dx = 0, -- px/frame dy = 3, } protron_t = { __index = protron } protron_gun = { icon = 25, enemy = false, power = 35, cooldown = 15, -- frames between shots ammo = nil, maxammo = nil, actually_shoot = function(self, x, y) local sprite = protron.psprite if (self.enemy) sprite=protron.esprite for i=1,3 do local b = { enemy=self.enemy, sprite=sprite, dx = i, dy = 4-i } setmetatable(b, protron_t) b:spawn_at(x,y) local b2 = { enemy=self.enemy, sprite=sprite, dx = -i, dy = 4-i } setmetatable(b2, protron_t) b2:spawn_at(x,y) end local bup = { enemy=self.enemy, sprite=sprite, dy=4 } setmetatable(bup, protron_t) bup:spawn_at(x,y) end } protron_gun_t = { __index = protron_gun } vulcan = { --shape psprite = 22, --index of player ammo sprite esprite = 21, -- index of enemy 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 = 1, height = 4 }, center_x_off = 0.5, -- how to position by ship bottom_y_off = 4, top_y_off = 0, damage = 0.5, dx = 0, -- px/frame dy = 4, } vulcan_t = { __index = vulcan } vulcan_gun = { icon = 37, enemy = false, power = 8, cooldown = 2, -- frames between shots ammo = nil, maxammo = nil, 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 sprite = self.enemy and vulcan.esprite or vulcan.psprite local b = { enemy=self.enemy, sprite=sprite, dx = self.dxs[self.dxidx], } setmetatable(b, vulcan_t) b:spawn_at(self.xoffs[self.dxidx]+x,y) self.dxidx += 1 if (self.dxidx > #self.dxs) self.dxidx = 1 end } vulcan_gun_t = { __index = vulcan_gun } -->8 -- bullet and gun behaviors bullet_base = { enemyspd = 0.5 } bullet_t = { __index = bullet_base } gun_base = { shoot_ready = -32768, icon = 20 } gun_t = { __index = gun_base } function bullet_base:hitship(_) self:die() return true end function bullet_base:die() end function bullet_base:move() self.x += self.dx if self.enemy then self.y += self.dy if self.y > 128 then self:die() return true end else self.y -= self.dy if self.y < -8*self.height then self:die() return true end end return false end function bullet_base:draw() spr(self.sprite, self.x, self.y, self.width, self.height) end function bullet_base:spawn_at(x, y) self.x = x - self.center_x_off if self.enemy then self.dx *= self.enemyspd self.dy *= self.enemyspd self.y = y + self.top_y_off ebullets:push_back(self) else self.y = y - (8 * self.height) + self.bottom_y_off pbullets:push_back(self) end 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 function gun_base:actually_shoot(x, y) b = { } setmetatable(b, self.t) if self.enemy then b.enemy = true b.sprite = b.esprite else b.enemy = false b.sprite = b.psprite end b:spawn_at(x, y) return true end -->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. don't modify a list while it's being iterated. ships that want to create ships and bullets that want to create bullets need to create an event to do this (which will run before the ship and bullet phases of the next frame). events that want to create events add to new_events, rather than events. 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 ---- * power - cost in generator power to fire. may be 0. field directly read by ships; required in all 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. ships manage generator power before asking the gun to shoot. this behavior is in ship_m:maybe_shoot. 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 * psprite, esprite - index of player or enemy sprite. * 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. grab_butts - ship thrust control based on btn() api. returns a table indexed from 0..5 with 0 to not push this button and 1 to push it. ships can use fractional or out-of-range numbers to get varying amounts of thrust, including using negative numbers for thrust in the opposite direction. 4 and 5 just check for nonzeroness to attempt to fire. 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. ships have power, from 0 to ship.maxpower, increasing by ship.generator per frame. in maybe_shoot, ships check that they have power to fire before trying to fire (the gun itself checks ammo and cooldown), and spend that power if they fire. power is also used to restore shields - ship.shieldcost per point of shields. shieldcooldown is the interval between restoring shield points, which is reset when a ship takes damage (regardless of whether that damage is stopped by the shield or not). therefore: * damaged ships spend power repairing shields, which may affect ability to fire guns. this looks like a slow firing rate because the ship will eventually recover enough energy to fire. * a ship firing nonstop will typically be unable to recover any shields because it will not have energy to do so. 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. then if the level is not frozen (more on that later), it increments distance 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. 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. weird coding conventions ======================== * i am inconsistent between including underscores or not (sorry) * a triple underscore prefix is used for fields created by invasive algorithms (those algorithms that modify the items they're given to perform some kind of bookkeeping) ]] -->8 -- standard events blip_fx = { 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 mknew(blip_fx) function blip(obj, col, frames) local p = {[0]=0} obj.fx_pal = p for i=1,15 do p[i]=col end 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) local butts = {0, sin(angle), 0} butts[0] = cos(angle) spark(sp,x+4,y+4,butts,boombase+rnd(boombonus),1, true) end return end function spark(sprs, x, y, butts, thrust, odds, fg) if (sprs==nil or flr(rnd(odds)) ~= 0) return thrust *= 2.5 local target = fg and intangibles_fg or intangibles_bg target:push_back{ x = x + rnd(4) - 2, y = y + rnd(4) - 2, sprs = sprs, sidx = 1, dx = (butts[0] - butts[1]) * thrust + rnd(2) - 1, dy = (butts[2] - butts[3]) * thrust + rnd(2) - 1, draw = function(self) pset(self.x, self.y, self.sprs[self.sidx]) end, move = function(self) 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 } end -->8 -- powerups function init_powerup_mt() setmetatable(powerup, bullet_t) setmetatable(gun_swap, powerup_t) end powerup = { -- 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 enemy = true, -- 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" powerup_t = { __index = powerup } -- 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\self.anim_speed) %(#self.sprites+self.loop_pause) -self.loop_pause +1)], self.x, self.y, self.width, self.height) end function spawn_repair_at(x, y) local repair = { hurt = { x_off = -2, y_off = -2, width = 12, height = 12 }, center_x_off = 4, top_y_off = 0, bottom_y_off = 0, sprites = sheen8x8, 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(53, self.x, self.y, self.width, self.height) powerup.draw(self) end } setmetatable(repair, powerup_t) repair:spawn_at(x, y) end gun_swap = { hurt = { x_off = -2, y_off = -2, width = 16, height = 16 }, -- gun = new_gun_of(t, false) center_x_off = 6, top_y_off = 0, bottom_y_off = 4, 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 } gun_swap_t = { __index=gun_swap } function spawn_main_gun_at(x, y, mt) local gun_p = { gun = new_gun_of(mt, false) } setmetatable(gun_p, gun_swap_t) gun_p:spawn_at(x, y) end spec_gun_pl = { [1] = 2, [14] = 6, [2] = 14 } function spawn_spec_gun_at(x, y, mt) local gun_p = { gun = new_gun_of(mt, false), 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 } setmetatable(gun_p, gun_swap_t) 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 0000000000000000000000000000000000000000cccccccc77000000007700000000770000000077000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000c11ee11d70000000077000000007700000000770000000070000000000000000000000000000000000000000 0000000000000000000000000000000000000000c11ee11d00000000770000000077000000007700000000770000000700000000000000000000000000000000 0000000000000000000000000000000000000000ceeeeeed00000000700000000770000000077000000007700000007700000000000000000000000000000000 0000000000000000000000000000000000000000ceeeeeed00000000000000007700000000770000000077000000077000000007000000000000000000000000 0000000000000000000000000000000000000000c11ee11d00000000000000007000000007700000000770000000770000000077000000000000000000000000 0000000000000000000000000000000000000000c11ee11d00000000000000000000000077000000007700000007700000000770000000070000000000000000 0000000000000000000000000000000000000000cddddddd00000000000000000000000070000000077000000077000000007700000000770000000000000000 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