diff --git a/.gitignore b/.gitignore index f9f5d82..1d4ff14 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ skins/donotupload skins/donotupload/* ss ss/* -*.sav \ No newline at end of file +*.sav +res/bgm/track* \ No newline at end of file diff --git a/res/bgm/ICEFRONT.S3M b/res/bgm/ICEFRONT.S3M new file mode 100644 index 0000000..d7ac6ef Binary files /dev/null and b/res/bgm/ICEFRONT.S3M differ diff --git a/res/bgm/OMNIPHIL.S3M b/res/bgm/OMNIPHIL.S3M new file mode 100644 index 0000000..2802fe9 Binary files /dev/null and b/res/bgm/OMNIPHIL.S3M differ diff --git a/res/bgm/data_jack_2nd_version.s3m b/res/bgm/data_jack_2nd_version.s3m new file mode 100644 index 0000000..5ca5856 Binary files /dev/null and b/res/bgm/data_jack_2nd_version.s3m differ diff --git a/tetris/components/lifegrid.lua b/tetris/components/lifegrid.lua new file mode 100644 index 0000000..a917bdc --- /dev/null +++ b/tetris/components/lifegrid.lua @@ -0,0 +1,574 @@ +local Object = require 'libs.classic' + +local LifeGrid = Object:extend() + +local empty = { skin = "", colour = "" } +local oob = { skin = "", colour = "" } +local block = { skin = "2tie", colour = "A" } + +function LifeGrid:new(width, height) + self.grid = {} + self.grid_age = {} + self.width = width + self.height = height + for y = 1, self.height do + self.grid[y] = {} + self.grid_age[y] = {} + for x = 1, self.width do + self.grid[y][x] = empty + self.grid_age[y][x] = 0 + end + end +end + +function LifeGrid:clear() + for y = 1, self.height do + for x = 1, self.width do + self.grid[y][x] = empty + self.grid_age[y][x] = 0 + end + end +end + +function LifeGrid:getCell(x, y) + if x < 1 or x > self.width or y > self.height then return oob + elseif y < 1 then return empty + else return self.grid[y][x] + end +end + +function LifeGrid:isOccupied(x, y) + return self:getCell(x+1, y+1) ~= empty +end + +function LifeGrid:isRowFull(row) + for index, square in pairs(self.grid[row]) do + if square == empty then return false end + end + return true +end + +function LifeGrid:canPlacePiece(piece) + if piece.big then + return self:canPlaceBigPiece(piece) + end + + local offsets = piece:getBlockOffsets() + for index, offset in pairs(offsets) do + local x = piece.position.x + offset.x + local y = piece.position.y + offset.y + if self:isOccupied(x, y) then + return false + end + end + return true +end + +function LifeGrid:canPlaceBigPiece(piece) + local offsets = piece:getBlockOffsets() + for index, offset in pairs(offsets) do + local x = piece.position.x + offset.x + local y = piece.position.y + offset.y + if ( + self:isOccupied(x * 2 + 0, y * 2 + 0) + or self:isOccupied(x * 2 + 1, y * 2 + 0) + or self:isOccupied(x * 2 + 0, y * 2 + 1) + or self:isOccupied(x * 2 + 1, y * 2 + 1) + ) then + return false + end + end + return true +end + +function LifeGrid:canPlacePieceInVisibleLifeGrid(piece) + if piece.big then + return self:canPlaceBigPiece(piece) + -- forget canPlaceBigPieceInVisibleLifeGrid for now + end + + local offsets = piece:getBlockOffsets() + for index, offset in pairs(offsets) do + local x = piece.position.x + offset.x + local y = piece.position.y + offset.y + if y < 4 or self:isOccupied(x, y) ~= empty then + return false + end + end + return true +end + +function LifeGrid:getClearedRowCount() + local count = 0 + local cleared_row_table = {} + for row = 1, self.height do + if self:isRowFull(row) then + count = count + 1 + table.insert(cleared_row_table, row) + end + end + return count, cleared_row_table +end + +function LifeGrid:markClearedRows() + local block_table = {} + for row = 1, self.height do + if self:isRowFull(row) then + block_table[row] = {} + for x = 1, self.width do + block_table[row][x] = { + skin = self.grid[row][x].skin, + colour = self.grid[row][x].colour, + } + self.grid[row][x] = { + skin = self.grid[row][x].skin, + colour = "X" + } + --self.grid_age[row][x] = 0 + end + end + end + return block_table +end + +function LifeGrid:clearClearedRows() + for row = 1, self.height do + if self:isRowFull(row) then + for above_row = row, 2, -1 do + self.grid[above_row] = self.grid[above_row - 1] + self.grid_age[above_row] = self.grid_age[above_row - 1] + end + self.grid[1] = {} + self.grid_age[1] = {} + for i = 1, self.width do + self.grid[1][i] = empty + self.grid_age[1][i] = 0 + end + end + end + return true +end + +function LifeGrid:copyBottomRow() + for row = 1, self.height - 1 do + self.grid[row] = self.grid[row+1] + self.grid_age[row] = self.grid_age[row+1] + end + self.grid[self.height] = {} + self.grid_age[self.height] = {} + for i = 1, self.width do + self.grid[self.height][i] = (self.grid[self.height - 1][i] == empty) and empty or block + self.grid_age[self.height][i] = 0 + end + return true +end + +function LifeGrid:garbageRise(row_vals) + for row = 1, self.height - 1 do + self.grid[row] = self.grid[row+1] + self.grid_age[row] = self.grid_age[row+1] + end + self.grid[self.height] = {} + self.grid_age[self.height] = {} + for i = 1, self.width do + self.grid[self.height][i] = (row_vals[i] == "e") and empty or block + self.grid_age[self.height][i] = 0 + end +end + +function LifeGrid:clearSpecificRow(row) + for col = 1, self.width do + self.grid[row][col] = empty + end +end + +function LifeGrid:applyPiece(piece) + if piece.big then + self:applyBigPiece(piece) + return + end + offsets = piece:getBlockOffsets() + for index, offset in pairs(offsets) do + x = piece.position.x + offset.x + y = piece.position.y + offset.y + if y + 1 > 0 and y < self.height then + self.grid[y+1][x+1] = { + skin = piece.skin, + colour = piece.colour + } + end + end +end + +function LifeGrid:applyBigPiece(piece) + offsets = piece:getBlockOffsets() + for index, offset in pairs(offsets) do + x = piece.position.x + offset.x + y = piece.position.y + offset.y + for a = 1, 2 do + for b = 1, 2 do + if y*2+a > 0 and y*2 < self.height then + self.grid[y*2+a][x*2+b] = { + skin = piece.skin, + colour = piece.colour + } + end + end + end + end +end + +function LifeGrid:checkForBravo(cleared_row_count) + for i = 0, self.height - 1 - cleared_row_count do + for j = 0, self.width - 1 do + if self:isOccupied(j, i) then return false end + end + end + return true +end + +function LifeGrid:checkStackHeight() + for i = 0, self.height - 1 do + for j = 0, self.width - 1 do + if self:isOccupied(j, i) then return self.height - i end + end + end + return 0 +end + +function LifeGrid:checkSecretGrade() + local sgrade = 0 + for i=23,5,-1 do + local validLine = true + local emptyCell = 0 + if i > 13 then + emptyCell = 23-i + end + if i <= 13 then + emptyCell = i-5 + end + for j=0,9 do + if (not self:isOccupied(j,i) and j ~= emptyCell) or (j == emptyCell and self:isOccupied(j,i)) then + validLine = false + end + end + if not self:isOccupied(emptyCell,i-1) then + validLine = false + end + if(validLine) then + sgrade = sgrade + 1 + else + return sgrade + end + end + --[[ + if(sgrade == 0) then return "" + elseif(sgrade < 10) then return 10-sgrade + elseif(sgrade < 19) then return "S"..(sgrade-9) end + return "GM" + --]] + return sgrade +end + +function LifeGrid:hasGemBlocks() + for y = 1, self.height do + for x = 1, self.width do + if self.grid[y][x].skin == "gem" then + return true + end + end + end + return false +end + +function LifeGrid:mirror() + local new_grid = {} + for y = 1, self.height do + new_grid[y] = {} + for x = 1, self.width do + new_grid[y][x] = empty + end + end + + for y = 1, self.height do + for x = 1, self.width do + new_grid[y][x] = self.grid[y][self.width + 1 - x] + end + end + self.grid = new_grid +end + +function LifeGrid:applyMap(map) + for y, row in pairs(map) do + for x, block in pairs(row) do + self.grid_age[y][x] = 0 + self.grid[y][x] = block + end + end +end + +-- inefficient algorithm for squares +function LifeGrid:markSquares() + -- goes up by 1 for silver, 2 for gold + local square_count = 0 + for i = 1, 2 do + for y = 5, self.height - 3 do + for x = 1, self.width - 3 do + local age_table = {} + local age_count = 0 + local colour_table = {} + local is_square = true + for j = 0, 3 do + for k = 0, 3 do + if self.grid[y+j][x+k].skin == "" or self.grid[y+j][x+k].skin == "square" then + is_square = false + end + if age_table[self.grid_age[y+j][x+k]] == nil then + age_table[self.grid_age[y+j][x+k]] = 1 + age_count = age_count + 1 + else + age_table[self.grid_age[y+j][x+k]] = age_table[self.grid_age[y+j][x+k]] + 1 + end + if age_count > 4 or age_table[self.grid_age[y+j][x+k]] > 4 then + is_square = false + end + if not table.contains(colour_table, self.grid[y+j][x+k].colour) then + table.insert(colour_table, self.grid[y+j][x+k].colour) + end + end + end + if is_square then + if i == 1 and #colour_table == 1 then + for j = 0, 3 do + for k = 0, 3 do + self.grid[y+j][x+k].colour = "Y" + self.grid[y+j][x+k].skin = "square" + end + end + square_count = square_count + 2 + elseif i == 2 then + for j = 0, 3 do + for k = 0, 3 do + self.grid[y+j][x+k].colour = "F" + self.grid[y+j][x+k].skin = "square" + end + + end + square_count = square_count + 1 + end + end + end + end + end + return square_count +end + +-- square scan +function LifeGrid:scanForSquares() + local table = {} + for row = 1, self.height do + local silver = 0 + local gold = 0 + for col = 1, self.width do + local colour = self.grid[row][col].colour + if self.grid[row][col].skin == "square" then + if colour == "Y" then gold = gold + 1 + else silver = silver + 1 end + end + end + table[row] = gold * 2.5 + silver * 1.25 + end + return table +end + +function LifeGrid:update() + for y = 1, self.height do + for x = 1, self.width do + if self.grid[y][x] ~= empty then + self.grid_age[y][x] = self.grid_age[y][x] + 1 + end + end + end +end + +function LifeGrid:draw() + for y = 5, self.height do + for x = 1, self.width do + if blocks[self.grid[y][x].skin] and + blocks[self.grid[y][x].skin][self.grid[y][x].colour] then + if self.grid_age[y][x] < 2 then + love.graphics.setColor(1, 1, 1, 1) + love.graphics.draw(blocks[self.grid[y][x].skin]["F"], 48+x*16, y*16) + else + if self.grid[y][x].colour == "X" then + love.graphics.setColor(0, 0, 0, 0) + elseif self.grid[y][x].skin == "bone" then + love.graphics.setColor(1, 1, 1, 1) + else + love.graphics.setColor(0.5, 0.5, 0.5, 1) + end + love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16) + end + if self.grid[y][x].skin ~= "bone" and self.grid[y][x].colour ~= "X" then + love.graphics.setColor(0.8, 0.8, 0.8, 1) + love.graphics.setLineWidth(1) + if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then + love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16) + end + if y < self.height and self.grid[y+1][x] == empty or + (y + 1 <= self.height and self.grid[y+1][x].colour == "X") then + love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16) + end + if x > 1 and self.grid[y][x-1] == empty then + love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16) + end + if x < self.width and self.grid[y][x+1] == empty then + love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16) + end + end + end + end + end +end + +function LifeGrid:drawOutline() + for y = 5, self.height do + for x = 1, self.width do + if self.grid[y][x] ~= empty and self.grid[y][x].colour ~= "X" then + love.graphics.setColor(0.8, 0.8, 0.8, 1) + love.graphics.setLineWidth(1) + if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then + love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16) + end + if y < self.height and self.grid[y+1][x] == empty or + (y + 1 <= self.height and self.grid[y+1][x].colour == "X") then + love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16) + end + if x > 1 and self.grid[y][x-1] == empty then + love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16) + end + if x < self.width and self.grid[y][x+1] == empty then + love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16) + end + end + end + end +end + +function LifeGrid:drawInvisible(opacity_function, garbage_opacity_function, lock_flash, brightness) + lock_flash = lock_flash == nil and true or lock_flash + brightness = brightness == nil and 0.5 or brightness + for y = 5, self.height do + for x = 1, self.width do + if self.grid[y][x] ~= empty then + if self.grid[y][x].colour == "X" then + opacity = 0 + elseif garbage_opacity_function and self.grid[y][x].colour == "A" then + opacity = garbage_opacity_function(self.grid_age[y][x]) + else + opacity = opacity_function(self.grid_age[y][x]) + end + love.graphics.setColor(brightness, brightness, brightness, opacity) + love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16) + if lock_flash then + if opacity > 0 and self.grid[y][x].colour ~= "X" then + love.graphics.setColor(0.64, 0.64, 0.64) + love.graphics.setLineWidth(1) + if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then + love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16) + end + if y < self.height and self.grid[y+1][x] == empty or + (y + 1 <= self.height and self.grid[y+1][x].colour == "X") then + love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16) + end + if x > 1 and self.grid[y][x-1] == empty then + love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16) + end + if x < self.width and self.grid[y][x+1] == empty then + love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16) + end + end + end + end + end + end +end + +function LifeGrid:drawCustom(colour_function, gamestate) + --[[ + colour_function: (game, block, x, y, age) -> (R, G, B, A, outlineA) + When called, calls the supplied function on every block passing the block itself as argument + as well as coordinates and the grid_age value of the same cell. + Should return a RGBA colour for the block, as well as the opacity of the stack outline (0 for no outline). + + gamestate: the gamemode instance itself to pass in colour_function + ]] + for y = 5, self.height do + for x = 1, self.width do + local block = self.grid[y][x] + if block ~= empty then + local R, G, B, A, outline = colour_function(gamestate, block, x, y, self.grid_age[y][x]) + if self.grid[y][x].colour == "X" then + A = 0 + end + love.graphics.setColor(R, G, B, A) + love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16) + if outline > 0 and self.grid[y][x].colour ~= "X" then + love.graphics.setColor(0.64, 0.64, 0.64, outline) + love.graphics.setLineWidth(1) + if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then + love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16) + end + if y < self.height and self.grid[y+1][x] == empty or + (y + 1 <= self.height and self.grid[y+1][x].colour == "X") then + love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16) + end + if x > 1 and self.grid[y][x-1] == empty then + love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16) + end + if x < self.width and self.grid[y][x+1] == empty then + love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16) + end + end + end + end + end +end + +function LifeGrid:advanceLife() + local newgrid = {} + local newgrid_age = {} + for y = 1, self.height do + newgrid[y] = {} + newgrid_age[y] = {} + for x = 1, self.width do + newgrid[y][x] = empty + newgrid_age[y][x] = 0 + end + end + for y = 1, self.height do + for x = 1, self.width do + count = 0 + for dy = -1, 1 do + for dx = -1, 1 do + if (dy~=0 or dx~=0) and self.grid[dy+y] and self.grid[dy+y][dx+x] and self.grid[dy+y][dx+x] ~= empty then + count = count + 1 + end + end + end + if (count > 1) and (count < 4) then + newgrid[y][x] = self.grid[y][x] + if (count == 3) and (newgrid[y][x] == empty) then + newgrid[y][x] = { + skin = "2tie", + colour = ({"R", "O", "Y", "G", "B", "C", "M"})[math.random(7)] + } + end + end + end + end + self.grid=newgrid + self.grid_age=newgrid_age +end + +return LifeGrid diff --git a/tetris/components/strategygrid.lua b/tetris/components/strategygrid.lua new file mode 100644 index 0000000..6c9dadd --- /dev/null +++ b/tetris/components/strategygrid.lua @@ -0,0 +1,562 @@ +local Object = require 'libs.classic' + +local StrategyGrid = Object:extend() + +local empty = { skin = "", colour = "" } +local oob = { skin = "", colour = "" } +local block = { skin = "2tie", colour = "A" } + +function StrategyGrid:new(width, height) + self.grid = {} + self.grid_age = {} + self.grid_placements={} + self.width = width + self.height = height + for y = 1, self.height do + self.grid[y] = {} + self.grid_age[y] = {} + self.grid_placements[y] = {} + for x = 1, self.width do + self.grid[y][x] = empty + self.grid_age[y][x] = 0 + self.grid_placements[y][x] = 0 + end + end +end + +function StrategyGrid:clear() + for y = 1, self.height do + for x = 1, self.width do + self.grid[y][x] = empty + self.grid_age[y][x] = 0 + self.grid_placements[y][x] = 0 + end + end +end + +function StrategyGrid:getCell(x, y) + if x < 1 or x > self.width or y > self.height then return oob + elseif y < 1 then return empty + else return self.grid[y][x] + end +end + +function StrategyGrid:isOccupied(x, y) + return self:getCell(x+1, y+1) ~= empty +end + +function StrategyGrid:isRowFull(row) + for index, square in pairs(self.grid[row]) do + if square == empty then return false end + end + return true +end + +function StrategyGrid:canPlacePiece(piece) + if piece.big then + return self:canPlaceBigPiece(piece) + end + + local offsets = piece:getBlockOffsets() + for index, offset in pairs(offsets) do + local x = piece.position.x + offset.x + local y = piece.position.y + offset.y + if self:isOccupied(x, y) then + return false + end + end + return true +end + +function StrategyGrid:canPlaceBigPiece(piece) + local offsets = piece:getBlockOffsets() + for index, offset in pairs(offsets) do + local x = piece.position.x + offset.x + local y = piece.position.y + offset.y + if ( + self:isOccupied(x * 2 + 0, y * 2 + 0) + or self:isOccupied(x * 2 + 1, y * 2 + 0) + or self:isOccupied(x * 2 + 0, y * 2 + 1) + or self:isOccupied(x * 2 + 1, y * 2 + 1) + ) then + return false + end + end + return true +end + +function StrategyGrid:canPlacePieceInVisibleStrategyGrid(piece) + if piece.big then + return self:canPlaceBigPiece(piece) + -- forget canPlaceBigPieceInVisibleStrategyGrid for now + end + + local offsets = piece:getBlockOffsets() + for index, offset in pairs(offsets) do + local x = piece.position.x + offset.x + local y = piece.position.y + offset.y + if y < 4 or self:isOccupied(x, y) ~= empty then + return false + end + end + return true +end + +function StrategyGrid:getClearedRowCount() + local count = 0 + local cleared_row_table = {} + for row = 1, self.height do + if self:isRowFull(row) then + count = count + 1 + table.insert(cleared_row_table, row) + end + end + return count, cleared_row_table +end + +function StrategyGrid:markClearedRows() + local block_table = {} + for row = 1, self.height do + if self:isRowFull(row) then + block_table[row] = {} + for x = 1, self.width do + block_table[row][x] = { + skin = self.grid[row][x].skin, + colour = self.grid[row][x].colour, + } + self.grid[row][x] = { + skin = self.grid[row][x].skin, + colour = "X" + } + --self.grid_age[row][x] = 0 + --self.grid_placements[row][x] = 0 + end + end + end + return block_table +end + +function StrategyGrid:clearClearedRows() + for row = 1, self.height do + if self:isRowFull(row) then + for above_row = row, 2, -1 do + self.grid[above_row] = self.grid[above_row - 1] + self.grid_age[above_row] = self.grid_age[above_row - 1] + self.grid_placements[above_row] = self.grid_placements[above_row - 1] + end + self.grid[1] = {} + self.grid_age[1]={} + self.grid_placements[1] = {} + for i = 1, self.width do + self.grid[1][i] = empty + self.grid_age[1][i] = 0 + self.grid_placements[1][i] = 0 + end + end + end + return true +end + +function StrategyGrid:copyBottomRow() + for row = 1, self.height - 1 do + self.grid[row] = self.grid[row+1] + self.grid_age[row] = self.grid_age[row+1] + self.grid_placements[row] = self.grid_placements[row+1] + end + self.grid[self.height] = {} + self.grid_age[self.height] = {} + self.grid_placements[self.height] = {} + for i = 1, self.width do + self.grid[self.height][i] = (self.grid[self.height - 1][i] == empty) and empty or block + self.grid_age[self.height][i] = 0 + self.grid_placements[self.height][i] = 0 + end + return true +end + +function StrategyGrid:garbageRise(row_vals) + for row = 1, self.height - 1 do + self.grid[row] = self.grid[row+1] + self.grid_age[row] = self.grid_age[row+1] + self.grid_placements[row] = self.grid_placements[row+1] + end + self.grid[self.height] = {} + self.grid_age[self.height] = {} + self.grid_placements[self.height] = {} + for i = 1, self.width do + self.grid[self.height][i] = (row_vals[i] == "e") and empty or block + self.grid_age[self.height][i] = 0 + self.grid_placements[self.height][i] = 0 + end +end + +function StrategyGrid:clearSpecificRow(row) + for col = 1, self.width do + self.grid[row][col] = empty + end +end + +function StrategyGrid:applyPiece(piece) + if piece.big then + self:applyBigPiece(piece) + return + end + offsets = piece:getBlockOffsets() + for index, offset in pairs(offsets) do + x = piece.position.x + offset.x + y = piece.position.y + offset.y + if y + 1 > 0 and y < self.height then + self.grid[y+1][x+1] = { + skin = piece.skin, + colour = piece.colour + } + end + end +end + +function StrategyGrid:applyBigPiece(piece) + offsets = piece:getBlockOffsets() + for index, offset in pairs(offsets) do + x = piece.position.x + offset.x + y = piece.position.y + offset.y + for a = 1, 2 do + for b = 1, 2 do + if y*2+a > 0 and y*2 < self.height then + self.grid[y*2+a][x*2+b] = { + skin = piece.skin, + colour = piece.colour + } + end + end + end + end +end + +function StrategyGrid:checkForBravo(cleared_row_count) + for i = 0, self.height - 1 - cleared_row_count do + for j = 0, self.width - 1 do + if self:isOccupied(j, i) then return false end + end + end + return true +end + +function StrategyGrid:checkStackHeight() + for i = 0, self.height - 1 do + for j = 0, self.width - 1 do + if self:isOccupied(j, i) then return self.height - i end + end + end + return 0 +end + +function StrategyGrid:checkSecretGrade() + local sgrade = 0 + for i=23,5,-1 do + local validLine = true + local emptyCell = 0 + if i > 13 then + emptyCell = 23-i + end + if i <= 13 then + emptyCell = i-5 + end + for j=0,9 do + if (not self:isOccupied(j,i) and j ~= emptyCell) or (j == emptyCell and self:isOccupied(j,i)) then + validLine = false + end + end + if not self:isOccupied(emptyCell,i-1) then + validLine = false + end + if(validLine) then + sgrade = sgrade + 1 + else + return sgrade + end + end + --[[ + if(sgrade == 0) then return "" + elseif(sgrade < 10) then return 10-sgrade + elseif(sgrade < 19) then return "S"..(sgrade-9) end + return "GM" + --]] + return sgrade +end + +function StrategyGrid:hasGemBlocks() + for y = 1, self.height do + for x = 1, self.width do + if self.grid[y][x].skin == "gem" then + return true + end + end + end + return false +end + +function StrategyGrid:mirror() + local new_grid = {} + for y = 1, self.height do + new_grid[y] = {} + for x = 1, self.width do + new_grid[y][x] = empty + end + end + + for y = 1, self.height do + for x = 1, self.width do + new_grid[y][x] = self.grid[y][self.width + 1 - x] + end + end + self.grid = new_grid +end + +function StrategyGrid:applyMap(map) + for y, row in pairs(map) do + for x, block in pairs(row) do + self.grid_age[y][x] = 0 + self.grid[y][x] = block + end + end +end + +-- inefficient algorithm for squares +function StrategyGrid:markSquares() + -- goes up by 1 for silver, 2 for gold + local square_count = 0 + for i = 1, 2 do + for y = 5, self.height - 3 do + for x = 1, self.width - 3 do + local age_table = {} + local age_count = 0 + local colour_table = {} + local is_square = true + for j = 0, 3 do + for k = 0, 3 do + if self.grid[y+j][x+k].skin == "" or self.grid[y+j][x+k].skin == "square" then + is_square = false + end + if age_table[self.grid_age[y+j][x+k]] == nil then + age_table[self.grid_age[y+j][x+k]] = 1 + age_count = age_count + 1 + else + age_table[self.grid_age[y+j][x+k]] = age_table[self.grid_age[y+j][x+k]] + 1 + end + if age_count > 4 or age_table[self.grid_age[y+j][x+k]] > 4 then + is_square = false + end + if not table.contains(colour_table, self.grid[y+j][x+k].colour) then + table.insert(colour_table, self.grid[y+j][x+k].colour) + end + end + end + if is_square then + if i == 1 and #colour_table == 1 then + for j = 0, 3 do + for k = 0, 3 do + self.grid[y+j][x+k].colour = "Y" + self.grid[y+j][x+k].skin = "square" + end + end + square_count = square_count + 2 + elseif i == 2 then + for j = 0, 3 do + for k = 0, 3 do + self.grid[y+j][x+k].colour = "F" + self.grid[y+j][x+k].skin = "square" + end + + end + square_count = square_count + 1 + end + end + end + end + end + return square_count +end + +-- square scan +function StrategyGrid:scanForSquares() + local table = {} + for row = 1, self.height do + local silver = 0 + local gold = 0 + for col = 1, self.width do + local colour = self.grid[row][col].colour + if self.grid[row][col].skin == "square" then + if colour == "Y" then gold = gold + 1 + else silver = silver + 1 end + end + end + table[row] = gold * 2.5 + silver * 1.25 + end + return table +end + +function StrategyGrid:update() + for y = 1, self.height do + for x = 1, self.width do + if self.grid[y][x] ~= empty then + self.grid_age[y][x] = self.grid_age[y][x] + 1 + end + end + end +end + +function StrategyGrid:placement() + for y = 1, self.height do + for x = 1, self.width do + if self.grid[y][x] ~= empty then + self.grid_placements[y][x] = self.grid_placements[y][x] + 1 + end + end + end +end + +function StrategyGrid:draw() + for y = 5, self.height do + for x = 1, self.width do + if blocks[self.grid[y][x].skin] and + blocks[self.grid[y][x].skin][self.grid[y][x].colour] then + if self.grid_age[y][x] < 2 then + love.graphics.setColor(1, 1, 1, 1) + love.graphics.draw(blocks[self.grid[y][x].skin]["F"], 48+x*16, y*16) + else + if self.grid[y][x].colour == "X" then + love.graphics.setColor(0, 0, 0, 0) + elseif self.grid[y][x].skin == "bone" then + love.graphics.setColor(1, 1, 1, 1) + else + love.graphics.setColor(0.5, 0.5, 0.5, 1) + end + love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16) + end + if self.grid[y][x].skin ~= "bone" and self.grid[y][x].colour ~= "X" then + love.graphics.setColor(0.8, 0.8, 0.8, 1) + love.graphics.setLineWidth(1) + if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then + love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16) + end + if y < self.height and self.grid[y+1][x] == empty or + (y + 1 <= self.height and self.grid[y+1][x].colour == "X") then + love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16) + end + if x > 1 and self.grid[y][x-1] == empty then + love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16) + end + if x < self.width and self.grid[y][x+1] == empty then + love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16) + end + end + end + end + end +end + +function StrategyGrid:drawOutline() + for y = 5, self.height do + for x = 1, self.width do + if self.grid[y][x] ~= empty and self.grid[y][x].colour ~= "X" then + love.graphics.setColor(0.8, 0.8, 0.8, 1) + love.graphics.setLineWidth(1) + if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then + love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16) + end + if y < self.height and self.grid[y+1][x] == empty or + (y + 1 <= self.height and self.grid[y+1][x].colour == "X") then + love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16) + end + if x > 1 and self.grid[y][x-1] == empty then + love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16) + end + if x < self.width and self.grid[y][x+1] == empty then + love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16) + end + end + end + end +end + +function StrategyGrid:drawInvisible(opacity_function, garbage_opacity_function, lock_flash, brightness) + lock_flash = lock_flash == nil and true or lock_flash + brightness = brightness == nil and 0.5 or brightness + for y = 5, self.height do + for x = 1, self.width do + if self.grid[y][x] ~= empty then + if self.grid[y][x].colour == "X" then + opacity = 0 + elseif garbage_opacity_function and self.grid[y][x].colour == "A" then + opacity = garbage_opacity_function(self.grid_age[y][x], self.grid_placements[y][x]) + else + opacity = opacity_function(self.grid_age[y][x], self.grid_placements[y][x]) + end + love.graphics.setColor(brightness, brightness, brightness, opacity) + love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16) + if lock_flash then + if opacity > 0 and self.grid[y][x].colour ~= "X" then + love.graphics.setColor(0.64, 0.64, 0.64) + love.graphics.setLineWidth(1) + if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then + love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16) + end + if y < self.height and self.grid[y+1][x] == empty or + (y + 1 <= self.height and self.grid[y+1][x].colour == "X") then + love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16) + end + if x > 1 and self.grid[y][x-1] == empty then + love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16) + end + if x < self.width and self.grid[y][x+1] == empty then + love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16) + end + end + end + end + end + end +end + +function StrategyGrid:drawCustom(colour_function, gamestate) + --[[ + colour_function: (game, block, x, y, age) -> (R, G, B, A, outlineA) + When called, calls the supplied function on every block passing the block itself as argument + as well as coordinates and the grid_age value of the same cell. + Should return a RGBA colour for the block, as well as the opacity of the stack outline (0 for no outline). + + gamestate: the gamemode instance itself to pass in colour_function + ]] + for y = 5, self.height do + for x = 1, self.width do + local block = self.grid[y][x] + if block ~= empty then + local R, G, B, A, outline = colour_function(gamestate, block, x, y, self.grid_age[y][x]) + if self.grid[y][x].colour == "X" then + A = 0 + end + love.graphics.setColor(R, G, B, A) + love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16) + if outline > 0 and self.grid[y][x].colour ~= "X" then + love.graphics.setColor(0.64, 0.64, 0.64, outline) + love.graphics.setLineWidth(1) + if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then + love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16) + end + if y < self.height and self.grid[y+1][x] == empty or + (y + 1 <= self.height and self.grid[y+1][x].colour == "X") then + love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16) + end + if x > 1 and self.grid[y][x-1] == empty then + love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16) + end + if x < self.width and self.grid[y][x+1] == empty then + love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16) + end + end + end + end + end +end + +return StrategyGrid diff --git a/tetris/modes/a3plus.lua b/tetris/modes/a3plus.lua new file mode 100644 index 0000000..102917a --- /dev/null +++ b/tetris/modes/a3plus.lua @@ -0,0 +1,15 @@ +local SurvivalA3Game = require 'tetris.modes.survival_a3' + +local A3Plus = SurvivalA3Game:extend() + +A3Plus.name = "Survival A3+" +A3Plus.hash = "A3Plus" +A3Plus.tagline = "A hardcore version of Survival A3." + +function A3Plus:new() + A3Plus.super:new() + self.next_queue_length = 1 + self.enable_hold = false +end + +return A3Plus \ No newline at end of file diff --git a/tetris/modes/conway_a1.lua b/tetris/modes/conway_a1.lua new file mode 100644 index 0000000..a3103a5 --- /dev/null +++ b/tetris/modes/conway_a1.lua @@ -0,0 +1,257 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Piece = require 'tetris.components.piece' +local LifeGrid = require 'tetris.components.lifegrid' + +local History4RollsRandomizer = require 'tetris.randomizers.history_4rolls' + +local MarathonA1Game = GameMode:extend() + +MarathonA1Game.name = "Conway A1" +MarathonA1Game.hash = "ConwayA1" +MarathonA1Game.tagline = "Something isn't right here." + + + +function MarathonA1Game:new() + MarathonA1Game.super:new() + self.grid=LifeGrid(10, 24) + + self.life_frames = 0 + self.roll_frames = 0 + self.combo = 1 + self.bravos = 0 + self.gm_conditions = { + level300 = false, + level500 = false, + level999 = false + } + self.SGnames = { + "9", "8", "7", "6", "5", "4", "3", "2", "1", + "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9", + "GM" + } + + self.randomizer = History4RollsRandomizer() + + self.additive_gravity = false + self.lock_drop = false + self.enable_hard_drop = false + self.enable_hold = false + self.next_queue_length = 1 +end + +function MarathonA1Game:getARE() + return 30 +end + +function MarathonA1Game:getLineARE() + return 27 +end + +function MarathonA1Game:getDasLimit() + return 15 +end + +function MarathonA1Game:getLineClearDelay() + return 44 +end + +function MarathonA1Game:getLockDelay() + return 30 +end + +local function getRankForScore(score) + if score < 400 then return {rank = "9", next = 400} + elseif score < 800 then return {rank = "8", next = 800} + elseif score < 1400 then return {rank = "7", next = 1400} + elseif score < 2000 then return {rank = "6", next = 2000} + elseif score < 3500 then return {rank = "5", next = 3500} + elseif score < 5500 then return {rank = "4", next = 5500} + elseif score < 8000 then return {rank = "3", next = 8000} + elseif score < 12000 then return {rank = "2", next = 12000} + elseif score < 16000 then return {rank = "1", next = 16000} + elseif score < 22000 then return {rank = "S1", next = 22000} + elseif score < 30000 then return {rank = "S2", next = 30000} + elseif score < 40000 then return {rank = "S3", next = 40000} + elseif score < 52000 then return {rank = "S4", next = 52000} + elseif score < 66000 then return {rank = "S5", next = 66000} + elseif score < 82000 then return {rank = "S6", next = 82000} + elseif score < 100000 then return {rank = "S7", next = 100000} + elseif score < 120000 then return {rank = "S8", next = 120000} + else return {rank = "S9", next = "???"} + end +end + +function MarathonA1Game:getGravity() + local level = self.level + if (level < 30) then return 4/256 + elseif (level < 35) then return 6/256 + elseif (level < 40) then return 8/256 + elseif (level < 50) then return 10/256 + elseif (level < 60) then return 12/256 + elseif (level < 70) then return 16/256 + elseif (level < 80) then return 32/256 + elseif (level < 90) then return 48/256 + elseif (level < 100) then return 64/256 + elseif (level < 120) then return 80/256 + elseif (level < 140) then return 96/256 + elseif (level < 160) then return 112/256 + elseif (level < 170) then return 128/256 + elseif (level < 200) then return 144/256 + elseif (level < 220) then return 4/256 + elseif (level < 230) then return 32/256 + elseif (level < 233) then return 64/256 + elseif (level < 236) then return 96/256 + elseif (level < 239) then return 128/256 + elseif (level < 243) then return 160/256 + elseif (level < 247) then return 192/256 + elseif (level < 251) then return 224/256 + elseif (level < 300) then return 1 + elseif (level < 330) then return 2 + elseif (level < 360) then return 3 + elseif (level < 400) then return 4 + elseif (level < 420) then return 5 + elseif (level < 450) then return 4 + elseif (level < 500) then return 3 + else return 20 + end +end + +function MarathonA1Game:advanceOneFrame() + self.life_frames = self.life_frames + 1 + if self.life_frames >= (620 - math.max(20, math.min(self.level, 500))) then + self.life_frames=0 + self.grid:advanceLife() + end + if self.clear then + self.roll_frames = self.roll_frames + 1 + if self.roll_frames > 2968 then + self.completed = true + end + elseif self.ready_frames == 0 then + self.frames = self.frames + 1 + end + return true +end + +function MarathonA1Game:onPieceEnter() + if (self.level % 100 ~= 99 and self.level ~= 998) and not self.clear and self.frames ~= 0 then + self.level = self.level + 1 + end +end + +function MarathonA1Game:onLineClear(cleared_row_count) + self:checkGMRequirements(self.level, self.level + cleared_row_count) + if not self.clear then + local new_level = math.min(self.level + cleared_row_count, 999) + if new_level == 999 then + self.clear = true + end + self.level = new_level + end +end + +function MarathonA1Game:updateScore(level, drop_bonus, cleared_lines) + if not self.clear then + if self.grid:checkForBravo(cleared_lines) then + self.bravo = 4 + self.bravos = self.bravos + 1 + else self.bravo = 1 end + if cleared_lines > 0 then + self.combo = self.combo + (cleared_lines - 1) * 2 + self.score = self.score + ( + (math.ceil((level + cleared_lines) / 4) + drop_bonus) * + cleared_lines * self.combo * self.bravo + ) + else + self.combo = 1 + end + self.drop_bonus = 0 + end +end + +function MarathonA1Game:checkGMRequirements(old_level, new_level) + if old_level < 300 and new_level >= 300 then + if self.score >= 12000 and self.frames <= frameTime(4,15) then + self.gm_conditions["level300"] = true + end + elseif old_level < 500 and new_level >= 500 then + if self.score >= 40000 and self.frames <= frameTime(7,30) then + self.gm_conditions["level500"] = true + end + elseif old_level < 999 and new_level >= 999 then + if self.score >= 126000 and self.frames <= frameTime(13,30) then + self.gm_conditions["level999"] = true + end + end +end + +function MarathonA1Game:drawLifeGrid() + self.grid:draw() + if self.piece ~= nil and self.level < 100 then + self:drawGhostPiece(ruleset) + end +end + +function MarathonA1Game:drawScoringInfo() + MarathonA1Game.super.drawScoringInfo(self) + love.graphics.setColor(1, 1, 1, 1) + + love.graphics.setFont(font_3x5_2) + love.graphics.print( + self.das.direction .. " " .. + self.das.frames .. " " .. + strTrueValues(self.prev_inputs) + ) + love.graphics.printf("NEXT", 64, 40, 40, "left") + love.graphics.printf("GRADE", 240, 120, 40, "left") + love.graphics.printf("SCORE", 240, 200, 40, "left") + love.graphics.printf("NEXT RANK", 240, 260, 90, "left") + love.graphics.printf("LEVEL", 240, 320, 40, "left") + local sg = self.grid:checkSecretGrade() + if sg >= 5 then + love.graphics.printf("SECRET GRADE", 240, 430, 180, "left") + end + + if self.bravos > 0 then love.graphics.printf("BRAVO", 300, 120, 40, "left") end + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(self.score, 240, 220, 90, "left") + if self.gm_conditions["level300"] and self.gm_conditions["level500"] and self.gm_conditions["level999"] then + love.graphics.printf("GM", 240, 140, 90, "left") + else + love.graphics.printf(getRankForScore(self.score).rank, 240, 140, 90, "left") + end + love.graphics.printf(getRankForScore(self.score).next, 240, 280, 90, "left") + love.graphics.printf(self.level, 240, 340, 40, "right") + love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right") + if sg >= 5 then + love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left") + end + if self.bravos > 0 then love.graphics.printf(self.bravos, 300, 140, 40, "left") end + + love.graphics.setFont(font_8x11) + love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") +end + +function MarathonA1Game:getSectionEndLevel() + if self.level >= 900 then return 999 + else return math.floor(self.level / 100 + 1) * 100 end +end + +function MarathonA1Game:getBackground() + return math.floor(self.level / 100) +end + +function MarathonA1Game:getHighscoreData() + return { + grade = self.grade, + score = self.score, + level = self.level, + frames = self.frames, + } +end + +return MarathonA1Game diff --git a/tetris/modes/phantom_mania_n2.lua b/tetris/modes/phantom_mania_n2.lua new file mode 100644 index 0000000..c42800e --- /dev/null +++ b/tetris/modes/phantom_mania_n2.lua @@ -0,0 +1,34 @@ +local SurvivalA3Game = require 'tetris.modes.survival_a3' + +local PhantomManiaN2Game = SurvivalA3Game:extend() + +PhantomManiaN2Game.name = "Phantom Mania N2" +PhantomManiaN2Game.hash = "PhantomManiaN2" +PhantomManiaN2Game.tagline = "As PM1 is to Death, PM2 is to Shirase." + +PhantomManiaN2Game.rollOpacityFunction = function(age) + if age > 4 then return 0 + else return 1 - age / 4 end +end + +PhantomManiaN2Game.garbageOpacityFunction = function(age) + return age > 4 and 0 or 1 +end + +function PhantomManiaN2Game:canDrawLCA() + return self.level < 1000 and self.lcd > 0 +end + +function PhantomManiaN2Game:drawGrid() + if not (self.game_over or self.completed) then + self.grid:drawInvisible( + self.rollOpacityFunction, + self.garbageOpacityFunction, + self.level < 1000 + ) + elseif self.game_over or self.completed then + self.grid:draw() + end +end + +return PhantomManiaN2Game \ No newline at end of file diff --git a/tetris/modes/phantom_mania_nx.lua b/tetris/modes/phantom_mania_nx.lua new file mode 100644 index 0000000..c35b8ae --- /dev/null +++ b/tetris/modes/phantom_mania_nx.lua @@ -0,0 +1,371 @@ +local GameMode = require 'tetris.modes.gamemode' + +local PhantomManiaNXGame = GameMode:extend() + +local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls_35bag' + +PhantomManiaNXGame.name = "Phantom Mania NX" +PhantomManiaNXGame.hash = "PhantomManiaNX" +PhantomManiaNXGame.tagline = "The ultimate invisible challenge! Can you survive the brutal gimmicks?" + +function PhantomManiaNXGame:new() + PhantomManiaNXGame.super:new() + + self.grade = 0 + self.garbage = 0 + self.roll_frames = 0 + self.roll_points = 0 + self.combo = 1 + self.cools = 0 + self.last_section_cool = false + self.tetrises = 0 + self.section_tetris = {} + self.randomizer = History6RollsRandomizer() + + self.SGnames = { + "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9", + "M1", "M2", "M3", "M4", "M5", "M6", "M7", "M8", "M9", + "GM" + } + + self.lock_drop = true + self.lock_hard_drop = true + self.enable_hold = true + self.next_queue_length = 3 + + self.coolregret_message = "COOL!!" + self.coolregret_timer = 0 +end + +function PhantomManiaNXGame:getARE() + if self.level < 300 then return 12 + else return 6 end +end + +function PhantomManiaNXGame:getLineARE() + if self.level < 100 then return 8 + elseif self.level < 200 then return 7 + elseif self.level < 500 then return 6 + elseif self.level < 1300 then return 5 + else return 6 end +end + +function PhantomManiaNXGame:getDasLimit() + if self.level < 100 then return 9 + elseif self.level < 500 then return 7 + else return 5 end +end + +function PhantomManiaNXGame:getLineClearDelay() + if self.level < 1300 then return self:getLineARE() - 2 + else return 6 end +end + +function PhantomManiaNXGame:getLockDelay() + if self.level < 200 then return 18 + elseif self.level < 300 then return 17 + elseif self.level < 500 then return 15 + elseif self.level < 600 then return 13 + elseif self.level < 1100 then return 12 + elseif self.level < 1200 then return 10 + elseif self.level < 1300 then return 8 + else return 15 end +end + +function PhantomManiaNXGame:getGravity() + return 20 +end + +function PhantomManiaNXGame:getGarbageLimit() + if self.level < 600 then return 20 + elseif self.level < 700 then return 18 + elseif self.level < 800 then return 10 + elseif self.level < 900 then return 9 + else return 8 end +end + +function PhantomManiaNXGame:getSkin() + return self.level >= 1000 and "bone" or "2tie" +end + +function PhantomManiaNXGame:hitTorikan(old_level, new_level) + if old_level < 300 and new_level >= 300 and self.frames > frameTime(1,30) then + self.level = 300 + return true + end + if old_level < 500 and new_level >= 500 and self.frames > frameTime(2,28) then + self.level = 500 + return true + end + if old_level < 800 and new_level >= 800 and self.frames > frameTime(3,38) then + self.level = 800 + return true + end + if old_level < 1000 and new_level >= 1000 and self.frames > frameTime(4,56) then + self.level = 1000 + return true + end + return false +end + +function PhantomManiaNXGame:advanceOneFrame() + if self.clear then + self.roll_frames = self.roll_frames + 1 + if self.roll_frames < 0 then + if self.roll_frames + 1 == 0 then + switchBGM("credit_roll", "gm3") + return true + end + return false + elseif self.roll_frames > 3238 then + switchBGM(nil) + self.completed = true + end + elseif self.ready_frames == 0 then + self.frames = self.frames + 1 + end + return true +end + +function PhantomManiaNXGame:onPieceEnter() + if (self.level % 100 ~= 99) and not self.clear and self.frames ~= 0 then + self:updateSectionTimes(self.level, self.level + 1) + self.level = self.level + 1 + end +end + +local cleared_row_levels = {1, 2, 4, 6} +local roll_points = {4, 8, 12, 26} +local mroll_points = {10, 20, 30, 100} + +function PhantomManiaNXGame:qualifiesForGMRoll() + for i = 0, 12 do + if not self.section_tetris[i] then + return false + end + end + return self.cools >= 13 and self.tetrises >= 40 +end + +function PhantomManiaNXGame:onLineClear(cleared_row_count) + if not self.clear then + if cleared_row_count >= 4 then + self.tetrises = self.tetrises + 1 + self.section_tetris[math.floor(self.level / 100)] = true + end + local new_level = self.level + cleared_row_levels[cleared_row_count] + self:updateSectionTimes(self.level, new_level) + if new_level >= 1300 or self:hitTorikan(self.level, new_level) then + self.clear = true + if new_level >= 1300 then + self.level = 1300 + self.grid:clear() + self.roll_frames = -150 + else + self.game_over = true + end + else + self.level = math.min(new_level, 1300) + end + self:advanceBottomRow(-cleared_row_count) + else + if self:qualifiesForGMRoll() then + self.roll_points = self.roll_points + mroll_points[cleared_row_count] + else + self.roll_points = self.roll_points + roll_points[cleared_row_count] + end + end +end + +function PhantomManiaNXGame:onPieceLock(piece, cleared_row_count) + playSE("lock") + if cleared_row_count == 0 then self:advanceBottomRow(1) end +end + +function PhantomManiaNXGame:updateScore(level, drop_bonus, cleared_lines) + if not self.clear then + if cleared_lines > 0 then + self.combo = self.combo + (cleared_lines - 1) * 2 + self.score = self.score + ( + (math.ceil((level + cleared_lines) / 4) + drop_bonus) * + cleared_lines * self.combo + ) + else + self.combo = 1 + end + self.drop_bonus = 0 + end +end + +local cool_cutoffs = { + [0] = frameTime(0,32), frameTime(0,32), frameTime(0,29), frameTime(0,25), frameTime(0,25), + frameTime(0,22), frameTime(0,22), frameTime(0,18), frameTime(0,18), frameTime(0,16), + frameTime(0,16), frameTime(0,14), frameTime(0,14) +} + +function PhantomManiaNXGame:updateSectionTimes(old_level, new_level) + local section_time = self.frames - self.section_start_time + if math.floor(old_level / 100) < math.floor(new_level / 100) then + table.insert(self.section_times, section_time) + self.section_start_time = self.frames + if section_time >= frameTime(1,00) then + self.last_section_cool = false + self.coolregret_message = "REGRET!!" + self.coolregret_timer = 300 + self.grade = self.grade - 1 + elseif self.last_section_cool then + self.cools = self.cools + 1 + end + self.grade = self.grade + 1 + elseif old_level % 100 < 70 and new_level % 100 >= 70 then + local old_section = math.floor(old_level / 100) + table.insert(self.secondary_section_times, section_time) + if ( + (( + self.last_section_cool and + section_time < ( + self.secondary_section_times[old_section] + 120 + ) + ) or not self.last_section_cool) and + section_time <= cool_cutoffs[old_section] + ) then + self.last_section_cool = true + self.coolregret_message = "COOL!!" + self.coolregret_timer = 300 + end + end +end + +function PhantomManiaNXGame:advanceBottomRow(dx) + if self.level >= 500 and self.level < 1000 then + self.garbage = math.max(self.garbage + dx, 0) + if self.garbage >= self:getGarbageLimit() then + self.grid:copyBottomRow() + self.garbage = 0 + end + end +end + +function PhantomManiaNXGame:canDrawLCA() + return ( + self.level < 1000 or ( + self.level >= 1300 and + not self:qualifiesForGMRoll() + ) + ) and self.lcd > 0 +end + +PhantomManiaNXGame.rollOpacityFunction = function(age) + if age > 4 then return 0 + else return 1 - age / 4 end +end + +PhantomManiaNXGame.garbageOpacityFunction = function(age) + return age > 4 and 0 or 1 +end + +function PhantomManiaNXGame:drawGrid() + if not ( + self.game_over or self.completed or + (self.level >= 1300 and not self:qualifiesForGMRoll()) + ) then + self.grid:drawInvisible( + self.rollOpacityFunction, + self.garbageOpacityFunction, + self.level < 1000 + ) + else + self.grid:draw() + end +end + +function PhantomManiaNXGame:getBackground() + return math.floor(self.level / 100) +end + +function PhantomManiaNXGame:getHighscoreData() + return { + grade = self:getAggregateGrade(), + level = self.level, + frames = self.frames, + } +end + +function PhantomManiaNXGame:getAggregateGrade() + local grade_cap + if self:qualifiesForGMRoll() then + if self.roll_frames > 3238 then + grade_cap = 42 + else + grade_cap = 41 + end + else + grade_cap = 26 + end + return math.min( + self.grade + self.cools + math.floor(self.roll_points / 100) + ( + self:qualifiesForGMRoll() and 1 or 0 + ), grade_cap + ) +end + +local master_grades = {"M", "MK", "MV", "MO"} + +local function getLetterGrade(grade) + if grade == 0 then + return "1" + elseif grade <= 13 then + return "S" .. tostring(grade) + elseif grade <= 26 then + return "M" .. tostring(grade - 13) + elseif grade <= 30 then + return master_grades[grade - 26] + elseif grade <= 41 then + return "MM-" .. tostring(grade - 30) + else + return "GM" + end +end + +function PhantomManiaNXGame:drawScoringInfo() + PhantomManiaNXGame.super.drawScoringInfo(self) + + love.graphics.setColor(1, 1, 1, 1) + + local text_x = config["side_next"] and 320 or 240 + + love.graphics.setFont(font_3x5_2) + love.graphics.printf("GRADE", text_x, 120, 40, "left") + love.graphics.printf("SCORE", text_x, 200, 40, "left") + love.graphics.printf("LEVEL", text_x, 320, 40, "left") + local sg = self.grid:checkSecretGrade() + if sg >= 5 then + love.graphics.printf("SECRET GRADE", 240, 430, 180, "left") + end + + if(self.coolregret_timer > 0) then + love.graphics.printf(self.coolregret_message, 64, 400, 160, "center") + self.coolregret_timer = self.coolregret_timer - 1 + end + + local current_section = math.floor(self.level / 100) + 1 + self:drawSectionTimesWithSecondary(current_section) + + love.graphics.setFont(font_3x5_3) + if self.roll_frames > 3238 then love.graphics.setColor(1, 0.5, 0, 1) + elseif self.level >= 1300 then love.graphics.setColor(0, 1, 0, 1) end + love.graphics.printf(getLetterGrade(self:getAggregateGrade()), text_x, 140, 90, "left") + love.graphics.setColor(1, 1, 1, 1) + love.graphics.printf(self.score, text_x, 220, 90, "left") + love.graphics.printf(self.level, text_x, 340, 50, "right") + if self.clear then + love.graphics.printf(self.level, text_x, 370, 50, "right") + else + love.graphics.printf(math.floor(self.level / 100 + 1) * 100, text_x, 370, 50, "right") + end + if sg >= 5 then + love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left") + end +end + +return PhantomManiaNXGame \ No newline at end of file diff --git a/tetris/modes/strategy_pf.lua b/tetris/modes/strategy_pf.lua new file mode 100644 index 0000000..ac6914a --- /dev/null +++ b/tetris/modes/strategy_pf.lua @@ -0,0 +1,225 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Piece = require 'tetris.components.piece' +local Grid = require 'tetris.components.strategygrid' + +local MasterOfBags = require 'tetris.randomizers.masterofbags' + +local StrategyPFGame = GameMode:extend() + +bgm.strategy_pf= + { + love.audio.newSource("res/bgm/data_jack_2nd_version.s3m", "stream"), + love.audio.newSource("res/bgm/OMNIPHIL.s3m", "stream"), + love.audio.newSource("res/bgm/ICEFRONT.s3m", "stream"), + } + + +StrategyPFGame.name = "Strategy PF" +StrategyPFGame.hash = "StrategyPF" +StrategyPFGame.tagline = "Work with a limited number of pieces to reach the end!" + + + +function StrategyPFGame:new() + StrategyPFGame.super:new() + self.grid=Grid(10,24) + + self.roll_frames = 0 + self.remaining = 50 + self.used = 0 + self.bravos = 0 + self.roll_lines = 0 + + self.Gnames = { + "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", + "X", "XI", "XII", "XIII", "XIV", + "M", "M-I", "M-II", "M-III", "M-IV", "M-V", "M-VI", "M-VII", "M-VIII", "M-IX", + "GM" + } + + self.randomizer = MasterOfBags() + + self.instant_hard_drop = true + self.instant_soft_drop = false + self.enable_hard_drop = true + self.enable_hold = true + self.next_queue_length = 2 + self.torikan_check = {} + self.torikan_penalty = 0 + + self.bgmcheck=0 +end + +function StrategyPFGame:getRank() + local level=self.level + local names=self.Gnames + return names[math.floor(level/100) + 1 + math.floor(math.min(30, self.roll_lines)/3) - self.torikan_penalty] +end + +function StrategyPFGame:getGravity() + return 0 +end + +function StrategyPFGame:advanceOneFrame() + if self.ready_frames == 0 then + self.frames = self.frames + 1 + end + if self.bgmcheck==0 then + switchBGM("strategy_pf", 1) + self.bgmcheck=1 + elseif self.bgmcheck==1 then + if self.level>980 then + fadeoutBGM(180) + self.bgmcheck=-1 + end + elseif self.bgmcheck==-1 then + if self.level>999 then + switchBGM("strategy_pf", 2) + self.bgmcheck=2 + end + elseif self.bgmcheck==2 then + if self.level>1480 then + fadeoutBGM(180) + self.bgmcheck=-2 + end + elseif self.bgmcheck==-2 then + if self.level>1499 then + switchBGM("strategy_pf", 3) + self.bgmcheck=3 + end + end + processBGMFadeout(1) + return true +end + +function StrategyPFGame:onPieceEnter() + if (self.level % 100 ~= 99) and not self.clear and self.frames ~= 0 then + self.level = self.level + 1 + end + self.remaining = self.remaining - 1 +end + +function StrategyPFGame:onPieceLock(piece, cleared_row_count) + playSE("lock") + if self.remaining==0 and (self.clear or cleared_row_count==0) then + self.completed=true + end + if self.level<1500 then + self.used = self.used + 1 + end + self.grid:placement() +end + +function StrategyPFGame:onLineClear(cleared_row_count) + if not self.clear then + if self.level<1500 then self.remaining = self.remaining + math.ceil(({2, 6, 8, 20})[math.min(4,cleared_row_count)] * (1 - math.min(1000, self.level)/2000)) end + local new_level = math.min(self.level + cleared_row_count + math.max(0, (self.remaining - self:getPieceLimit())), 1500) + if new_level == 1500 then + self.remaining = 100 + self.clear = true + end + self.level = new_level + if self.level < 1500 then + self.remaining = math.min(self.remaining, self:getPieceLimit()) + end + for x=1,6 do + if self.level > (899 + x * 100) and not self.torikan_check[x] then + if self.used > 70 * math.floor(self.level/100) - 100 then + self.level = 900 + 100 * x + self.completed = true + self.torikan_penalty = 1 + return + end + self.torikan_check[x]=true + end + end + else + self.roll_lines = self.roll_lines + cleared_row_count + if self.roll_lines > 29 then + self.completed = true + end + end +end + +function StrategyPFGame:getPieceLimit() + return 100 - math.floor(math.min(1000 , self.level)/20) +end + +StrategyPFGame.rollOpacityFunction = {} + +for x=1, 26 do + StrategyPFGame.rollOpacityFunction[x] = function(age, placements) + if placements < 27-x or age < 4 then return 1 + else return 0 end + end +end + + + +function StrategyPFGame:drawGrid() + if self.level>999 and not (self.completed or self.game_over) then + self.grid:drawInvisible(self.rollOpacityFunction[math.floor(self.level/20)-49], nil, true) + else + self.grid:draw() + end +end + +function StrategyPFGame:drawScoringInfo() + love.graphics.setColor(1, 1, 1, 1) + + love.graphics.setFont(font_3x5_2) + love.graphics.print( + self.das.direction .. " " .. + self.das.frames .. " " .. + strTrueValues(self.prev_inputs) + ) + + love.graphics.printf("NEXT", 64, 40, 40, "left") + love.graphics.printf("GRADE", 240, 120, 40, "left") + love.graphics.printf("REMAINING PIECES", 240, 200, 150, "left") + love.graphics.printf("LEVEL", 240, 320, 40, "left") + love.graphics.printf("PIECES USED", 64, 420, 160, "left") + + if self.bravos > 0 then love.graphics.printf("BRAVO", 300, 120, 40, "left") end + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(self:getRank(), 240, 140, 120, "left") + if self.level<1500 then + if self.remaining<=25 then + love.graphics.setColor(1, 1, 0, 1) + if self.remaining<=10 then + love.graphics.setColor(1, 0, 0, 1) + end + end + love.graphics.printf(self.remaining.."/"..self:getPieceLimit(), 240, 220, 80, "left") + love.graphics.setColor(1, 1, 1, 1) + else + love.graphics.printf(self.remaining, 240, 220, 80, "left") + end + + love.graphics.printf(self.level, 240, 340, 50, "right") + love.graphics.printf(self:getSectionEndLevel(), 240, 370, 50, "right") + if self.bravos > 0 then love.graphics.printf(self.bravos, 300, 140, 40, "left") end + + love.graphics.setFont(font_8x11) + love.graphics.printf(self.used, 64, 420, 160, "right") +end + +function StrategyPFGame:getSectionEndLevel() + return math.min(1500, math.floor(self.level / 100 + 1) * 100) +end + +function StrategyPFGame:getBackground() + return math.floor(self.level / 100) +end + +function StrategyPFGame:getHighscoreData() + return { + level = self.level, + roll_lines = self.roll_lines + } +end + +return StrategyPFGame diff --git a/tetris/modes/survival_gte.lua b/tetris/modes/survival_gte.lua new file mode 100644 index 0000000..c14dbf3 --- /dev/null +++ b/tetris/modes/survival_gte.lua @@ -0,0 +1,99 @@ +local GameMode = require 'tetris.modes.gamemode' +local Bag7Randomizer = require 'tetris.randomizers.bag7' + +local SurvivalGTEGame = GameMode:extend() + +SurvivalGTEGame.name = "Survival GTE" +SurvivalGTEGame.hash = "SurvivalGTE" +SurvivalGTEGame.tagline = "A well-known Master mode that ramps up in difficulty quickly!" + +function SurvivalGTEGame:new() + SurvivalGTEGame.super:new() + + self.lock_drop = true + self.lock_hard_drop = true + self.instant_hard_drop = true + self.instant_soft_drop = false + self.enable_hold = true + self.next_queue_length = 4 +end + +function SurvivalGTEGame:getARE() return 6 end +function SurvivalGTEGame:getLineARE() return 6 end +function SurvivalGTEGame:getGravity() return 20 end +function SurvivalGTEGame:getARR() return 2 end + +function SurvivalGTEGame:getDasLimit() + return math.min(self:getLockDelay() - 2, 10) +end + +function SurvivalGTEGame:getLockDelay() + if self.lines < 100 then return 29 + elseif self.lines < 110 then return 27 + elseif self.lines < 120 then return 25 + elseif self.lines < 130 then return 22 + elseif self.lines < 140 then return 20 + elseif self.lines < 150 then return 17 + elseif self.lines < 160 then return 16 + elseif self.lines < 180 then return 15 + elseif self.lines < 190 then return 13 + elseif self.lines < 210 then return 11 + elseif self.lines < 230 then return 10 + elseif self.lines < 240 then return 9 + elseif self.lines < 260 then return 8 + elseif self.lines < 280 then return 7 + elseif self.lines < 290 then return 6 + else return 5 end +end + +function SurvivalGTEGame:getLineClearDelay() + return math.max(30 - math.floor(self.lines / 10) * 3, 1) +end + +function SurvivalGTEGame:onLineClear(cleared_row_count) + self.lines = math.min(self.lines + cleared_row_count, 300) + self.completed = self.lines == 300 +end + +function SurvivalGTEGame:advanceOneFrame() + if self.ready_frames == 0 then + self.frames = self.frames + 1 + end +end + +function SurvivalGTEGame:getBackground() + return math.floor(self.lines / 10) % 20 +end + +function SurvivalGTEGame:getHighscoreData() + return { + lines = self.lines, + frames = self.frames + } +end + +function SurvivalGTEGame:drawScoringInfo() + love.graphics.setColor(1, 1, 1, 1) + + love.graphics.setFont(font_3x5_2) + love.graphics.print( + self.das.direction .. " " .. + self.das.frames .. " " .. + strTrueValues(self.prev_inputs) + ) + love.graphics.printf("NEXT", 64, 40, 40, "left") + love.graphics.printf("SPEED LEVEL", 240, 280, 160, "left") + love.graphics.printf("LINES", 240, 350, 160, "left") + + love.graphics.setFont(font_3x5_3) + love.graphics.printf( + "M" .. math.min(30, math.floor(self.lines / 10) + 1), + 240, 300, 160, "left" + ) + love.graphics.printf(self.lines, 240, 370, 160, "left") + + love.graphics.setFont(font_8x11) + love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") +end + +return SurvivalGTEGame \ No newline at end of file diff --git a/tetris/randomizers/masterofbags.lua b/tetris/randomizers/masterofbags.lua new file mode 100644 index 0000000..a3ab2d3 --- /dev/null +++ b/tetris/randomizers/masterofbags.lua @@ -0,0 +1,48 @@ +local Randomizer = require 'tetris.randomizers.randomizer' + +local MasterOfBags = Randomizer:extend() + +function MasterOfBags:initialize() + self.history = {"S", "Z", "S", "Z"} + self.first = true + self.bag={} + for x=1,28 do + self.bag[x]=({"I", "J", "L", "O", "S", "T", "Z"})[(x-1)%7+1] + end +end + +function MasterOfBags:generatePiece() + local piece=math.random(({[true]=#self.bag,[false]=#self.bag+1})[#self.bag>14]) + if piece>#self.bag then + self.bag={} + for x=1,28 do + self.bag[x]=({"I", "J", "L", "O", "S", "T", "Z"})[(x-1)%7+1] + end + piece=math.random(#self.bag) + end + for i = 1, 6 do + if not inHistory(self.bag[piece], self.history) then + break + end + piece=math.random(#self.bag) + end + self:updateHistory(self.bag[piece]) + + return table.remove(self.bag, piece) +end + +function MasterOfBags:updateHistory(shape) + table.remove(self.history, 1) + table.insert(self.history, shape) +end + +function inHistory(piece, history) + for idx, entry in pairs(history) do + if entry == piece then + return true + end + end + return false +end + +return MasterOfBags diff --git a/tetris/rulesets/cambridge-m.lua b/tetris/rulesets/cambridge-m.lua new file mode 100644 index 0000000..920340a --- /dev/null +++ b/tetris/rulesets/cambridge-m.lua @@ -0,0 +1,18 @@ +local CRS = require 'tetris.rulesets.cambridge' + +local CRS_M = CRS:extend() + +CRS_M.name = "Cambridge-M" +CRS_M.hash = "Cambridge-M" + +function CRS_M:onPieceMove(piece, grid) + CRS.onPieceMove(CRS, piece, grid) + piece.lock_delay = 0 +end + +function CRS_M:onPieceRotate(piece, grid) + CRS.onPieceRotate(CRS, piece, grid) + piece.lock_delay = 0 +end + +return CRS_M \ No newline at end of file