From 1afe79eac12e20a6a5783230f6ef30e5befeea52 Mon Sep 17 00:00:00 2001 From: heav Date: Tue, 4 Apr 2023 02:54:40 +0000 Subject: added 40 lines mode, and the concept of modes in general. --- evloop.lua | 20 ++++++++++++++++-- game/gfx.lua | 57 +++++++++++++++++++++++++++++++++++++++++++------- game/init.lua | 39 +++++++++++++++++++++++++--------- game/modes/40lines.lua | 30 ++++++++++++++++++++++++++ game/music.lua | 11 +++++++++- game/text_events.lua | 43 +++++++++++++++++++++++++++++++++++++ main.lua | 2 +- 7 files changed, 180 insertions(+), 22 deletions(-) create mode 100644 game/modes/40lines.lua create mode 100644 game/text_events.lua diff --git a/evloop.lua b/evloop.lua index 637d266..cebf510 100644 --- a/evloop.lua +++ b/evloop.lua @@ -7,6 +7,7 @@ local function new(...) local new = setmetatable({}, M) new.event_queue = {} new.tasks = {} + new.named_tasks = {} for i = 1, select("#", ...) do new:wrap(select(i, ...)) end @@ -20,7 +21,12 @@ M.new = new -- create a new task in the event loop. function M:wrap(fn, ...) - table.insert(self.event_queue, {coroutine.create(fn), ...}) + local name = false + if type(fn) == "table" then + name = fn.name + fn = fn[1] + end + table.insert(self.event_queue, {coroutine.create(fn), name, ...}) end -- send an event: evloop:queue(event, args...) @@ -29,6 +35,14 @@ function M:queue(...) table.insert(self.event_queue, {...}) end +-- destroy a named task +function M:kill(name) + if self.named_tasks[name] then + self.tasks[self.named_tasks[name]] = nil + self.named_tasks[name] = nil + end +end + local function event_filter(timeout, ...) local filter = {} for i = 1, select("#", ...) do @@ -123,8 +137,10 @@ function M:run() for _, e in ipairs(q) do if type(e[1]) == "thread" then -- start a new task - if resume_task(e[1], unpack(e, 2)) then + local name = e[2] + if resume_task(e[1], unpack(e, 3)) then self.tasks[e[1]] = true + if name then self.named_tasks[name] = e[1] end end elseif e[1] == "quit" then return unpack(e, 2) diff --git a/game/gfx.lua b/game/gfx.lua index 0f45fc4..c74bc64 100644 --- a/game/gfx.lua +++ b/game/gfx.lua @@ -3,6 +3,9 @@ local viewport = require "viewport" local tetrominoes = require "game/tetrominoes" local debug_gfx = require "game.debug_gfx" +local font = love.graphics.newFont(20) +love.graphics.setFont(font) + local M = {} M.__index = M @@ -10,6 +13,7 @@ function M.new(assets, game) local new = setmetatable({}, M) new.assets = assets new.game = game + new.text_sidebar = {{text = "this won't save you from the mind spiders.\n\n"}} return new end @@ -25,11 +29,11 @@ local colors = { function M:field_dimensions() local padding = 20 - local area_width = 1280 - padding * 2 + local area_width = 1920 - padding * 2 local area_height = 1080 - padding * 2 local c, l = self.game.field.columns, self.game.field.lines - local scalex, scaley = area_width / c, area_height / l + local scalex, scaley = (area_width-500) / c, area_height / l local block_size if scalex * l < area_height then block_size = scalex @@ -89,19 +93,27 @@ function M:draw_tetromino_confined(tetromino, x, y, sidelength, margin) self:draw_tetromino(tetromino, tetr_x, tetr_y, scale * block_size, true) end -function M:draw_hold() +local function get_hold_size(self) local block_size, field_x, field_y, field_w, field_h = self:field_dimensions() - local hold_x = field_x - block_size - block_size/2 - block_size*3/2 - local hold_y = field_y + local x = field_x - block_size - block_size/2 - block_size*3/2 + local y = field_y local margin = block_size/2 + local inner_size = block_size*3/2 + local size = inner_size + margin + return x, y, margin, inner_size, size +end +function M:draw_hold() + local block_size, field_x, field_y, field_w, field_h = self:field_dimensions() + local x, y, margin, size = get_hold_size(self) love.graphics.setColor(0, 0, 0) - love.graphics.rectangle("fill", hold_x, hold_y, block_size*1.5 + margin, block_size*1.5 + margin) + love.graphics.rectangle("fill", x, y, block_size*1.5 + margin, block_size*1.5 + margin) if not self.game.can_hold then love.graphics.setColor(0.5, 0.5, 0.5, 0.5) - love.graphics.rectangle("fill", hold_x, hold_y, block_size*1.5 + margin, block_size*1.5 + margin) + love.graphics.rectangle("fill", x, y, block_size*1.5 + margin, block_size*1.5 + margin) end if not self.game.hold then return end - self:draw_tetromino_confined(self.game.hold, hold_x, hold_y, block_size*3/2, margin) + hold_size = block_size*3/2 + margin -- to be fair this is better described as padding. + self:draw_tetromino_confined(self.game.hold, x, y, size, margin) end function M:draw_queue() @@ -153,6 +165,34 @@ function M:draw_field() end end end +function M:draw_game_text() + local font = love.graphics.getFont() + local line_height = font:getHeight() * 3/2 + local hold_x, hold_y, _, _, hold_size = get_hold_size(self) + local text_end_x = hold_x + hold_size + local text_y = hold_y + hold_size + line_height + + for i, obj in ipairs(self.text_sidebar) do + local y = text_y + (i-1) * line_height + local newlines = 0 + for j=1, #obj.text do + if obj.text:sub(j,j) == "\n" then newlines = newlines + 1 end + end + text_y = text_y + font:getHeight() * newlines + if obj.color then + love.graphics.setColor(unpack(obj.color)) + else + love.graphics.setColor(1, 1, 1) + end + local j = 0 + for text in obj.text:gfind("[^\n]+") do + local w = font:getWidth(text) + local x = text_end_x - w + love.graphics.print(text, x, y + font:getHeight() * j) + j = j + 1 + end + end +end function M:draw(dt) love.graphics.setColor(0.2, 0.2, 0.2) @@ -164,6 +204,7 @@ function M:draw(dt) if self.game.piece then self:draw_piece(true) -- shadow. end + self:draw_game_text() for i=1, #debug_gfx.stack do debug_gfx.stack[i](self) end diff --git a/game/init.lua b/game/init.lua index ab55867..e03aa76 100644 --- a/game/init.lua +++ b/game/init.lua @@ -39,13 +39,15 @@ function M.new(assets, params) new.t_spun = false new.combo = -1 - new.stats = {pieces = 0, lines = 0} + new.stats = {pieces = 0, lines = 0, time = 0, start_time = love.timer.getTime()} new.gravity_delay = 0.5 new.lock_delay = params.lock_delay or 0.8 new.infinity = params.infinity or false new.das_delay = params.das_delay or 0.16 new.das_repeat_delay = params.das_repeat_delay or 0.03 + + new.loop = evloop.new() return new end @@ -171,11 +173,11 @@ function M:place_piece() local sound = ({"tspinsingle","tspindouble","tspintriple"})[cleared] self.sfx:play(sound) end - self.loop:queue "game.line_clear" + self.loop:queue("game.line_clear") else self.combo = -1 end - self.loop:queue "game.piece_placed" + self.loop:queue("game.piece_placed") self:next_piece() return true else @@ -209,15 +211,32 @@ function M:gravity_loop() end end +function M:time_loop() + while true do + self.loop.poll("update") + self.stats.time = love.timer.getTime() - self.stats.start_time + end +end + +function M:win() + self.loop:kill("input_loop") + self.loop:kill("gravity_loop") + self.loop:kill("lock_loop") + self.loop:kill("das_loop") + self.loop:kill("time_loop") + self.gfx.text_sidebar[1].text = "you win.\n\n" + self.gfx.text_sidebar[1].color = {1, 1, 0} + self.music:fade(self.loop, 4) +end + function M:run() self:next_piece() - self.loop = evloop.new( - function() self:input_loop() end, - function() self:das_loop() end, - function() self:gravity_loop() end, - function() self:lock_loop() end, - function() self.gfx:run() end - ) + self.loop:wrap{function() self:input_loop() end, name="input_loop"} + self.loop:wrap{function() self:gravity_loop() end, name="gravity_loop"} + self.loop:wrap{function() self:lock_loop() end, name="lock_loop"} + self.loop:wrap{function() self:das_loop() end, name="das_loop"} + self.loop:wrap{function() self:time_loop() end, name="time_loop"} + self.loop:wrap{function() self.gfx:run() end, name="gfx"} return self.loop:run() end diff --git a/game/modes/40lines.lua b/game/modes/40lines.lua new file mode 100644 index 0000000..faf8cb5 --- /dev/null +++ b/game/modes/40lines.lua @@ -0,0 +1,30 @@ +local game = require "game" +local text_events = require "game.text_events" + +local M = {} +M.__index = M + +function M.new(assets) + local new = {} + setmetatable(new, M) + + new.game = game.new(assets, {}) + + table.insert(new.game.gfx.text_sidebar, {text="40 lines mode."}) + table.insert(new.game.gfx.text_sidebar, text_events.pieces(new.game)) + table.insert(new.game.gfx.text_sidebar, text_events.lines(new.game, 40)) + table.insert(new.game.gfx.text_sidebar, text_events.time(new.game)) + + new.game.loop:wrap(function() + while true do + new.game.loop.poll("game.line_clear") + if new.game.stats.lines >= 40 then + new.game:win() + end + end + end) + + return new.game, new +end + +return M \ No newline at end of file diff --git a/game/music.lua b/game/music.lua index d9f3e91..d9aa80d 100644 --- a/game/music.lua +++ b/game/music.lua @@ -1,6 +1,7 @@ local M = {} M.__index = M M.playing = nil +M.volume = 0.5 function M.new(assets) local new = setmetatable({}, M) @@ -15,9 +16,17 @@ function M:play(name) self.assets.music[name]:seek(0) M.playing = self.assets.music[name] end - self.assets.music[name]:setVolume(0.5) + self.assets.music[name]:setVolume(self.volume) self.assets.music[name]:play() self.assets.music[name]:setLooping(true) end +function M:fade(loop, time) + for i=1, math.ceil(time*20) do + loop.poll(1/20) + local volume = self.volume * (1-i/(time*20)) + self.playing:setVolume(volume) + end +end + return M diff --git a/game/text_events.lua b/game/text_events.lua new file mode 100644 index 0000000..ace00eb --- /dev/null +++ b/game/text_events.lua @@ -0,0 +1,43 @@ +local M = {} + +M.pieces = function(game) + local t = {text="pieces:\n0"} + game.loop:wrap(function() + while true do + local e = game.loop.poll("game.piece_placed") + t.text = "pieces:\n"..game.stats.pieces + end + end) + return t +end + +M.lines = function(game, goal) + local t = {text="lines:\n0"..(goal and "/"..goal or "")} + game.loop:wrap(function() + while true do + local e = game.loop.poll("game.line_clear") + t.text = "lines:\n"..game.stats.lines..(goal and "/"..goal or "") + end + end) + return t +end + +local function display_time(t) + local seconds = math.floor(t%60) + local minutes = math.floor(t/60) + local centiseconds = math.floor((t*100)%100) + return ("%02d:%02d:%02d"):format(minutes, seconds, centiseconds) + +end +M.time = function(game) + local t = {text="time:\n"..display_time(0)} + game.loop:wrap(function() + while true do + local e = game.loop.poll("update") + t.text = "time:\n"..display_time(game.stats.time) + end + end) + return t +end + +return M \ No newline at end of file diff --git a/main.lua b/main.lua index 224c3ab..5f47e6a 100644 --- a/main.lua +++ b/main.lua @@ -5,7 +5,7 @@ local function main() evloop.poll "load" local game_assets = assets.load_from "assets" evloop.poll "loaded" - local game_obj = game.new(game_assets, {}) + local game_obj = require("game.modes.40lines").new(game_assets) game_obj.music:play("the") game_obj:run() evloop:quit() -- cgit v1.2.3