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() mode = game_mode init_blip_pals() wipe_game() -- redundant? load_level(example_level_csv) game_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_game() xpwhoosh = nil 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() primary_ship.main_gun = zap_gun_p.new() primary_ship.main_gun:peel() end function _update60() mode:update() end function call_f(x) return x:f() end function call_move(x) return x:move() end function updategame() if (primary_ship.xp >= primary_ship.xptarget) and (lframe - primary_ship.last_xp_frame > 0x0.000f) and (not primary_ship.dead) then mode = rearm_mode.new() return _update60() end 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 game_state = win end if (not pships.next) game_state = lose if primary_ship.xp >= primary_ship.xptarget then if not xpwhoosh then xpwhoosh = 0 else xpwhoosh += 1 if (xpwhoosh > 60) xpwhoosh = 0 end else xpwhoosh = nil end end function _draw() mode:draw() end function drawgame_top() camera() fillp(0) drawgame() if (game_state == game) fadelvl = -45 if (game_state == win) dropshadow("win",50,61,11) if (game_state == lose) dropshadow("fail",48,61,8) fadescreen() end game_mode = { update = updategame, draw = drawgame_top, } 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,1) draw_gun_info("🅾️",1,116,29,2) 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) if xpwhoosh then clip(115,58,4,60) rectfill(115,58,118,117,0xaa) local voff = 5*xpwhoosh+6 rectfill(115,118-voff,118,117-voff+10,0xbb) rectfill(115,118-voff+11,118,117-voff+20,0xba) clip() else vertmeter(115,58,118,117,primary_ship.xp, primary_ship.xptarget, powcols) end -- 60 px vertically. note that -- there was at one point an -- off-by-one and I'm not sure -- it's actually fixed 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,gn) dropshadow(lbl,x,y,fgc) inset(114,y+7,125,y+18) inset(114,y+20,125,y+24) if (not primary_ship.special_guns) return local gun = primary_ship.special_guns[gn] if (not gun) return 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 function vertmeter(x0,y0,x1,y1,val,maxval,cols) if ((val <= 0) or (maxval <= 0)) return if val < 0x0.001 or maxval < 0x0.001 then val *= 16 maxval *= 16 end val=min(val, maxval) local h = y1-y0 local px = val*h/maxval \ 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 shield_refresh_ready = 0, 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) return -- blow up and drop xp local sz4 = self.size * 4 local cx, cy, xp, z = self.x + sz4, self.y + sz4, self.xp or 0, 0 boom(cx, cy, 3*sz4, self.boss) if xp > 0x0.01f3 then -- dec 499 -- spawn a huge gem with all -- overage XP, min 100 spawn_xp_at(cx, cy, 0, xp-0x0.018f) xp = 0x0.018f -- dec 399 z += 1 end -- 100, 25, 5, 1 for gsz in all{0x0.0064, 0x0.0019, 0x0.0005, 0x0.0001} do while xp >= gsz do spawn_xp_at(cx, cy, z, gsz) xp -= gsz z += 1 end end 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_spec1, shoot_spec2 = 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) self:maybe_shoot(self.main_gun) if (shoot_spec1 and self.special_guns) self:maybe_shoot(self.special_guns[1]) if (shoot_spec2 and self.special_guns) self:maybe_shoot(self.special_guns[2]) 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 } -- gun_base subtypes are -- level-up options that, -- as an action, assign -- themselves to the player function gun_base:action() local item = self.new() item:peel() item.ammo = item.maxammo if not primary_ship.special_guns then primary_ship.special_guns = {item} else add(primary_ship.special_guns, item) end end -- make shot type unique so -- stat modifications do not -- damage base data function gun_base:peel() self.munition = mknew(self.munition.new()) end -- default firing behavior: -- single shot function gun_base:actually_shoot(x, y) self.munition.new{}:spawn_at(x, y) end 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 > 145) 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 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.0020, -- frames between shots munition = zap_e, }) zap_gun_p = mknew(zap_gun_e.new{ munition = zap_p, hdr = "mAIN gUN", }) 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 = -1, awaitcancel = false, -- disable damage for 4 frames -- when hitting something -- todo: rewrite all ship hit -- logic so i can avoid -- repeating hits to the -- same ship instead of -- using a cooldown hitship = function(self, _) if self.damage > 0 and not self.awaitcancel then self.awaitcancel = true once_next_frame(function() new_events:push_back{ wait = 4, 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.0078, -- 120 frames between shots ammo = 5, maxammo = 5, munition = blast, hdr = "bLASTER", body= [[plasma orb cuts through enemies. slow. ammo: 5 rate: 1/2sec dmg: 4 ]], }) 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.0040, -- 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, maxammo = 20, cooldown = 0x0.0018, hdr = "pROTRON", body = [[spray shots in a dense arc. ammo: 20 rate: 2/sec dmg: 1 ]], }) 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.0003, -- 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, maxammo = 100, hdr = "vULCAN", body = [[rapid fire in a v shape. ammo: 100 rate: 20/sec dmg: 0.5 ]], }) -->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 in increments of 0x0.0001 xp = 0, xptarget = 0x0.0004, last_xp_frame = 0, level = 1, -- gun main_gun = nil, -- assign at spawn time special_guns = 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 = 1.5, -- momentum cap thrust = 0.1875, -- momentum added from button ymin = 0, ymax = 120, -- stay on screen drag = 0.0625, -- 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, }) function player:small_upgrade_opts() local cdr, pr = (self.shieldcooldown - 0x0.000f) / 8, (self.shieldpenalty - 0x0.003c) / 9 if (cdr == 0 and self.shieldcooldown > 0x0.000f) cdr = 0x0.0001 if (pr == 0 and self.shieldpenalty > 0x0.003c) pr = 0x0.0001 local ret = {{ icon=53, hdr="hull", body=[[ armor +2 hp]], action=function() self.maxhp += 2 self.hp += 2 end, },{ icon=52, hdr="shield", body=[[ capacity +1 hp]], action=function() self.maxshield += 1 self.shield += 1 end, },{ icon=1, hdr="thrusters", body=[[performance move faster, steer faster]], action=function() --maxspd thrust drag self.maxspd += 0.5 self.thrust += 0.0625 self.drag += 0.03125 end, }} if cdr > 0 then add(ret, { icon = 6, hdr = "shield", body=[[charge rate ]] .. tostr(ceil(100 * cdr / self.shieldcooldown)) .. "% faster", action = function() self.shieldcooldown -= cdr end }) end if pr > 0 then add(ret, { icon = 6, hdr = "shield", body=[[disruption ]] .. tostr(ceil(100 * pr / self.shieldpenalty)) .. "% shorter", action = function() self.shieldpenalty -= pr end }) end return ret 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, hp = 0.5, -- enemy ships need no max hp xp = 0x0.0001, -- 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, xp = 0x0.0002, 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, xp = 0x0.0003, 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, xp = 0x0.0004, 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 }) -- todo: use constraints 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, xp = 0x0.000a, 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_vulcan_chasey() local c = spawn_chasey() c.main_gun=vulcan_gun_e.new{enemy=true} 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) end, } if (altspr) s.spr = altspr eships:push_back(s) return s 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_vulcan_chasey 61,spawn_blocky 85,spawn_spewy 115,spawn_spewy 130,spawn_frownie 145,spawn_frownie 180,spawn_spewy 230,spawn_chasey 250,spawn_blocking_blocky 310,spawn_blocking_blocky 310,spawn_blocking_blocky 310,spawn_blocking_blocky 311,spawn_frownie 401,spawn_frownie 420,spawn_blocking_frownie 430,spawn_vulcan_chasey 450,spawn_frownie 465,spawn_frownie 480,spawn_chasey 500,multi,20,12,spawn_blocking_blocky 501,spawn_frownie 620,spawn_blocking_blocky 630,spawn_vulcan_chasey 720,spawn_blocking_boss_chasey 721,eol]] -->8 -- standard events blip_fx = mknew{ cancel=false } function blip_fx:move() if (self.cancel) return true self.frames -= 1 if self.frames < 0 then self.obj.fx_pal = nil return true end return false end function blip_fx:abort() self.cancel=true end blip_pals = {} function init_blip_pals() for i=0,15 do local pp = {[0]=0} for j=1,15 do pp[j] = i end blip_pals[i]=pp end end function blip(obj, col, frames) obj.fx_pal = blip_pals[col] if (obj.___fx_pal_event) obj.___fx_pal_event:abort() events:push_back(blip_fx.new{frames=frames, obj=obj}) end bossspark = split"7,7,10,10,9,9,9,8,8,8,2,2,5,5" function boom(x,y,boominess,is_boss) local sp = firespark if is_boss then boominess *= 10 sp = bossspark end local boombase = min(0.023 * boominess, 0.25) local boombonus = min(0.05 * boominess, 1.25) for _=1,boominess do local angle = rnd(1) spark(sp,x+4,y+4,cos(angle), sin(angle),boombase+rnd(boombonus),1, true) end return end spark_particle=mknew{} function spark_particle:move() if (rnd(4) < 1) self.sidx += 1 if (self.sidx > #self.sprs) return true self.x += self.dx self.y += self.dy self.dx -= mid(0.05,-0.05, self.dx) self.dy -= mid(0.05,-0.05, self.dy) end function spark_particle:draw() pset(self.x,self.y,self.sprs[self.sidx]) end function spark(sprs, x, y, dx, dy, odds, fg) if (sprs==nil or flr(rnd(odds)) ~= 0) return local target = fg and intangibles_fg or intangibles_bg target:push_back(spark_particle.new{ x = x + rnd(4) - 2, y = y + rnd(4) - 2, sprs = sprs, sidx = 1, dx = dx + rnd(2) - 1, dy = dy + rnd(2) - 1, }) end -->8 -- powerups xp_gem = mknew(bullet_base.new{ dx = 0, dy = 0.75, width=1, -- not used for spr but height=1,-- bullet_base uses it category = enemy_blt_cat, damage = 0, hurt = { x_off = -2, y_off = -2, width = 8, height = 8, }, x_off = 2, y_off = 2, }) function xp_gem:draw() local s,qx,qy = self.qsprite,0,0 -- sprite map position: -- sprite id to x and y, -- offset shifts specific low -- bits of lframe up to the the -- bit with value 4 as a cheap -- way to pick an anim frame if (lframe&0x0.003 == 0) qx, qy = (lframe&0x0.0004)<<16, (lframe&0x0.0008)<<15 sspr( (s%16<<3)+qx, (s\16<<3)+qy, 4, 4, self.x, self.y ) end -- todo: "magnetic" behavior -- when near player ship function xp_gem:hitship(ship) if (ship ~= primary_ship or primary_ship.dead) return false primary_ship.xp += self.val primary_ship.last_xp_frame = lframe return true end -- small gems for 1, 5, 25 -- exactly; else huge function spawn_xp_at(x, y, off, amt) x += rnd(off+off)-off y += rnd(off+off)-off xp_gem.new{ qsprite=amt == 0x0.0001 and 32 or amt == 0x0.0005 and 33 or amt == 0x0.0019 and 34 or 35, val = amt, }:spawn_at(mid(x, 0, 124),mid(y,-4,125)) end -->8 -- upgrade options -- all these return -- a [2] of rearm_t: -- -- icon: sprite id -- hdr: title text -- body: text -- action: callback -- (method) spec_gunt = { protron_gun_p, vulcan_gun_p, blast_gun, } -- picks n random items from -- tbl; permutes tbl, selected -- items at end function pick(tbl, n) local ret, top={}, #tbl for x=top,top-n,-1 do local idx = 1+rnd(x)\1 add(ret, tbl[idx]) tbl[idx]=tbl[x] tbl[x]=ret[#ret] end return ret end -- add a new gun function spec_gun_opts() return pick(spec_gunt, 2) end -- major upgrades function big_opts() return {{ icon=1, hdr="placeholder", body="placeholder", action = function() end, }, { icon=1, hdr="placeholder", body="placeholder", action = function() end, }} end -- ordinary upgrades function small_opts() -- todo: include gun opts return pick(primary_ship:small_upgrade_opts(), 2) end -->8 -- rearm screen rearm_mode = mknew{ sel=1, bfm=1, crt_frm = 1, pos=-1, init=function(this) poke(0x5f5c, 255) --no btnp repeat rearm_mode.shuffle(this) end, } crt={-91,-166,-2641,-1441,-23041,23295,-20491,24570} function rearm_mode:glow_box(x0, y0, x1, y1, c, cf) for i,v in ipairs{c[1],c[2],c[1],0} do i -= 1 rect(x0+i,y0+i,x1-i,y1-i,v) end fillp(crt[self.crt_frm&0xff]) rectfill(x0+4, y0+4, x1-4, y1-4, cf) fillp() end function easeoutbounce(t) local n1=7.5625 local d1=2.75 if (t<1/d1) then return n1*t*t; elseif(t<2/d1) then t-=1.5/d1 return n1*t*t+.75; elseif(t<2.5/d1) then t-=2.25/d1 return n1*t*t+.9375; else t-=2.625/d1 return n1*t*t+.984375; end end function rearm_mode:frame_col(hot) if (not hot) return {4,10} if (self.bfm<=16) return {14,7} return {2,8} end function rearm_mode:draw_option(id) local rec = self.options[id] self:glow_box(0,0,55,100,self:frame_col(self.sel == id),1) spr(rec.icon,5, 5) print(rec.hdr, 13, 8, 7) print(rec.body, 5, 15, 6) end function rearm_mode:pos_frac() local pos = self.pos if (not pos) return if (pos < 0) return 1-easeoutbounce(1+pos) if (pos > 0) return (1-pos)*(1-pos) return 0 end function rearm_mode:shuffle() -- these will be placeholders -- until the upgrade deck -- is a thing that exists local lev = primary_ship.level + 1 if lev == 4 or lev == 12 then self.options = spec_gun_opts() elseif lev % 4 == 0 then self.options = big_opts() else self.options = small_opts() end end function rearm_mode:draw() drawgame_top() local frac = self:pos_frac() camera(frac * 55, 0) self:draw_option(1) camera(frac * -128 + (1-frac) * -56, 0) self:draw_option(2) camera(0, -28 * frac) self:glow_box(0,101,111,127,self:frame_col(self.sel < 0),1) spr(96,15,107,4,2) print("full ammo\nfull shield\n+50% health",54, 106, 6) end function rearm_mode:update_pos() local pos = self.pos if (not pos) return if (pos == 0) then if (primary_ship.xp < primary_ship.xptarget) self.pos = 1 xpwhoosh = nil return end if (pos < 0) pos = min(pos + 0x0.05, 0) if pos > 0 then pos -= 0x0.1 if (pos <= 0) pos = 999 end self.pos = pos end function rearm_mode:update() self:update_pos() if self.pos > 1 then mode = game_mode return -- do not advance frame end local sel, bfm = self.sel, self.bfm if (btn(3) and sel > 0 or btn(2) and sel < 0) sel=-sel if (btn(0)) sel = 1 if (btn(1)) sel = 2 if (btn()&0xF ~= 0) and bfm >= 10 or bfm >= 30 then bfm = 1 else bfm += 1 end self.bfm = bfm if primary_ship.xp < primary_ship.xptarget then sel = 0 elseif btnp(4) or btnp(5) and self.pos == 0 then if sel < 0 then -- todo: sound: rearm primary_ship.shield = primary_ship.maxshield -- todo: rewrite for three guns local specs = primary_ship.special_guns if specs then specs[1].ammo = specs[1].maxammo if (specs[2]) specs[2].ammo = specs[2].maxammo end primary_ship.hp = min(primary_ship.maxhp, primary_ship.hp + primary_ship.maxhp/2) primary_ship.xp -= primary_ship.xptarget / 2 else local c = self.options[sel] if c then -- todo: sound: upgrade c:action() primary_ship.xp -= primary_ship.xptarget primary_ship.xptarget += primary_ship.level * 0x0.0002 primary_ship.level += 1 if (primary_ship.xp >= primary_ship.xptarget) self:shuffle() end end end self.sel = sel end __gfx__ 00000000000650000000000000000000bb0b50b59909209200cc0c00000000003b00000082000000e00e8002e00e800200333300002222000000000000000000 00000000006765000000000000cccc00b50b3055920940220c0000c000bbbb0037000000a2000000e0e8880240e8480403bbbb30028888200000000000000000 00700700006d6500000000000cddddd00b33335009444420c00c000c0b333330b7000000a8000000e88e2882e48e24823bbaabb3288aa8820000000000000000 00077000067c665000000000cdd10cd10b3dd350094dd42000c0000cb3350b35b7000000a8000000e88e2882484e24423ba77ab328a77a820000000000000000 00077000067d665000000000cd10cdd100b3350000944200c0000000b350b335b7000000a8000000e88e2882e84e28823ba77ab328a77a820000000000000000 0070070065666765000000000ddddd100b33355009444220c000000c03333350b7000000a800000008888820048488203bbaabb3288aa8820000000000000000 000000006506506500000000001111000b0b5050090920200c0000c00055550037000000a2000000008882000048420003bbbb30028888200000000000000000 00000000650000650000000000000000000b50000009200000c0cc00000000003b00000082000000000820000008200000333300002222000000000000000000 00000000000650000006500000000000b000000b80000000700000000bb0000008800000000000000009200000000000cccccccd000650000000000000000000 0000000000675000000765000000000000bbbb0080000000b0000000b76300008a920000000000009009200200000000c111111d006765000000000000000000 00000000006d6500006d6500000000000b0000b09000000030000000b663000089920000000550009994444200000000c111111d006d65000000000000000000 00000000067c6650067c6650000000000b0bb0b0a000000030000000033000000220000000576d009446544200000000c111111d067c66500000000000000000 00000000067d6650067d6650000000000b0bb0b00000000000000000000000000000000000566d009244442200000000c111111d067d66500000000000000000 000000005666657576667650000000000b0000b000000000000000000000000000000000000dd0009092220200000000c111111d656667650000000000000000 0000000056565066665656500000000000bbbb0000000000000000000000000000000000000000000090020000000000c111111d650650650000000000000000 00000000565000566500065000000000b000000b000000000000000000000000000000000000000000a00a0000000000cddddddd650000650000000000000000 060007000600070006600770766c777c0000000000a0008000000000000000000000000000000000000000000000000000000000000000000000000000000000 6cd07cd06cd07cd06ccd7ccd6ccd7ccd000000000090008000000000000000000000000000000000000000000000000000000000000000000000000000000000 0d000d006cd07cd06ccd7ccd6ccd7ccd0000000000800a0000000000000000000000000000000000000000000000000000000000000000000000000000000000 000000000d000d000dd00dd0cdd1cdd0000000000080090000000000000000000000000000000000000000000000000000000000000000000000000000000000 0600060006000600066006607667766c00000000000a080000000000000000000000000000000000000000000000000000000000000000000000000000000000 67d06c7067d06c70677d6cc7677d6cc7000000000009080000000000000000000000000000000000000000000000000000000000000000000000000000000000 0d00070067d06c7067cd6cc767cd6cc7000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000 000000000d0007000dd007707dd1c771000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000cccccccccccccccc77000000007700000000770000000077000000000000000000000000000000000000000000000000 00000000000000000000000000000000c116611dc11ee11d70000000077000000007700000000770000000070000000000000000000000000000000000000000 00000000000000000000000000000000c1611c1dc11ee11d00000000770000000077000000007700000000770000000700000000000000000000000000000000 00000000000000000000000000000000c61111cdceeeeeed00000000700000000770000000077000000007700000007700000000000000000000000000000000 00000000000000000000000000000000c6111bcdceeeeeed00000000000000007700000000770000000077000000077000000007000000000000000000000000 00000000000000000000000000000000c161bbbdc11ee11d00000000000000007000000007700000000770000000770000000077000000000000000000000000 00000000000000000000000000000000c11ccb1dc11ee11d00000000000000000000000077000000007700000007700000000770000000070000000000000000 00000000000000000000000000000000cdddddddcddddddd00000000000000000000000070000000077000000077000000007700000000770000000000000000 cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000 c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000ceee2222eeed0000c2221111222d0000 c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c1ee2222ee1d0000ce22111122ed0000c2111111112d0000 c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c11e2222e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000 c1111111111d0000c1111111111d0000c1111ee1111d0000c11ee22ee11d0000c1e221122e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000 c1111111111d0000c1111ee1111d0000c111e22e111d0000c11e2112e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000 c1111111111d0000c1111ee1111d0000c111e22e111d0000c11e2112e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000 c1111111111d0000c1111111111d0000c1111ee1111d0000c11ee22ee11d0000c1e221122e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000 c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c11e2222e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000 c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c1ee2222ee1d0000ce22111122ed0000c2111111112d0000 c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000ceee2222eeed0000c2221111222d0000 cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 04444400044444440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 447777700477777a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 477aaa7a0477aaaa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 47a0047a047a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 47a0447a047a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 47a4477a047a44400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 477777a00477777a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 477770000422aaaa2222000200000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 47a77700022ee0002eeee002e00022e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 47a4777002ea2e002e002e02ee022ee0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 47a0477a22ea2e002e002e02e2e2e2e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 47a0047a2e2222e02e222e02e02e02e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 47a0047a2eeeeeea2eeee002e02e02e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0aa000aa2e7aa2ea2e00e002e02e02e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 000000002e0002e02e002e02e02e02e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 000000000e0000e00e000e00e00e00e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 __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