0 ]']ú(ï}WÿÍu§ðp8·d%qy)6o(öA¦ö úÿÿÿÿ”°ÂVl† 0ÿÿÿÿÿÿÿÿ2Tv˜ºÜþïÍ«‰gE#2Tv˜ºÜþ2Tv˜ºÜþ ‡  89:;<<<<<<<==== = = = = = >>?“00^0w0}1}0|1[0K1;0:1)2)2(2(2'3330344õ5ô7ô8ó9ò;ñ<=?x—?ðððððððððððððððððÀÌÌÌÌ ÀÌÌðððÀ ÀÀðððÀ ÀÀðððÿÀÌÌ ÀÀÿÀ ÀÀðÿÀ ÀÀðÀÌÌÌÌ ÀÌÌððÿÿððÿðÿððððððððððððððððððð ðððððÿ ÿððÿðÿð ÿÿðð ÿð "ÿ ð"" ÿ" ðð ð ðð ðððð ð  ð ððð ð ðð ððð ðÿ ð  ÿ ðò ðÿÿÿ ð ÿÿÿ ð  "ðÿÿÿ" ""ÿÿÿ"ðð "ÿÿÿÿ "ðÿÿÿð "ÿÿÿð "òÿÿððð  "ÿððð ÿ"òðð" ðÿ/"ððð"ð ÿÿ"ððð " ðÿÿ""ððð" ðÿÿ "ððð " ÿÿ"ðÿððð" ð/ÿ "ÿ " ÿ/"ÿð"ðÿÿ "ÿððÿ/" "ÿððÿÿ "  "ÿðÿÿ"""ÿÿÿÿ"""ÿÿÿÏ ÀÌ ÂÌÌ "ÂÌÌðÿÿÿÀÀÌ "À À"Àÿÿðð Ì ,À ,ÀÂ"ðÿÿÿðÀÀ " À ÀÀÿÿðððÀÌÌ " À ÀÀÿÿÿðÀð"  ÀÀðÿÿðð "  ÂÀÿÿðð"ðÀÌÌ ÀÌÌ ÂÌÌðÿÿððð"ðÿ ÿÿÿ ÿÿÿðÿ ðÿÿðÿ ÿÿð/ ðÿÿÿ ðÿÿððÿ ÿÿð ÿ ðÿÿðÿ/ Àÿÿðð ðÿÿðð ððÿÿððð ð ÿÿð ððÿÿððð ð ÿÿðð ð ðÿÿðð ðð ðÿ ðð ÿð òð ÿ ÿðð ÿð ÿð ÿð ððð ððð ðð ðððð ððð ððð ðððÿðððÿÿðÿðÿððÿÿðÿðÿððððððððððððððððððððÿðððÿðÿð"""ð "ðð  ðð ð ð  ðð ððð "ð"""ðð¥5-- title: Vector based Space shooter -- author: grayblight -- desc: Asteroids clone using vectors inspired by the Vectrex! -- site: website link -- license: MIT License -- version: 0.1 -- script: lua -- Character definitions using points -- Each character is a table of points, where each point is a {x, y} pair. -- The points are connected in sequence to form the shape of the character. local characters = { A = {{1,7}, {4,1}, {7,7}, {2,4}, {6,4}}, B = {{1,1}, {1,7}, {5,7}, {6,6}, {6,5}, {5,4}, {1,4}, {5,4}, {6,3}, {6,2}, {5,1}, {1,1}}, C = {{7,1}, {2,1}, {1,2}, {1,6}, {2,7}, {7,7}}, D = {{1,1}, {1,7}, {5,7}, {6,6}, {6,2}, {5,1}, {1,1}}, E = {{7,1}, {1,1}, {1,7}, {7,7}, {1,4}, {5,4}}, F = {{7,1}, {1,1}, {1,7}, {1,4}, {5,4}}, G = {{7,1}, {2,1}, {1,2}, {1,6}, {2,7}, {6,7}, {6,4}, {4,4}}, H = {{1,1}, {1,7}, {1,4}, {7,4}, {7,7}, {7,1}}, ["0"] = {{1,1}, {5,1}, {5,7}, {1,7}, {1,1}}, ["1"] = {{1,1}, {3,1}, {3,7}, {1,7}, {5,7}}, ["2"] = {{1,1}, {5,1}, {5,4}, {1,4}, {1,7}, {5,7}}, ["3"] = {{1,1}, {5,1}, {5,4}, {1,4}, {5,4}, {5,7}, {1,7}}, ["4"] = {{1,1}, {1,4}, {5,4}, {5,1}, {5,7}}, ["5"] = {{5,1}, {1,1}, {1,4}, {5,4}, {5,7}, {1,7}}, ["6"] = {{5,1}, {1,1}, {1,4}, {5,4}, {5,7}, {1,7}, {1,4}}, ["7"] = {{1,1}, {5,1}, {5,7}}, ["8"] = {{5,4}, {5,1}, {1,1}, {1,7}, {5,7}, {5,4}, {1,4}}, ["9"] = {{5,4}, {1,4}, {1,1}, {5,1}, {5,7}, {1,7}}, -- Add more characters as needed } -- Function to draw a character at a specified position and scale function draw_char(char, x, y, scale) local points = characters[char] if points then for i = 1, #points - 1 do local p1 = points[i] local p2 = points[i + 1] line(x + p1[1] * scale, y + p1[2] * scale, x + p2[1] * scale, y + p2[2] * scale, 12) end end end -- Function to draw a string of text function draw_text(text, x, y, spacing, scale) for i = 1, #text do local char = text:sub(i, i) draw_char(char, x + (i - 1) * spacing * scale, y, scale) end end -- Vector Shape Object VectorShape = {} VectorShape.__index = VectorShape function VectorShape:new(points) local shape = { points = points or {}, x = 120, -- Position y = 68, angle = 0, -- Rotation angle scale = 1, -- Scaling factor velocity_x = 0, -- Velocity in the x direction velocity_y = 0, -- Velocity in the y direction acceleration = 0.1, -- Acceleration rate friction = 0.99 -- Friction to slow down over time } setmetatable(shape, VectorShape) return shape end function VectorShape:rotate_point(x, y, angle) local cos_a = math.cos(angle) local sin_a = math.sin(angle) return x * cos_a - y * sin_a, x * sin_a + y * cos_a end function VectorShape:draw(color) local num_points = #self.points for i = 1, num_points do local x1, y1 = self:rotate_point(self.points[i][1] * self.scale, self.points[i][2] * self.scale, self.angle) local x2, y2 = self:rotate_point(self.points[(i % num_points) + 1][1] * self.scale, self.points[(i % num_points) + 1][2] * self.scale, self.angle) -- Translate to the shape's position x1, y1 = self.x + x1, self.y + y1 x2, y2 = self.x + x2, self.y + y2 -- Draw line between points using the specified color line(x1, y1, x2, y2, color or 12) end end function VectorShape:set_position(x, y) self.x = x self.y = y end function VectorShape:set_rotation(angle) self.angle = angle end function VectorShape:set_scale(scale) self.scale = scale end function VectorShape:apply_motion() -- Calculate gravitational pull towards the center local center_x = 120 local center_y = 68 local pull_strength = 0.02 -- Adjust this value to control the strength of the pull local direction_x = center_x - self.x local direction_y = center_y - self.y -- Normalize the direction vector local distance = math.sqrt(direction_x^2 + direction_y^2) if distance > 0 then direction_x = direction_x / distance direction_y = direction_y / distance end -- Apply the gravitational pull to the velocity self.velocity_x = self.velocity_x + direction_x * pull_strength self.velocity_y = self.velocity_y + direction_y * pull_strength -- Update position based on velocity self.x = self.x + self.velocity_x self.y = self.y + self.velocity_y -- Apply friction to simulate gradual stopping self.velocity_x = self.velocity_x * self.friction self.velocity_y = self.velocity_y * self.friction -- Screen wrapping (for asteroids-style gameplay) if self.x < 0 then self.x = 239 end if self.x > 239 then self.x = 0 end if self.y < 0 then self.y = 135 end if self.y > 135 then self.y = 0 end end function VectorShape:accelerate() -- Accelerate the ship in the direction it's facing local angle_rad = self.angle - math.pi / 2 -- Corrected to align with forward direction self.velocity_x = self.velocity_x + math.cos(angle_rad) * self.acceleration self.velocity_y = self.velocity_y + math.sin(angle_rad) * self.acceleration end function VectorShape:decelerate() -- Decelerate (or accelerate backwards) in the direction opposite to the facing local angle_rad = self.angle - math.pi / 2 -- Corrected to align with backward direction self.velocity_x = self.velocity_x - math.cos(angle_rad) * self.acceleration self.velocity_y = self.velocity_y - math.sin(angle_rad) * self.acceleration end -- Bullet Object Bullet = {} Bullet.__index = Bullet function Bullet:new(x, y, angle) local bullet = { x = x, y = y, angle = angle - math.pi / 2, -- Adjust to match forward direction speed = 8, -- Speed of the bullet life = 60 -- Lifetime of the bullet in frames } setmetatable(bullet, Bullet) return bullet end function Bullet:update() -- Move the bullet in the direction it's facing self.x = self.x + math.cos(self.angle) * self.speed self.y = self.y + math.sin(self.angle) * self.speed -- Decrease the life of the bullet self.life = self.life - 1 -- Return false if the bullet's life is over, true otherwise return self.life > 0 end function Bullet:draw() pix(self.x, self.y, 12) -- Draw the bullet as a single pixel end -- Explosion Object Explosion = {} Explosion.__index = Explosion function Explosion:new(x, y, is_black_hole, color) local explosion = { x = x, y = y, lines = {}, life = is_black_hole and -1 or 30, -- If it's a black hole, it never "dies" is_black_hole = is_black_hole, color = color or 12 -- Default to color index 12 if no color is provided } -- Create lines for the explosion effect for i = 1, 10 do local angle = (math.pi * 2 / 10) * i table.insert(explosion.lines, { angle = angle, length = is_black_hole and 5 or 10, -- Shorter lines for the black hole speed = 0 + math.random() * 2 }) end setmetatable(explosion, Explosion) return explosion end function Explosion:update() for _, l in ipairs(self.lines) do l.length = l.length + l.speed l.angle = l.angle + 0.1 -- Rotate lines for cool effect end if not self.is_black_hole then self.life = self.life - 1 end return self.is_black_hole or self.life > 0 end function Explosion:draw() for _, l in ipairs(self.lines) do local x2 = self.x + l.length * math.cos(l.angle) local y2 = self.y + l.length * math.sin(l.angle) line(self.x, self.y, x2, y2, self.color) -- Use the specified color end end -- Enemy Object Enemy = {} Enemy.__index = Enemy function Enemy:new(points, x, y, scale, speed) local enemy = { points = points, x = x or math.random(0, 239), -- Spawn at random x position y = y or math.random(0, 135), -- Spawn at random y position angle = math.random() * math.pi * 2, -- Random angle scale = scale or 1, speed = speed or 1, -- Default speed of 1 if not provided velocity_x = math.cos(math.random() * math.pi * 2) * (speed or 1), velocity_y = math.sin(math.random() * math.pi * 2) * (speed or 1) } setmetatable(enemy, Enemy) return enemy end function Enemy:apply_motion() self.x = self.x + self.velocity_x self.y = self.y + self.velocity_y -- Screen wrapping if self.x < 0 then self.x = 239 end if self.x > 239 then self.x = 0 end if self.y < 0 then self.y = 135 end if self.y > 135 then self.y = 0 end end function Enemy:draw() local num_points = #self.points for i = 1, num_points do local x1, y1 = self:rotate_point(self.points[i][1] * self.scale, self.points[i][2] * self.scale, self.angle) local x2, y2 = self:rotate_point(self.points[(i % num_points) + 1][1] * self.scale, self.points[(i % num_points) + 1][2] * self.scale, self.angle) -- Translate to the enemy's position x1, y1 = self.x + x1, self.y + y1 x2, y2 = self.x + x2, self.y + y2 -- Draw line between points using a specified color line(x1, y1, x2, y2, 2) -- Use a different color for enemies end end function Enemy:rotate_point(x, y, angle) local cos_a = math.cos(angle) local sin_a = math.sin(angle) return x * cos_a - y * sin_a, x * sin_a + y * cos_a end -- Check for collision between a bullet and an enemy function check_collision(bullet, enemy) local distance = math.sqrt((bullet.x - enemy.x) ^ 2 + (bullet.y - enemy.y) ^ 2) return distance < 10 * enemy.scale -- Adjust the threshold as needed end -- Variables for score popup local score_popups = {} -- Function to add a score popup function add_score_popup(x, y, text) table.insert(score_popups, { x = x, y = y, text = text, frames = 0, scale = 1 }) end -- Function to update and draw score popups function update_and_draw_score_popups() for i = #score_popups, 1, -1 do local popup = score_popups[i] popup.frames = popup.frames + 1 -- Scale up the text over time popup.scale = 0.1 + popup.frames * 0.05 -- Draw the popup text local text_width = #popup.text * 8 * popup.scale -- Approximate width based on character width (8 pixels) draw_text(popup.text, popup.x - text_width / 2, popup.y, 8, popup.scale) -- Remove the popup after 30 frames if popup.frames > 30 then table.remove(score_popups, i) end end end -- Initialize game objects local ship_points = { { 0, -10 }, -- Tip of the ship (nose) { 5, 5 }, -- Bottom right corner of the ship { 0, 2.5 }, -- Middle bottom of the ship { -5, 5 }, -- Bottom left corner of the ship { 0, -10 } -- Back to the tip of the ship (to close the shape) } enemies = {} enemy_spawn_timer = 0 enemy_spawn_interval = 120 -- Spawn a new enemy every 2 seconds ship = VectorShape:new(ship_points) ship:set_position(120, 68) ship:set_scale(0.5) bullets = {} explosions = {} score = 0 black_hole = Explosion:new(120, 68, true,15) score_display_size = 1 -- TIC-80 main loop function TIC() cls(0) black_hole:update() black_hole:draw() -- Update ship's rotation with left/right controls if btn(3) then ship:set_rotation(ship.angle + 0.1) elseif btn(2) then ship:set_rotation(ship.angle - 0.1) end -- Accelerate/Decelerate with up/down controls if btn(0) then ship:accelerate() elseif btn(1) then ship:decelerate() end -- Shooting with the A button (btn(4)) if btnp(4) then local bullet = Bullet:new(ship.x, ship.y, ship.angle) table.insert(bullets, bullet) sfx(4,"C-4",-1,1) end -- Apply motion and draw the ship ship:apply_motion() ship:draw() -- Update and draw bullets for i = #bullets, 1, -1 do local bullet = bullets[i] if not bullet:update() then table.remove(bullets, i) else bullet:draw() end end -- Update and draw explosions for i = #explosions, 1, -1 do local explosion = explosions[i] if not explosion:update() then table.remove(explosions, i) else explosion:draw() end end update_and_draw_score_popups() -- Spawn new enemies enemy_spawn_timer = enemy_spawn_timer + 1 if enemy_spawn_timer >= enemy_spawn_interval then local enemy_shape = { { 0, -5 }, { 3, -3 }, { 5, 0 }, { 3, 3 }, { 0, 5 }, { -3, 3 }, { -5, 0 }, { -3, -3 }, { 0, -5 } } local new_enemy = Enemy:new(enemy_shape, nil, nil, math.random(10,20)*0.1, 1.5) -- Added default scale and speed table.insert(enemies, new_enemy) enemy_spawn_timer = 0 end -- Update and draw enemies for i = #enemies, 1, -1 do local enemy = enemies[i] enemy:apply_motion() enemy:draw() -- Check collision with bullets for j = #bullets, 1, -1 do local bullet = bullets[j] if check_collision(bullet, enemy) then -- Create explosion and increase score local explosion = Explosion:new(enemy.x, enemy.y, false, 2) table.insert(explosions, explosion) table.remove(enemies, i) table.remove(bullets, j) score = score + 100 score_display_size = 2 -- Add score popup at the enemy's position add_score_popup(enemy.x, enemy.y, "100") -- Play sound effect sfx(5) break end end end if score_display_size > 1 then score_display_size = score_display_size - 0.1 end -- Draw the score text draw_text(tostring(score), 10, 10, 7, score_display_size) end