local paddle_x = nil local paddle_y = nil local paddle_width = 0.1 local paddle_height = 0.02 local wall_thickness = 0.01 local missiles = {} local missile_radius = 0.005 local missile_trail_length = 7 local cities = {} local city_width = 0.07 local city_height = 0.05 local window_width = nil local window_height = nil local viewport_x_offset = nil local viewport_y_offset = nil local scale = nil function setScreenDimensions(width, height) window_width = width window_height = height viewport_x_offset = 0 viewport_y_offset = 0 scale = math.min(width, height) viewport_x_offset = (window_width - scale) / 2 viewport_y_offset = (window_height - scale) / 2 end function toScreenCoordinates(x, y) local screen_x = x * scale + viewport_x_offset local screen_y = y * scale + viewport_y_offset return screen_x, screen_y end function toScreenSize(size) return size * scale end function fromScreenCoordinate(x, y) local logical_x = (x - viewport_x_offset) / scale local logical_y = (y - viewport_y_offset) / scale return logical_x, logical_y end function love.load() love.mouse.setVisible(false) local width, height = love.graphics.getDimensions() setScreenDimensions(width, height) movePaddle(0) spawnCities() spawnMissile(0.5, 0.1, -0.1, -0.2) spawnMissile(0.5, 0.1, 0.1, 0.1) end function spawnCities() local number_of_cities = 7 cities = {} for i = 1, number_of_cities do local city_x = ((i - 0.5) / number_of_cities) * (1 - 2*wall_thickness) + wall_thickness local city_y = 1 - city_height/2 table.insert(cities, { x = city_x, y = city_y, alive = true }) end end function spawnMissile(x, y, dx, dy) table.insert(missiles, { x = x, y = y, dx = dx, dy = dy, reflected = false, history = { {x = x, y = y} }, alive = true }) end function updateMissiles(dt) for _, missile in ipairs(missiles) do missile.x = missile.x + missile.dx * dt missile.y = missile.y + missile.dy * dt local collided = false if missile.y < wall_thickness + missile_radius then -- Collision with top wall missile.y = wall_thickness + missile_radius missile.dy = -missile.dy collided = true end if missile.x < wall_thickness + missile_radius then -- Collision with left wall missile.x = wall_thickness + missile_radius missile.dx = -missile.dx collided = true elseif missile.x > 1 - (wall_thickness + missile_radius) then -- Collision with right wall missile.x = 1 - (wall_thickness + missile_radius) missile.dx = -missile.dx collided = true end local paddle_left = paddle_x - paddle_width/2 local paddle_right = paddle_x + paddle_width/2 local paddle_top = paddle_y - paddle_height/2 if paddle_left <= missile.x and missile.x <= paddle_right and paddle_top <= missile.y and missile.y <= paddle_y then -- Collision with the paddle missile.y = paddle_top missile.dy = -missile.dy missile.reflected = true collided = true end for _, city in ipairs(cities) do local city_left = city.x - city_width/2 local city_right = city.x + city_width/2 local city_top = city.y - city_height/2 local city_bottom = city.y + city_height/2 if city.alive and city_left <= missile.x and missile.x <= city_right and city_top <= missile.y and missile.y <= city_bottom then -- Destroy city city.alive = false -- Destroy missile missile.alive = false end end if collided then table.insert(missile.history, 1, { x = missile.x, y = missile.y }) -- Remove the oldest segments if #missile.history > missile_trail_length then table.remove(missile.history) end end end local i = 1 while i <= #missiles do if missiles[i].y > 1 + missile_radius or not missiles[i].alive then -- Went off the bottom of the screen or exploded, delete table.remove(missiles, i) else i = i +1 end end end function love.update(dt) updateMissiles(dt) end function movePaddle(screen_x) paddle_x = fromScreenCoordinate(screen_x, 0) paddle_x = math.max(paddle_x, paddle_width/2 + wall_thickness) paddle_x = math.min(paddle_x, 1 - (paddle_width/2 + wall_thickness)) paddle_y = 0.9 end love.mousemoved = movePaddle love.resize = setScreenDimensions function drawWalls() love.graphics.setColor(0, 0, 1) local x, y = toScreenCoordinates(0, 0) local width = toScreenSize(1) local height =toScreenSize(wall_thickness) love.graphics.rectangle('fill', x, y, width, height) local width = toScreenSize(wall_thickness) local height = toScreenSize(1) love.graphics.rectangle('fill', x, y, width, height) local x, y = toScreenCoordinates(1 - wall_thickness, 0) love.graphics.rectangle('fill', x, y, width, height) end function drawMissiles() love.graphics.setLineWidth(0.001 * scale) -- Trails for _, missile in ipairs(missiles) do local from_x, from_y = toScreenCoordinates(missile.x, missile.y) local visibility = 1 for _, point in ipairs(missile.history) do local x, y = toScreenCoordinates(point.x, point.y) if missile.reflected then love.graphics.setColor(1 * visibility, 1 * visibility, 0.5 * visibility) else love.graphics.setColor(1 * visibility, 0.4 * visibility, 0 * visibility) end visibility = visibility * 0.8 love.graphics.line(from_x, from_y, x, y) from_x = x from_y = y end end -- Missiles themselves. Drawn separately so that they're always over the trails for _, missile in ipairs(missiles) do local style = 'fill' if missile.reflected then love.graphics.setColor(1, 0.5, 0) style = 'line' else love.graphics.setColor(1, 0.5, 0.5) end local x, y = toScreenCoordinates(missile.x, missile.y) local radius = toScreenSize(missile_radius) love.graphics.circle(style, x, y, radius) end end function drawPaddle() love.graphics.setColor(1, 1, 1) local x, y = toScreenCoordinates(paddle_x - paddle_width / 2, paddle_y - paddle_height / 2) local width = toScreenSize(paddle_width) local height = toScreenSize(paddle_height) love.graphics.rectangle('fill', x, y, width, height) end function drawCities() love.graphics.setColor(1, 1, 1) for _, city in ipairs(cities) do if city.alive then local x, y = toScreenCoordinates(city.x - city_width/2, city.y - city_height/2) local width = toScreenSize(city_width) local height = toScreenSize(city_height) love.graphics.rectangle('fill', x, y, width, height) end end end function love.draw() drawCities() drawWalls() drawMissiles() drawPaddle() end