Compare commits

..

12 Commits

Author SHA1 Message Date
Joe Z
2b9740768b How did that get away? 2019-07-07 23:14:17 -04:00
Joe Z
0e65b39395 BGM kept looping after exiting credits_a3, which is not what we want. 2019-07-07 19:38:46 -04:00
Joe Z
ca04333bb7 Lowered the volume of Credits A3 by a factor of 10. 2019-07-07 19:34:07 -04:00
Joe Zeng
80c7c99bd7 Added Credits A3 mode. (#26)
Also updated the documentation.
2019-07-07 18:55:03 -04:00
Joe Z
717afbebf6 Added Marathon WCB. 2019-07-07 18:06:13 -04:00
Joe Z
7227085f84 Made instant DAS respect instant gravity. 2019-07-07 17:25:35 -04:00
Joe Zeng
c40392f00f DAS priority reversal (#25)
* Reversed the priority of key presses when charging DAS.
* Made it an actual config option.
* Config should be false by default.
2019-07-07 17:23:17 -04:00
Joe Z
001c8f0ea8 Fixed some issues with Marathon A1. 2019-07-07 12:11:28 -04:00
Joe Z
7fa0e60145 Made the menu scroll up and down when it overflows. 2019-06-29 15:09:48 -04:00
Joe Z
901f7f2d12 Fixed a bug in the "actual cleared row count" for Big Mode. 2019-06-22 00:07:10 -04:00
Joe Z
35f4aea67d Added Ti-SRS and modified delay curve behaviour on Marathon 2020.
I realized that playing at 4/8 for 800 levels straight is probably too much,
so I made it that only the first 10 sections count for advancing the delay
curve faster than it would normally go. Now only the last 500 levels can be
at delay level 20.
2019-06-21 23:44:58 -04:00
Joe Z
7deaa5ab92 Added all the Marathon AX modes and Ace-ARS. 2019-06-19 22:56:33 -04:00
58 changed files with 1054 additions and 1897 deletions

View File

@@ -3,14 +3,6 @@ Cambridge
Welcome to Cambridge, the next open-source falling-block game engine!
This fork is written and maintained exclusively by [SashLilac](https://github.com/SashLilac) and [Oshisaure](https://github.com/oshisaure)!
Credits
-------
- [Lilla Oshisaure](https://www.youtube.com/user/LeSpyroshisaure) for their amazing contributions to my life in general!
- [The Tetra Legends Discord](http://discord.com/invite/7hMx5r2) for supporting me and playtesting!
- [joezeng](https://github.com/joezeng) for the original project.
Installation instructions
-------------------------
@@ -23,7 +15,7 @@ Unzip the exe file and run it directly. All assets are currently bundled inside
### macOS
For the time being, the file `cambridge.love` only works on the command line. Install `love` with [Homebrew](https://brew.sh), and run:
For the time being, the file `cambridge.love` only works on the command line. Install `love` with [https://brew.sh/](Homebrew), and run:
$ love cambridge.love
@@ -43,7 +35,7 @@ If you haven't already, install `love` with your favourite package manager (Home
Clone the repository in git:
git clone https://github.com/SashLilac/cambridge
git clone https://github.com/joezeng/cambridge
Then, navigate to the root directory that you just cloned, and type:

View File

@@ -15,7 +15,9 @@ There are several classes of game modes. The modes that originate from other gam
* The "G" series stand for "Guideline" games, or games that follow the Tetris Guideline.
* GF - Tetris Friends (2007-2019)
* GJ - Tetris Online Japan (2005-2011)
* N stands for Nullpomino, only used for Phantom Mania N.
* The "N" series stands for Nullpomino, only used for Phantom Mania N.
* The "W" series stands for "Web" games, which are fanmade games released on the web.
* WCB - RainComplex.net's Cat Boi Quatro.
MARATHON
--------
@@ -23,12 +25,15 @@ MARATHON
Modes in which the goal is to play as well as possible over a limited game interval.
* **MARATHON 2020**: 2020 levels of pure pain. Can you make it all the way?
* **MARATHON WCB**: CatBoiQuatro! Can you control the pieces?
From other games:
* **MARATHON A1**: Tetris the Grand Master 1.
* **MARATHON A2**: Tetris the Grand Master 2 (TAP Master).
* **MARATHON A3**: Tetris the Grand Master 3 (no exams).
* **MARATHON AX4**: Another mode from TGM Ace.
* **MARATHON AX**: Normal mode from TGM Ace.
* **MARATHON AX2**: Hi-Speed1 mode from TGM Ace.
* **MARATHON AX3**: Hi-Speed2 mode from TGM Ace.
* **MARATHON C89**: Nintendo NES Tetris. Can you transition and make it to the killscreen?
@@ -43,6 +48,8 @@ From other games:
* **SURVIVAL A1**: 20G mode from Tetris the Grand Master.
* **SURVIVAL A2**: T.A. Death.
* **SURVIVAL A3**: Ti Shirase.
* **SURVIVAL AX**: Another mode from TGM Ace.
* **SURVIVAL AX2**: Another2 mode from TGM Ace.
RACE
@@ -53,6 +60,14 @@ Modes with no levels, just a single timed goal.
* **Race 40**: How fast can you clear 40 lines? No limits, no holds barred.
CREDITS
-------
Modes that are just the credit rolls of specific games.
* **CREDITS A3**: Tetris the Grand Master 3's famous M-roll.
PHANTOM MANIA
-------------
@@ -63,7 +78,6 @@ Modes where pieces turn invisible as soon as you lock them. One of Cambridge's s
* **Phantom Mania 2**: Phantom Mania but way faster! Can you face a mode where even the garbage and the next preview turn invisible?
OTHER MODES
-----------

View File

@@ -10,16 +10,13 @@ A ruleset consists of the following things:
If you're used to Nullpomino, you may notice a few things missing from that definition. For example, piece previews, hold queues, and randomizers have been moved to being game-specific rules, rather than rules that are changeable with the ruleset you use. Soft and hard drop behaviour is also game-specific now, so that times can be more plausibly compared across rulesets.
There are six rulesets currently supported:
Rotation system
---------------
A rotation system defines the following things:
* The block offsets of each piece orientation.
* The wall or floor kicks that will be attempted for each type of rotation.
* Cambridge - a ruleset original to Cambridge, used for all custom modes. Supports 180-degree rotations!
There are four rotation systems currently supported:
* SRS - the rotation system used in the Tetris Guideline games. Supports 180-degree rotations!
* Ti-SRS - SRS but with no 180-degree rotations.
* Cambridge
* Classic ARS
* Ti-ARS
* SRS
* ARS - the rotation system from the original Tetris the Grand Master.
* Ti-ARS - ARS with floorkicks! From TGM3: Terror Instinct.
* Ace-ARS - ARS with floorkicks and move reset! From TGM ACE.

View File

@@ -1,5 +1,4 @@
function copy(t)
-- returns deep copy of t (as opposed to the shallow copy you get from var = t)
if type(t) ~= "table" then return t end
local meta = getmetatable(t)
local target = {}
@@ -8,8 +7,7 @@ function copy(t)
return target
end
function strTrueValues(tbl)
-- returns a concatenation of all the keys in tbl with value true, separated with spaces
function st(tbl)
str = ""
for k, v in pairs(tbl) do
if v == true then
@@ -19,16 +17,14 @@ function strTrueValues(tbl)
return str
end
function frameTime(min, sec, hth)
-- returns a time in frames from a time in minutes-seconds-hundredths format
if min == nil then min = 0 end
if sec == nil then sec = 0 end
if hth == nil then hth = 0 end
return min*3600 + sec*60 + math.ceil(hth * 0.6)
function sp(m, s, f)
if m == nil then m = 0 end
if s == nil then s = 0 end
if f == nil then f = 0 end
return m*3600 + s*60 + math.ceil(f * 0.6)
end
function vAdd(v1, v2)
-- returns the sum of vectors v1 and v2
return {
x = v1.x + v2.x,
y = v1.y + v2.y
@@ -36,7 +32,6 @@ function vAdd(v1, v2)
end
function vNeg(v)
-- returns the opposite of vector v
return {
x = -v.x,
y = -v.y
@@ -44,18 +39,14 @@ function vNeg(v)
end
function formatTime(frames)
-- returns a mm:ss:hh (h=hundredths) representation of the time in frames given
if frames < 0 then return formatTime(0) end
local min, sec, hund
min = math.floor(frames/3600)
sec = math.floor(frames/60) % 60
hund = math.floor(frames/.6) % 100
str = string.format("%02d:%02d.%02d", min, sec, hund)
str = string.format("%02d", math.floor(frames / 3600)) .. ":"
.. string.format("%02d", math.floor(frames / 60) % 60) .. "."
.. string.format("%02d", math.floor(frames / 0.6) % 100)
return str
end
function formatBigNum(number)
-- returns a string representing a number with commas as thousands separator (e.g. 12,345,678)
local s = string.format("%d", number)
local pos = string.len(s) % 3
if pos == 0 then pos = 3 end

View File

@@ -20,7 +20,6 @@ backgrounds = {
love.graphics.newImage("res/backgrounds/1800-railways.png"),
love.graphics.newImage("res/backgrounds/1900-world-wide-web.png"),
title = love.graphics.newImage("res/backgrounds/title_v0.1.png"),
input_config = love.graphics.newImage("res/backgrounds/options-gears.png")
}
blocks = {

View File

@@ -11,6 +11,7 @@ function love.load()
config["side_next"] = false
config["reverse_rotate"] = true
config["fullscreen"] = false
config["das_last_key"] = false
love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

View File

@@ -7,7 +7,6 @@ function Scene:update() end
function Scene:render() end
function Scene:onKeyPress() end
ExitScene = require "scene.exit"
GameScene = require "scene.game"
ModeSelectScene = require "scene.mode_select"
InputConfigScene = require "scene.input_config"

View File

@@ -1,23 +0,0 @@
local ExitScene = Scene:extend()
require 'load.save'
ExitScene.title = "Exit Game"
function ExitScene:new()
end
function ExitScene:update()
love.event.quit()
end
function ExitScene:render()
end
function ExitScene:changeOption(rel)
end
function ExitScene:onKeyPress(e)
end
return ExitScene

View File

@@ -57,21 +57,11 @@ end
function GameScene:onKeyPress(e)
if (self.game.completed) and
(e.scancode == "return" or e.scancode == "escape") and e.isRepeat == false then
e.scancode == "return" and e.isRepeat == false then
highscore_entry = self.game:getHighscoreData()
highscore_hash = self.game.hash .. "-" .. self.ruleset.hash
submitHighscore(highscore_hash, highscore_entry)
scene = ModeSelectScene()
elseif (e.scancode == config.input.retry) then
-- fuck this, this is hacky but the way this codebase is setup prevents anything else
-- it seems like all the values that get touched in the child gamemode class
-- stop being linked to the values of the GameMode superclass because of how `mt.__index` works
-- not even sure this is the actual problem, but I don't want to have to rebuild everything about
-- the core organisation of everything. this hacky way will have to do until someone figures out something.
love.keypressed("escape", "escape", false)
love.keypressed("return", "return", false)
elseif e.scancode == "escape" then
scene = ModeSelectScene()
end
end

View File

@@ -15,7 +15,6 @@ local configurable_inputs = {
"rotate_right2",
"rotate_180",
"hold",
"retry",
}
function ConfigScene:new()
@@ -28,13 +27,6 @@ function ConfigScene:update()
end
function ConfigScene:render()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(
backgrounds["input_config"],
0, 0, 0,
0.5, 0.5
)
love.graphics.setFont(font_3x5_2)
for i, input in pairs(configurable_inputs) do
if config.input[input] then

95
scene/mode_select.lua Executable file → Normal file
View File

@@ -5,13 +5,16 @@ ModeSelectScene.title = "Game Start"
current_mode = 1
current_ruleset = 1
MAX_MODES = 19
game_modes = {
require 'tetris.modes.marathon_2020',
require 'tetris.modes.survival_2020',
--require 'tetris.modes.strategy',
--require 'tetris.modes.interval_training',
--require 'tetris.modes.pacer_test',
--require 'tetris.modes.demon_mode',
require 'tetris.modes.strategy',
require 'tetris.modes.interval_training',
require 'tetris.modes.pacer_test',
require 'tetris.modes.marathon_wcb',
require 'tetris.modes.demon_mode',
require 'tetris.modes.phantom_mania',
require 'tetris.modes.phantom_mania2',
require 'tetris.modes.phantom_mania_n',
@@ -19,23 +22,25 @@ game_modes = {
require 'tetris.modes.marathon_a1',
require 'tetris.modes.marathon_a2',
require 'tetris.modes.marathon_a3',
require 'tetris.modes.marathon_ax4',
require 'tetris.modes.marathon_ax',
require 'tetris.modes.marathon_ax2',
require 'tetris.modes.marathon_ax3',
require 'tetris.modes.marathon_c89',
require 'tetris.modes.survival_a1',
require 'tetris.modes.survival_a2',
require 'tetris.modes.survival_a3',
require 'tetris.modes.big_a2',
require 'tetris.modes.konoha',
require 'tetris.modes.survival_ax',
require 'tetris.modes.survival_ax2',
require 'tetris.modes.credits_a3',
}
rulesets = {
require 'tetris.rulesets.cambridge',
require 'tetris.rulesets.standard',
require 'tetris.rulesets.standard_ti',
require 'tetris.rulesets.arika',
require 'tetris.rulesets.arika_ti',
require 'tetris.rulesets.ti_srs',
require 'tetris.rulesets.arika_ace',
require 'tetris.rulesets.arika_srs',
require 'tetris.rulesets.standard_exp',
--require 'tetris.rulesets.bonkers',
--require 'tetris.rulesets.shirase',
--require 'tetris.rulesets.super302',
@@ -52,38 +57,58 @@ end
function ModeSelectScene:update()
end
function getCursorHeight(x)
return 78 + 20 * x
end
function ModeSelectScene:drawList(name, items, cursor, x)
local start, finish
if table.getn(items) < MAX_MODES then
start = 1
finish = table.getn(items)
else
if cursor < 10 then
start = 1
finish = 19
elseif cursor > table.getn(items) - 9 then
start = table.getn(items) - 18
finish = table.getn(items)
else
start = cursor - 9
finish = cursor + 9
end
end
if self.menu_state.select == name then
love.graphics.setColor(1, 1, 1, 0.5)
else
love.graphics.setColor(1, 1, 1, 0.25)
end
love.graphics.rectangle("fill", x, getCursorHeight(cursor - start), 240, 22)
love.graphics.setColor(1, 1, 1, 1)
for idx = start, finish do
local item = items[idx]
love.graphics.printf(item.name, x + 20, 80 + 20 * (idx - start), 200, "left")
end
end
function ModeSelectScene:render()
love.graphics.setFont(font_3x5_2)
love.graphics.draw(
backgrounds[0],
0, 0, 0,
0.5, 0.5
)
if self.menu_state.select == "mode" then
love.graphics.setColor(1, 1, 1, 0.5)
elseif self.menu_state.select == "ruleset" then
love.graphics.setColor(1, 1, 1, 0.25)
end
love.graphics.rectangle("fill", 20, 78 + 20 * self.menu_state.mode, 240, 22)
if self.menu_state.select == "mode" then
love.graphics.setColor(1, 1, 1, 0.25)
elseif self.menu_state.select == "ruleset" then
love.graphics.setColor(1, 1, 1, 0.5)
end
love.graphics.rectangle("fill", 340, 78 + 20 * self.menu_state.ruleset, 200, 22)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(misc_graphics["select_mode"], 20, 30)
love.graphics.draw(misc_graphics["select_mode"], 20, 40)
love.graphics.setFont(font_3x5_2)
for idx, mode in pairs(game_modes) do
love.graphics.printf(mode.name, 40, 80 + 20 * idx, 200, "left")
end
for idx, ruleset in pairs(rulesets) do
love.graphics.printf(ruleset.name, 360, 80 + 20 * idx, 160, "left")
end
self:drawList("mode", game_modes, self.menu_state.mode, 20)
self:drawList("ruleset", rulesets, self.menu_state.ruleset, 320)
end
function ModeSelectScene:onKeyPress(e)
@@ -101,8 +126,6 @@ function ModeSelectScene:onKeyPress(e)
elseif (e.scancode == config.input["left"] or e.scancode == "left") or
(e.scancode == config.input["right"] or e.scancode == "right") then
self:switchSelect()
elseif e.scancode == "escape" then
scene = TitleScene()
end
end

View File

@@ -3,7 +3,6 @@ local TitleScene = Scene:extend()
local main_menu_screens = {
ModeSelectScene,
InputConfigScene,
ExitScene,
}
function TitleScene:new()
@@ -44,8 +43,6 @@ function TitleScene:onKeyPress(e)
self:changeOption(-1)
elseif (e.scancode == config.input["down"] or e.scancode == "down") and e.isRepeat == false then
self:changeOption(1)
elseif e.scancode == "escape" and e.isRepeat == false then
love.event.quit()
end
end

View File

@@ -3,7 +3,6 @@ local Object = require 'libs.classic'
local Grid = Object:extend()
local empty = { skin = "", colour = "" }
local oob = { skin = "", colour = "" }
function Grid:new()
self.grid = {}
@@ -27,15 +26,8 @@ function Grid:clear()
end
end
function Grid:getCell(x, y)
if x < 1 or x > 10 or y > 24 then return oob
elseif y < 1 then return empty
else return self.grid[y][x]
end
end
function Grid:isOccupied(x, y)
return self:getCell(x+1, y+1) ~= empty
return self.grid[y+1][x+1] ~= empty
end
function Grid:isRowFull(row)
@@ -54,7 +46,7 @@ function Grid:canPlacePiece(piece)
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
if x >= 10 or x < 0 or y >= 24 or y < 0 or self.grid[y+1][x+1] ~= empty then
return false
end
end
@@ -66,12 +58,12 @@ function Grid:canPlaceBigPiece(piece)
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
if x >= 5 or x < 0 or y >= 12 or y < 0 or
self.grid[y * 2 + 1][x * 2 + 1] ~= empty or
self.grid[y * 2 + 1][x * 2 + 2] ~= empty or
self.grid[y * 2 + 2][x * 2 + 1] ~= empty or
self.grid[y * 2 + 2][x * 2 + 2] ~= empty
then
return false
end
end
@@ -88,7 +80,7 @@ function Grid:canPlacePieceInVisibleGrid(piece)
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
if x >= 10 or x < 0 or y >= 24 or y < 4 or self.grid[y+1][x+1] ~= empty then
return false
end
end
@@ -158,12 +150,10 @@ function Grid:applyPiece(piece)
for index, offset in pairs(offsets) do
x = piece.position.x + offset.x
y = piece.position.y + offset.y
if y + 1 > 0 then
self.grid[y+1][x+1] = {
skin = piece.skin,
colour = piece.shape
}
end
self.grid[y+1][x+1] = {
skin = piece.skin,
colour = piece.shape
}
end
end
@@ -174,60 +164,15 @@ function Grid:applyBigPiece(piece)
y = piece.position.y + offset.y
for a = 1, 2 do
for b = 1, 2 do
if y*2+a > 0 then
self.grid[y*2+a][x*2+b] = {
skin = piece.skin,
colour = piece.shape
}
end
self.grid[y*2+a][x*2+b] = {
skin = piece.skin,
colour = piece.shape
}
end
end
end
end
function Grid:checkForBravo(cleared_row_count)
for i = 0, 23 - cleared_row_count do
for j = 0, 9 do
if self:isOccupied(j, i) then return false end
end
end
return true
end
function Grid: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 Grid:update()
for y = 1, 24 do
for x = 1, 10 do

View File

@@ -77,12 +77,15 @@ function Piece:setRelativeRotation(rot)
return self
end
function Piece:moveInGrid(step, squares, grid)
function Piece:moveInGrid(step, squares, grid, instant)
local moved = false
for x = 1, squares do
if grid:canPlacePiece(self:withOffset(step)) then
moved = true
self:setOffset(step)
if instant then
self:dropToBottom(grid)
end
else
break
end
@@ -97,7 +100,7 @@ end
function Piece:dropToBottom(grid)
local piece_y = self.position.y
self:dropSquares(24, grid)
self:dropSquares(20, grid)
self.gravity = 0
if self.position.y > piece_y then
-- if it got dropped any, also reset lock delay

View File

@@ -1,365 +0,0 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local MarathonA2Game = GameMode:extend()
MarathonA2Game.name = "Big A2"
MarathonA2Game.hash = "BigA2"
MarathonA2Game.tagline = "The points don't matter! Can you reach the invisible roll? Big mode too!"
function MarathonA2Game:new()
self.super:new()
self.big_mode = true
self.roll_frames = 0
self.combo = 1
self.randomizer = History6RollsRandomizer()
self.grade = 0
self.grade_points = 0
self.grade_point_decay_counter = 0
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_tetrises = { [0] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
self.randomizer = History6RollsRandomizer()
self.lock_drop = false
self.enable_hold = false
self.next_queue_length = 1
end
function MarathonA2Game:getARE()
if self.level < 700 then return 27
elseif self.level < 800 then return 18
else return 14 end
end
function MarathonA2Game:getLineARE()
if self.level < 600 then return 27
elseif self.level < 700 then return 18
elseif self.level < 800 then return 14
else return 8 end
end
function MarathonA2Game:getDasLimit()
if self.level < 500 then return 15
elseif self.level < 900 then return 9
else return 7 end
end
function MarathonA2Game:getLineClearDelay()
if self.level < 500 then return 40
elseif self.level < 600 then return 25
elseif self.level < 700 then return 16
elseif self.level < 800 then return 12
else return 6 end
end
function MarathonA2Game:getLockDelay()
if self.level < 900 then return 30
else return 17 end
end
function MarathonA2Game:getGravity()
if (self.level < 30) then return 4/256
elseif (self.level < 35) then return 6/256
elseif (self.level < 40) then return 8/256
elseif (self.level < 50) then return 10/256
elseif (self.level < 60) then return 12/256
elseif (self.level < 70) then return 16/256
elseif (self.level < 80) then return 32/256
elseif (self.level < 90) then return 48/256
elseif (self.level < 100) then return 64/256
elseif (self.level < 120) then return 80/256
elseif (self.level < 140) then return 96/256
elseif (self.level < 160) then return 112/256
elseif (self.level < 170) then return 128/256
elseif (self.level < 200) then return 144/256
elseif (self.level < 220) then return 4/256
elseif (self.level < 230) then return 32/256
elseif (self.level < 233) then return 64/256
elseif (self.level < 236) then return 96/256
elseif (self.level < 239) then return 128/256
elseif (self.level < 243) then return 160/256
elseif (self.level < 247) then return 192/256
elseif (self.level < 251) then return 224/256
elseif (self.level < 300) then return 1
elseif (self.level < 330) then return 2
elseif (self.level < 360) then return 3
elseif (self.level < 400) then return 4
elseif (self.level < 420) then return 5
elseif (self.level < 450) then return 4
elseif (self.level < 500) then return 3
else return 20
end
end
function MarathonA2Game:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames > 3694 then
self.completed = true
if self.grade == 32 then
self.grade = 33
end
end
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
end
return true
end
function MarathonA2Game: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 MarathonA2Game:onLineClear(cleared_row_count)
cleared_row_count = cleared_row_count / 2
self:updateSectionTimes(self.level, self.level + cleared_row_count)
self.level = math.min(self.level + cleared_row_count, 999)
if self.level == 999 and not self.clear then
self.clear = true
if self:qualifiesForMRoll() then
self.grade = 32
end
self.grid:clear()
self.roll_frames = -150
end
end
function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
cleared_lines = cleared_lines / 2
self:updateGrade(cleared_lines)
if cleared_lines > 0 then
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
)
self.lines = self.lines + cleared_lines
self.combo = self.combo + cleared_lines - 1
else
self.drop_bonus = 0
self.combo = 1
end
end
function MarathonA2Game:updateSectionTimes(old_level, new_level)
if self.clear then return end
if math.floor(old_level / 100) < math.floor(new_level / 100) or
new_level >= 999 then
-- record new section
section_time = self.frames - self.section_start_time
self.section_times[math.floor(old_level / 100)] = section_time
self.section_start_time = self.frames
end
end
local grade_point_bonuses = {
{10, 20, 40, 50},
{10, 20, 30, 40},
{10, 20, 30, 40},
{10, 15, 30, 40},
{10, 15, 20, 40},
{5, 15, 20, 30},
{5, 10, 20, 30},
{5, 10, 15, 30},
{5, 10, 15, 30},
{5, 10, 15, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
}
local grade_point_decays = {
125, 80, 80, 50, 45, 45, 45,
40, 40, 40, 40, 40, 30, 30, 30,
20, 20, 20, 20, 20,
15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
10, 10
}
local combo_multipliers = {
{1.0, 1.0, 1.0, 1.0},
{1.2, 1.4, 1.5, 1.0},
{1.2, 1.5, 1.8, 1.0},
{1.4, 1.6, 2.0, 1.0},
{1.4, 1.7, 2.2, 1.0},
{1.4, 1.8, 2.3, 1.0},
{1.4, 1.9, 2.4, 1.0},
{1.5, 2.0, 2.5, 1.0},
{1.5, 2.1, 2.6, 1.0},
{2.0, 2.5, 3.0, 1.0},
}
local grade_conversion = {
[0] = 0,
1, 2, 3, 4, 5, 5, 6, 6, 7, 7,
7, 8, 8, 8, 9, 9, 9, 10, 11, 12,
12, 12, 13, 13, 14, 14, 15, 15, 16, 16,
17, 18, 19
}
function MarathonA2Game:updateGrade(cleared_lines)
if self.clear then return end
if cleared_lines == 0 then
self.grade_point_decay_counter = self.grade_point_decay_counter + 1
if self.grade_point_decay_counter >= grade_point_decays[self.grade + 1] then
self.grade_point_decay_counter = 0
self.grade_points = math.max(0, self.grade_points - 1)
end
else
self.grade_points = self.grade_points + (
math.ceil(
grade_point_bonuses[self.grade + 1][cleared_lines] *
combo_multipliers[math.min(self.combo, 10)][cleared_lines]
) * (1 + math.floor(self.level / 250))
)
if self.grade_points >= 100 and self.grade < 31 then
self.grade_points = 0
self.grade = self.grade + 1
end
end
end
local tetris_requirements = { [0] = 2, 2, 2, 2, 2, 1, 1, 1, 1, 1 }
function MarathonA2Game:qualifiesForMRoll()
if not self.clear then return false end
-- tetris requirements
for section = 0, 9 do
if self.section_tetrises[section] < tetris_requirements[section] then
return false
end
end
-- section time requirements
local section_average = 0
for section = 0, 4 do
section_average = section_average + self.section_times[section]
if self.section_times[section] > frameTime(1,05) then
return false
end
end
-- section time average requirements
if self.section_times[5] > section_average / 5 then
return false
end
for section = 6, 9 do
if self.section_times[section] > self.section_times[section - 1] + 120 then
return false
end
end
if self.grade < 17 or self.frames > frameTime(8,45) then
return false
end
return true
end
function MarathonA2Game:getLetterGrade()
local grade = grade_conversion[self.grade]
if grade < 9 then
return tostring(9 - grade)
elseif grade < 18 then
return "S" .. tostring(grade - 8)
elseif grade == 18 then
return "M"
else
return "GM"
end
end
MarathonA2Game.rollOpacityFunction = function(age)
if age < 240 then return 1
elseif age > 300 then return 0
else return 1 - (age - 240) / 60 end
end
MarathonA2Game.mRollOpacityFunction = function(age)
if age > 4 then return 0
else return 1 - age / 4 end
end
function MarathonA2Game:drawGrid(ruleset)
if self.clear and not (self.completed or self.game_over) then
if self:qualifiesForMRoll() then
self.grid:drawInvisible(self.mRollOpacityFunction)
else
self.grid:drawInvisible(self.rollOpacityFunction)
end
else
self.grid:draw()
if self.piece ~= nil and self.level < 100 then
self:drawGhostPiece(ruleset)
end
end
end
function MarathonA2Game: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("SCORE", 240, 200, 40, "left")
love.graphics.printf("LEVEL", 240, 320, 40, "left")
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
love.graphics.printf(self.score, 240, 220, 90, "left")
love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
end
function MarathonA2Game:getHighscoreData()
return {
grade = grade_conversion[self.grade],
score = self.score,
level = self.level,
frames = self.frames,
}
end
function MarathonA2Game:getSectionEndLevel()
if self.level >= 900 then return 999
else return math.floor(self.level / 100 + 1) * 100 end
end
function MarathonA2Game:getBackground()
return math.floor(self.level / 100)
end
return MarathonA2Game

119
tetris/modes/credits_a3.lua Normal file
View File

@@ -0,0 +1,119 @@
require 'funcs'
local IntervalTrainingMode = require 'tetris.modes.interval_training'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local CreditsA3Game = IntervalTrainingMode:extend()
CreditsA3Game.name = "Credits A3"
CreditsA3Game.hash = "CreditsA3"
CreditsA3Game.tagline = "How consistently can you clear the Ti M-roll?"
function CreditsA3Game:new()
CreditsA3Game.super:new()
self.section_time_limit = 3238
self.norm = 0
self.section = 0
end
function CreditsA3Game:advanceOneFrame(inputs, ruleset)
if self.frames == 0 then
switchBGM("credit_roll", "gm3")
end
if self.roll_frames > 0 then
self.roll_frames = self.roll_frames - 1
if self.roll_frames == 0 then
-- reset
self.norm = 0
self.frames = 0
self.grid:clear()
switchBGM("credit_roll", "gm3")
self:initializeOrHold(inputs, ruleset)
else
return false
end
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
if self:getSectionTime() >= self.section_time_limit then
self.norm = self.norm + 16
self.piece = nil
if self.norm >= 60 then
self.section = self.section + 1
self.roll_frames = 150
else
self.game_over = true
switchBGM(nil)
end
end
end
return true
end
function CreditsA3Game:onPieceEnter()
-- do nothing
end
function CreditsA3Game:onLineClear(cleared_row_count)
if not self.clear then
self.norm = self.norm + (cleared_row_count == 4 and 10 or cleared_row_count)
end
end
CreditsA3Game.rollOpacityFunction = function(age)
if age > 4 then return 0
else return 1 - age / 4 end
end
function CreditsA3Game:drawGrid(ruleset)
if not self.game_over and self.roll_frames < 30 then
self.grid:drawInvisible(self.rollOpacityFunction)
else
self.grid:draw()
end
end
function CreditsA3Game:drawScoringInfo()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_2)
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
st(self.prev_inputs)
)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("TIME LEFT", 240, 250, 80, "left")
love.graphics.printf("NORM", 240, 320, 40, "left")
self:drawSectionTimesWithSplits(self.section)
love.graphics.setFont(font_3x5_3)
-- draw time left, flash red if necessary
local time_left = self.section_time_limit - math.max(self:getSectionTime(), 0)
if not self.game_over and not self.clear and time_left < sp(0,10) and time_left % 4 < 2 then
if self.norm >= 44 then
love.graphics.setColor(0.3, 1, 0.3, 1) -- flash green if goal has been cleared
else
love.graphics.setColor(1, 0.3, 0.3, 1) -- otherwise flash red
end
end
love.graphics.printf(formatTime(time_left), 240, 270, 160, "left")
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(self.norm, 240, 340, 40, "right")
if self.game_over or self.roll_frames > 0 then
love.graphics.printf("60", 240, 370, 40, "right")
else
love.graphics.printf("44", 240, 370, 40, "right")
end
end
function CreditsA3Game:getBackground()
return self.section
end
return CreditsA3Game

View File

@@ -159,7 +159,7 @@ function DemonModeGame:updateSectionTimes(old_level, new_level)
end
elseif old_level < 100 then
-- If section time is under cutoff, skip to level 500.
if self.frames < frameTime(1,00) then
if self.frames < sp(1,00) then
self.level = 500
self.grade = 5
self.section_tries = 0
@@ -226,7 +226,7 @@ function DemonModeGame:drawScoringInfo()
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
strTrueValues(self.prev_inputs)
st(self.prev_inputs)
)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("GRADE", 240, 120, 40, "left")

View File

@@ -50,6 +50,13 @@ function GameMode:new()
self.secondary_section_times = { [0] = 0 }
end
function GameMode:initialize()
-- after all the variables are initialized, run initialization procedures
for i = 1, 30 do
table.insert(self.next_queue, self:getNextPiece(ruleset))
end
end
function GameMode:getARR() return 1 end
function GameMode:getDropSpeed() return 1 end
function GameMode:getARE() return 25 end
@@ -68,7 +75,6 @@ end
function GameMode:initialize(ruleset)
-- generate next queue
self:new()
for i = 1, self.next_queue_length do
table.insert(self.next_queue, self:getNextPiece(ruleset))
end
@@ -85,10 +91,20 @@ function GameMode:update(inputs, ruleset)
if self.completed then return end
-- advance one frame
if self:advanceOneFrame(inputs) == false then return end
if self:advanceOneFrame(inputs, ruleset) == false then return end
self:chargeDAS(inputs, self:getDasLimit(), self.getARR())
-- set attempt flags
if inputs["left"] or inputs["right"] then self:onAttemptPieceMove(self.piece) end
if
inputs["rotate_left"] or inputs["rotate_right"] or
inputs["rotate_left2"] or inputs["rotate_right2"] or
inputs["rotate_180"]
then
self:onAttemptPieceRotate(self.piece)
end
if self.piece == nil then
self:processDelays(inputs, ruleset)
else
@@ -146,6 +162,10 @@ function GameMode:update(inputs, ruleset)
local cleared_row_count = self.grid:getClearedRowCount()
if self.big_mode then
cleared_row_count = cleared_row_count / 2
end
self:onPieceLock(self.piece, cleared_row_count)
self:updateScore(self.level, self.drop_bonus, cleared_row_count)
@@ -188,6 +208,8 @@ end
-- event functions
function GameMode:whilePieceActive() end
function GameMode:onAttemptPieceMove(piece) end
function GameMode:onAttemptPieceRotate(piece) end
function GameMode:onPieceLock(piece, cleared_row_count) end
function GameMode:onLineClear(cleared_row_count) end
function GameMode:onPieceEnter() end
@@ -205,30 +227,66 @@ function GameMode:onGameOver()
switchBGM(nil)
end
function GameMode:chargeDAS(inputs)
if inputs[self.das.direction] == true then
local das_frames = self.das.frames + 1
if das_frames >= self:getDasLimit() then
if self.das.direction == "left" then
self.move = (self:getARR() == 0 and "speed" or "") .. "left"
self.das.frames = self:getDasLimit() - self:getARR()
elseif self.das.direction == "right" then
self.move = (self:getARR() == 0 and "speed" or "") .. "right"
self.das.frames = self:getDasLimit() - self:getARR()
end
else
self.move = "none"
self.das.frames = das_frames
-- DAS functions
function GameMode:startRightDAS()
self.move = "right"
self.das = { direction = "right", frames = 0 }
if self:getDasLimit() == 0 then
self:continueDAS()
end
end
function GameMode:startLeftDAS()
self.move = "left"
self.das = { direction = "left", frames = 0 }
if self:getDasLimit() == 0 then
self:continueDAS()
end
end
function GameMode:continueDAS()
local das_frames = self.das.frames + 1
if das_frames >= self:getDasLimit() then
if self.das.direction == "left" then
self.move = (self:getARR() == 0 and "speed" or "") .. "left"
self.das.frames = self:getDasLimit() - self:getARR()
elseif self.das.direction == "right" then
self.move = (self:getARR() == 0 and "speed" or "") .. "right"
self.das.frames = self:getDasLimit() - self:getARR()
end
elseif inputs["right"] == true then
self.move = "right"
self.das = { direction = "right", frames = 0 }
elseif inputs["left"] == true then
self.move = "left"
self.das = { direction = "left", frames = 0 }
else
self.move = "none"
self.das = { direction = "none", frames = -1 }
self.das.frames = das_frames
end
end
function GameMode:stopDAS()
self.move = "none"
self.das = { direction = "none", frames = -1 }
end
function GameMode:chargeDAS(inputs)
if config["das_last_key"] then
if inputs["right"] == true and self.das.direction ~= "right" and not self.prev_inputs["right"] then
self:startRightDAS()
elseif inputs["left"] == true and self.das.direction ~= "left" and not self.prev_inputs["left"] then
self:startLeftDAS()
elseif inputs[self.das.direction] == true then
self:continueDAS()
else
self:stopDAS()
end
else -- default behaviour, das first key pressed
if inputs[self.das.direction] == true then
self:continueDAS()
elseif inputs["right"] == true then
self:startRightDAS()
elseif inputs["left"] == true then
self:startLeftDAS()
else
self:stopDAS()
end
end
end
@@ -342,8 +400,8 @@ end
function GameMode:drawNextQueue(ruleset)
function drawPiece(piece, skin, offsets, pos_x, pos_y)
for index, offset in pairs(offsets) do
local x = offset.x + ruleset.spawn_positions[piece].x
local y = offset.y + 4.7
local x = ruleset.spawn_positions[piece].x + offset.x
local y = ruleset.spawn_positions[piece].y + offset.y
love.graphics.draw(blocks[skin][piece], pos_x+x*16, pos_y+y*16)
end
end
@@ -386,8 +444,7 @@ function GameMode:drawScoringInfo()
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
strTrueValues(self.prev_inputs) ..
self.drop_bonus
st(self.prev_inputs)
)
love.graphics.setFont(font_8x11)
@@ -397,7 +454,7 @@ end
function GameMode:drawSectionTimes(current_section)
local section_x = 530
for section, time in pairs(self.section_times) do
for section, time in ipairs(self.section_times) do
if section > 0 then
love.graphics.printf(formatTime(time), section_x, 40 + 20 * section, 90, "left")
end
@@ -406,7 +463,7 @@ function GameMode:drawSectionTimes(current_section)
love.graphics.printf(formatTime(self.frames - self.section_start_time), section_x, 40 + 20 * current_section, 90, "left")
end
function GameMode:drawSectionTimesWithSecondary(current_section)
function GameMode:drawSectionTimesWithSecondary(current_section, section_colour_function)
local section_x = 530
local section_secondary_x = 440
@@ -417,6 +474,9 @@ function GameMode:drawSectionTimesWithSecondary(current_section)
end
for section, time in pairs(self.secondary_section_times) do
if self.section_colour_function then
love.graphics.setColor(self:section_colour_function(section))
end
if section > 0 then
love.graphics.printf(formatTime(time), section_secondary_x, 40 + 20 * section, 90, "left")
end

View File

@@ -28,19 +28,19 @@ function IntervalTrainingGame:new()
end
function IntervalTrainingGame:getARE()
return 4
return 6
end
function IntervalTrainingGame:getLineARE()
return 4
return 6
end
function IntervalTrainingGame:getDasLimit()
return 6
return 7
end
function IntervalTrainingGame:getLineClearDelay()
return 6
return 4
end
function IntervalTrainingGame:getLockDelay()
@@ -120,7 +120,7 @@ function IntervalTrainingGame:drawScoringInfo()
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
strTrueValues(self.prev_inputs)
st(self.prev_inputs)
)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("TIME LEFT", 240, 250, 80, "left")
@@ -130,16 +130,15 @@ function IntervalTrainingGame:drawScoringInfo()
self:drawSectionTimesWithSplits(current_section)
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.level, 240, 340, 40, "right")
-- draw time left, flash red if necessary
local time_left = self.section_time_limit - math.max(self:getSectionTime(), 0)
if not self.game_over and not self.clear and time_left < frameTime(0,10) and time_left % 4 < 2 then
if not self.game_over and not self.clear and time_left < sp(0,10) and time_left % 4 < 2 then
love.graphics.setColor(1, 0.3, 0.3, 1)
end
love.graphics.printf(formatTime(time_left), 240, 270, 160, "left")
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
end

View File

@@ -1,190 +0,0 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local KonohaRandomizer = require 'tetris.randomizers.bag_konoha'
local KonohaGame = GameMode:extend()
KonohaGame.name = "All Clear A4"
KonohaGame.hash = "AllClearA4"
KonohaGame.tagline = "Get as many bravos as you can under the time limit!"
function KonohaGame:new()
KonohaGame.super:new()
self.randomizer = KonohaRandomizer()
self.bravos = 0
self.last_bonus_amount = 0
self.last_bonus_display_time = 0
self.time_limit = 10800
self.big_mode = true
self.enable_hold = true
self.next_queue_length = 3
end
function KonohaGame:getARE()
if self.level < 300 then return 30
elseif self.level < 400 then return 25
elseif self.level < 500 then return 20
elseif self.level < 600 then return 17
elseif self.level < 800 then return 15
elseif self.level < 900 then return 13
elseif self.level < 1000 then return 10
elseif self.level < 1300 then return 8
else return 6 end
end
function KonohaGame:getLineARE()
return self:getARE()
end
function KonohaGame:getDasLimit()
if self.level < 500 then return 10
elseif self.level < 800 then return 9
elseif self.level < 1000 then return 8
else return 7 end
end
function KonohaGame:getLineClearDelay()
if self.level < 200 then return 14
elseif self.level < 500 then return 9
elseif self.level < 800 then return 8
elseif self.level < 1000 then return 7
else return 6 end
end
function KonohaGame:getLockDelay()
if self.level < 500 then return 30
elseif self.level < 600 then return 25
elseif self.level < 700 then return 23
elseif self.level < 800 then return 20
elseif self.level < 900 then return 17
elseif self.level < 1000 then return 15
elseif self.level < 1200 then return 13
elseif self.level < 1300 then return 10
else return 8 end
end
function KonohaGame:getGravity()
if (self.level < 30) then return 4/256
elseif (self.level < 35) then return 8/256
elseif (self.level < 40) then return 12/256
elseif (self.level < 50) then return 16/256
elseif (self.level < 60) then return 32/256
elseif (self.level < 70) then return 48/256
elseif (self.level < 80) then return 64/256
elseif (self.level < 90) then return 128/256
elseif (self.level < 100) then return 192/256
elseif (self.level < 120) then return 1
elseif (self.level < 140) then return 2
elseif (self.level < 160) then return 3
elseif (self.level < 170) then return 4
elseif (self.level < 200) then return 5
else return 20 end
end
function KonohaGame:getSection()
return math.floor(level / 100) + 1
end
function KonohaGame:getSectionEndLevel()
return math.floor(self.level / 100 + 1) * 100
end
function KonohaGame:advanceOneFrame()
if self.ready_frames == 0 then
self.time_limit = self.time_limit - 1
self.frames = self.frames + 1
end
if self.time_limit <= 0 then
self.game_over = true
end
self.last_bonus_display_time = self.last_bonus_display_time - 1
end
function KonohaGame:onPieceEnter()
if (self.level % 100 ~= 99) and self.frames ~= 0 then
self.level = self.level + 1
end
end
function KonohaGame:drawGrid(ruleset)
self.grid:draw()
if self.piece ~= nil and self.level < 100 then
self:drawGhostPiece(ruleset)
end
end
local cleared_row_levels = {2, 4, 6, 12}
local bravo_bonus = {300, 480, 660, 900}
local non_bravo_bonus = {0, 0, 20, 40}
local bravo_ot_bonus = {0, 60, 120, 180}
function KonohaGame:onLineClear(cleared_row_count)
local oldtime = self.time_limit
self.level = self.level + cleared_row_levels[cleared_row_count / 2]
if self.grid:checkForBravo(cleared_row_count) then
self.bravos = self.bravos + 1
if self.level < 1000 then self.time_limit = self.time_limit + bravo_bonus[cleared_row_count / 2]
else self.time_limit = self.time_limit + bravo_ot_bonus[cleared_row_count / 2]
end
if self.bravos == 11 then self.randomizer.allowrepeat = true end
elseif self.level < 1000 then
self.time_limit = self.time_limit + non_bravo_bonus[cleared_row_count / 2]
end
local bonus = self.time_limit - oldtime
if bonus > 0 then
self.last_bonus_amount = bonus
self.last_bonus_display_time = 120
end
end
function KonohaGame:getBackground()
return math.floor(self.level / 100)
end
function KonohaGame: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("TIME LIMIT", 240, 120, 120, "left")
love.graphics.printf("BRAVOS", 240, 200, 50, "left")
love.graphics.printf("LEVEL", 240, 320, 40, "left")
love.graphics.setFont(font_3x5_3)
if not self.game_over and self.time_limit < frameTime(0,10) and self.time_limit % 4 < 2 then
love.graphics.setColor(1, 0.3, 0.3, 1)
end
love.graphics.printf(formatTime(self.time_limit), 240, 140, 120, "right")
love.graphics.setColor(1, 1, 1, 1)
if self.last_bonus_display_time > 0 then
love.graphics.printf("+"..formatTime(self.last_bonus_amount), 240, 160, 120, "right")
end
love.graphics.printf(self.bravos, 240, 220, 90, "left")
love.graphics.printf(self.level, 240, 340, 50, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 50, "right")
love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
end
function KonohaGame:getHighscoreData()
return {
bravos = self.bravos,
level = self.level,
frames = self.frames,
}
end
return KonohaGame

View File

@@ -151,11 +151,11 @@ function Marathon2020Game:advanceOneFrame()
end
local cool_cutoffs = {
frameTime(0,45,00), frameTime(0,41,50), frameTime(0,38,50), frameTime(0,35,00), frameTime(0,32,50),
frameTime(0,29,20), frameTime(0,27,20), frameTime(0,24,80), frameTime(0,22,80), frameTime(0,20,60),
frameTime(0,19,60), frameTime(0,19,40), frameTime(0,19,40), frameTime(0,18,40), frameTime(0,18,20),
frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20),
frameTime(0,15,20)
sp(0,45,00), sp(0,41,50), sp(0,38,50), sp(0,35,00), sp(0,32,50),
sp(0,29,20), sp(0,27,20), sp(0,24,80), sp(0,22,80), sp(0,20,60),
sp(0,19,60), sp(0,19,40), sp(0,19,40), sp(0,18,40), sp(0,18,20),
sp(0,16,20), sp(0,16,20), sp(0,16,20), sp(0,16,20), sp(0,16,20),
sp(0,15,20)
}
local levels_for_cleared_rows = { 1, 2, 4, 6 }
@@ -284,11 +284,11 @@ function Marathon2020Game:sectionPassed(old_level, new_level)
end
function Marathon2020Game:checkTorikan(section)
if section == 5 and self.frames < frameTime(6,00,00) then self.torikan_passed[500] = true end
if section == 9 and self.frames < frameTime(8,30,00) then self.torikan_passed[900] = true end
if section == 10 and self.frames < frameTime(8,45,00) then self.torikan_passed[1000] = true end
if section == 15 and self.frames < frameTime(11,30,00) then self.torikan_passed[1500] = true end
if section == 19 and self.frames < frameTime(13,15,00) then self.torikan_passed[1900] = true end
if section == 5 and self.frames < sp(6,00,00) then self.torikan_passed[500] = true end
if section == 9 and self.frames < sp(8,30,00) then self.torikan_passed[900] = true end
if section == 10 and self.frames < sp(8,45,00) then self.torikan_passed[1000] = true end
if section == 15 and self.frames < sp(11,30,00) then self.torikan_passed[1500] = true end
if section == 19 and self.frames < sp(13,15,00) then self.torikan_passed[1900] = true end
end
function Marathon2020Game:checkClear(level)
@@ -323,9 +323,9 @@ function Marathon2020Game:checkClear(level)
end
function Marathon2020Game:updateSectionTimes(old_level, new_level)
function sectionCool()
function sectionCool(section)
self.section_cool_count = self.section_cool_count + 1
self.delay_level = math.min(20, self.delay_level + 1)
if section < 10 then self.delay_level = math.min(20, self.delay_level + 1) end
table.insert(self.section_status, "cool")
end
@@ -343,7 +343,7 @@ function Marathon2020Game:updateSectionTimes(old_level, new_level)
table.insert(self.section_times, section_time)
self.section_start_time = self.frames
if section > 4 then self.delay_level = math.min(20, self.delay_level + 1) end
if section > 5 then self.delay_level = math.min(20, self.delay_level + 1) end
self:checkTorikan(section)
self:checkClear(new_level)
@@ -352,11 +352,11 @@ function Marathon2020Game:updateSectionTimes(old_level, new_level)
self.secondary_section_times[section] < self.secondary_section_times[section - 1] + 120 and
self.secondary_section_times[section] < cool_cutoffs[section]
) then
sectionCool()
sectionCool(section)
elseif self.section_status[section - 1] == "cool" then
table.insert(self.section_status, "none")
elseif section <= 19 and self.secondary_section_times[section] < cool_cutoffs[section] then
sectionCool()
sectionCool(section)
else
table.insert(self.section_status, "none")
end
@@ -417,6 +417,14 @@ function Marathon2020Game:drawGrid()
end
end
function Marathon2020Game:sectionColourFunction(section)
if self.section_status[section] == "cool" then
return { 0, 1, 0, 1 }
else
return { 1, 1, 1, 1 }
end
end
function Marathon2020Game:drawScoringInfo()
Marathon2020Game.super.drawScoringInfo(self)
@@ -428,7 +436,7 @@ function Marathon2020Game:drawScoringInfo()
love.graphics.printf("GRADE PTS.", text_x, 200, 90, "left")
love.graphics.printf("LEVEL", text_x, 320, 40, "left")
self:drawSectionTimesWithSecondary(current_section)
self:drawSectionTimesWithSecondary(current_section, self.sectionColourFunction)
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self:getTotalGrade(), text_x, 120, 90, "left")

View File

@@ -12,7 +12,6 @@ MarathonA1Game.hash = "MarathonA1"
MarathonA1Game.tagline = "Can you score enough points to reach the title of Grand Master?"
function MarathonA1Game:new()
MarathonA1Game.super:new()
@@ -23,11 +22,6 @@ function MarathonA1Game:new()
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()
@@ -136,7 +130,8 @@ 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 self.level == 999 then
if new_level == 999 then
self.level = 999
self.clear = true
else
self.level = new_level
@@ -145,6 +140,7 @@ function MarathonA1Game:onLineClear(cleared_row_count)
end
function MarathonA1Game:updateScore(level, drop_bonus, cleared_lines)
if self.clear then return end
if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2
self.score = self.score + (
@@ -160,15 +156,15 @@ 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
if self.score > 12000 and self.frames <= sp(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
if self.score > 40000 and self.frames <= sp(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
if self.score > 126000 and self.frames <= sp(13,30) then
self.gm_conditions["level900"] = true
end
end
@@ -189,17 +185,13 @@ function MarathonA1Game:drawScoringInfo()
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
strTrueValues(self.prev_inputs)
st(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
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.score, 240, 220, 90, "left")
@@ -211,10 +203,7 @@ function MarathonA1Game:drawScoringInfo()
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
love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
end

View File

@@ -27,12 +27,6 @@ function MarathonA2Game:new()
self.section_times = { [0] = 0 }
self.section_tetrises = { [0] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
self.SGnames = {
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"GM"
}
self.randomizer = History6RollsRandomizer()
self.lock_drop = false
@@ -266,7 +260,7 @@ function MarathonA2Game:qualifiesForMRoll()
local section_average = 0
for section = 0, 4 do
section_average = section_average + self.section_times[section]
if self.section_times[section] > frameTime(1,05) then
if self.section_times[section] > sp(1,05) then
return false
end
end
@@ -279,7 +273,7 @@ function MarathonA2Game:qualifiesForMRoll()
return false
end
end
if self.grade < 17 or self.frames > frameTime(9,30) then
if self.grade < 17 or self.frames > sp(8,45) then
return false
end
return true
@@ -331,25 +325,18 @@ function MarathonA2Game:drawScoringInfo()
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
strTrueValues(self.prev_inputs)
st(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("LEVEL", 240, 320, 40, "left")
local sg = self.grid:checkSecretGrade()
if sg >= 5 then
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
end
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
love.graphics.printf(self.score, 240, 220, 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
love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")

View File

@@ -32,19 +32,10 @@ function MarathonA3Game:new()
self.randomizer = History6RollsRandomizer()
self.SGnames = {
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"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 MarathonA3Game:getARE()
@@ -169,13 +160,13 @@ function MarathonA3Game:onLineClear(cleared_row_count)
end
local cool_cutoffs = {
frameTime(0,52), frameTime(0,52), frameTime(0,49), frameTime(0,45), frameTime(0,45),
frameTime(0,42), frameTime(0,42), frameTime(0,38), frameTime(0,38),
sp(0,52), sp(0,52), sp(0,49), sp(0,45), sp(0,45),
sp(0,42), sp(0,42), sp(0,38), sp(0,38),
}
local regret_cutoffs = {
frameTime(0,90), frameTime(0,75), frameTime(0,75), frameTime(0,68), frameTime(0,60),
frameTime(0,60), frameTime(0,50), frameTime(0,50), frameTime(0,50), frameTime(0,50),
sp(0,90), sp(0,75), sp(0,75), sp(0,68), sp(0,60),
sp(0,60), sp(0,50), sp(0,50), sp(0,50), sp(0,50),
}
function MarathonA3Game:updateSectionTimes(old_level, new_level)
@@ -191,23 +182,17 @@ function MarathonA3Game:updateSectionTimes(old_level, new_level)
if section_time > regret_cutoffs[section] then
self.section_cool_grade = self.section_cool_grade - 1
table.insert(self.section_status, "regret")
self.coolregret_message = "REGRET!!"
self.coolregret_timer = 300
elseif section <= 9 and self.section_status[section - 1] == "cool" and
self.section_70_times[section] < self.section_70_times[section - 1] + 120 then
self.section_cool_grade = self.section_cool_grade + 1
self.speed_level = self.speed_level + 100
table.insert(self.section_status, "cool")
self.coolregret_message = "COOL!!"
self.coolregret_timer = 300
elseif self.section_status[section - 1] == "cool" then
table.insert(self.section_status, "none")
elseif section <= 9 and self.section_70_times[section] < cool_cutoffs[section] then
self.section_cool_grade = self.section_cool_grade + 1
self.speed_level = self.speed_level + 100
table.insert(self.section_status, "cool")
self.coolregret_message = "COOL!!"
self.coolregret_timer = 300
else
table.insert(self.section_status, "none")
end
@@ -387,16 +372,12 @@ function MarathonA3Game:drawScoringInfo()
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
strTrueValues(self.prev_inputs)
st(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("LEVEL", 240, 320, 40, "left")
local sg = self.grid:checkSecretGrade()
if sg >= 5 then
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
end
-- draw section time data
current_section = math.floor(self.level / 100) + 1
@@ -424,21 +405,13 @@ function MarathonA3Game:drawScoringInfo()
end
love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left")
if(self.coolregret_timer > 0) then
love.graphics.printf(self.coolregret_message, 64, 400, 160, "center")
self.coolregret_timer = self.coolregret_timer - 1
end
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.score, 240, 220, 90, "left")
love.graphics.printf(self:getLetterGrade(), 240, 140, 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
love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
end

View File

@@ -0,0 +1,169 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local MarathonAXGame = GameMode:extend()
MarathonAXGame.name = "Marathon AX"
MarathonAXGame.hash = "MarathonAX"
MarathonAXGame.tagline = "Can you clear the time hurdles when the game goes this fast?"
function MarathonAXGame:new()
MarathonAXGame.super:new()
self.roll_frames = 0
self.randomizer = History6RollsRandomizer()
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_clear = false
self.lock_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function MarathonAXGame:getSectionTimeLimit()
if self.lines < 20 then return 7200
else return 5400 end
end
function MarathonAXGame:getARE()
return 27
end
function MarathonAXGame:getLineARE()
return self:getARE()
end
function MarathonAXGame:getDasLimit()
return 15
end
function MarathonAXGame:getLineClearDelay()
return 40
end
function MarathonAXGame:getLockDelay()
return 30
end
function MarathonAXGame:getGravity()
if self.lines < 10 then return 4/256
elseif self.lines < 20 then return 12/256
elseif self.lines < 30 then return 48/256
elseif self.lines < 40 then return 72/256
elseif self.lines < 50 then return 96/256
elseif self.lines < 60 then return 1/2
elseif self.lines < 70 then return 1
elseif self.lines < 80 then return 3/2
elseif self.lines < 90 then return 2
elseif self.lines < 100 then return 3
elseif self.lines < 110 then return 4
elseif self.lines < 120 then return 5
else return 20 end
end
function MarathonAXGame:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames < 0 then
return false
elseif self.roll_frames > 2968 then
self.completed = true
end
elseif self.ready_frames == 0 then
if not self.section_clear then
self.frames = self.frames + 1
end
if self:getSectionTime() >= self:getSectionTimeLimit() then
self.game_over = true
end
end
return true
end
function MarathonAXGame:onLineClear(cleared_row_count)
if not self.clear then
local new_lines = self.lines + cleared_row_count
self:updateSectionTimes(self.lines, new_lines)
self.lines = math.min(new_lines, 150)
if self.lines == 150 then
self.clear = true
self.roll_frames = -150
end
end
end
function MarathonAXGame:getSectionTime()
return self.frames - self.section_start_time
end
function MarathonAXGame:updateSectionTimes(old_lines, new_lines)
if math.floor(old_lines / 10) < math.floor(new_lines / 10) then
-- record new section
table.insert(self.section_times, self:getSectionTime())
self.section_start_time = self.frames
self.section_clear = true
end
end
function MarathonAXGame:onPieceEnter()
self.section_clear = false
end
function MarathonAXGame:drawGrid(ruleset)
self.grid:draw()
end
function MarathonAXGame:getHighscoreData()
return {
lines = self.lines,
frames = self.frames,
}
end
function MarathonAXGame:drawScoringInfo()
MarathonAXGame.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 .. " " ..
st(self.prev_inputs)
)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("TIME LEFT", 240, 250, 80, "left")
love.graphics.printf("LINES", 240, 320, 40, "left")
local current_section = math.floor(self.lines / 10) + 1
self:drawSectionTimesWithSplits(current_section)
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.lines, 240, 340, 40, "right")
love.graphics.printf(self.clear and self.lines or self:getSectionEndLines(), 240, 370, 40, "right")
-- draw time left, flash red if necessary
local time_left = self:getSectionTimeLimit() - math.max(self:getSectionTime(), 0)
if not self.game_over and not self.clear and time_left < sp(0,10) and time_left % 4 < 2 then
love.graphics.setColor(1, 0.3, 0.3, 1)
end
love.graphics.printf(formatTime(time_left), 240, 270, 160, "left")
love.graphics.setColor(1, 1, 1, 1)
end
function MarathonAXGame:getSectionEndLines()
return math.floor(self.lines / 10 + 1) * 10
end
function MarathonAXGame:getBackground()
return math.floor(self.lines / 10)
end
return MarathonAXGame

View File

@@ -0,0 +1,42 @@
require 'funcs'
local MarathonAX = require 'tetris.modes.marathon_ax'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local MarathonAX2Game = MarathonAX:extend()
MarathonAX2Game.name = "Marathon AX2"
MarathonAX2Game.hash = "MarathonAX2"
MarathonAX2Game.tagline = "Can you clear the time hurdles when the game goes this fast?"
function MarathonAX2Game:new()
MarathonAX2Game.super:new()
self.roll_frames = 0
self.randomizer = History6RollsRandomizer()
self.section_time_limit = 3600
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_clear = false
self.lock_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function MarathonAX2Game:getGravity()
if self.lines < 10 then return 84/256
elseif self.lines < 20 then return 1/2
elseif self.lines < 30 then return 1
elseif self.lines < 40 then return 2
elseif self.lines < 50 then return 3
elseif self.lines < 60 then return 4
elseif self.lines < 70 then return 5
else return 20 end
end
return MarathonAX2Game

View File

@@ -0,0 +1,35 @@
require 'funcs'
local MarathonAX = require 'tetris.modes.marathon_ax'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local MarathonAX3Game = MarathonAX:extend()
MarathonAX3Game.name = "Marathon AX3"
MarathonAX3Game.hash = "MarathonAX3"
MarathonAX3Game.tagline = "Can you clear the time hurdles when the game goes this fast?"
function MarathonAX3Game:new()
MarathonAX3Game.super:new()
self.roll_frames = 0
self.randomizer = History6RollsRandomizer()
self.section_time_limit = 3600
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_clear = false
self.lock_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function MarathonAX3Game:getGravity()
return 20
end
return MarathonAX3Game

View File

@@ -1,174 +0,0 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local Bag7NoSZOStartRandomizer = require 'tetris.randomizers.bag7noSZOstart'
local MarathonAX4Game = GameMode:extend()
MarathonAX4Game.name = "Marathon AX4"
MarathonAX4Game.hash = "MarathonAX4"
MarathonAX4Game.tagline = "Can you clear the time hurdles when the game goes this fast?"
function MarathonAX4Game:new()
MarathonAX4Game.super:new()
self.roll_frames = 0
self.randomizer = Bag7NoSZOStartRandomizer()
self.section_time_limit = 3600
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_clear = false
self.lock_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function MarathonAX4Game:getARE()
if self.lines < 10 then return 18
elseif self.lines < 40 then return 14
elseif self.lines < 60 then return 12
elseif self.lines < 70 then return 10
elseif self.lines < 80 then return 8
elseif self.lines < 90 then return 7
else return 6 end
end
function MarathonAX4Game:getLineARE()
return self:getARE()
end
function MarathonAX4Game:getDasLimit()
if self.lines < 20 then return 10
elseif self.lines < 50 then return 9
elseif self.lines < 70 then return 8
else return 7 end
end
function MarathonAX4Game:getLineClearDelay()
if self.lines < 10 then return 14
elseif self.lines < 30 then return 9
else return 5 end
end
function MarathonAX4Game:getLockDelay()
if self.lines < 10 then return 28
elseif self.lines < 20 then return 24
elseif self.lines < 30 then return 22
elseif self.lines < 40 then return 20
elseif self.lines < 50 then return 18
elseif self.lines < 70 then return 14
else return 13 end
end
function MarathonAX4Game:getGravity()
return 20
end
function MarathonAX4Game:getSection()
return math.floor(level / 100) + 1
end
function MarathonAX4Game:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames < 0 then
return false
elseif self.roll_frames > 2968 then
self.completed = true
end
elseif self.ready_frames == 0 then
if not self.section_clear then
self.frames = self.frames + 1
end
if self:getSectionTime() >= self.section_time_limit then
self.game_over = true
end
end
return true
end
function MarathonAX4Game:onLineClear(cleared_row_count)
if not self.clear then
local new_lines = self.lines + cleared_row_count
self:updateSectionTimes(self.lines, new_lines)
self.lines = math.min(new_lines, 150)
if self.lines == 150 then
self.clear = true
self.roll_frames = -150
end
end
end
function MarathonAX4Game:getSectionTime()
return self.frames - self.section_start_time
end
function MarathonAX4Game:updateSectionTimes(old_lines, new_lines)
if math.floor(old_lines / 10) < math.floor(new_lines / 10) then
-- record new section
table.insert(self.section_times, self:getSectionTime())
self.section_start_time = self.frames
self.section_clear = true
end
end
function MarathonAX4Game:onPieceEnter()
self.section_clear = false
end
function MarathonAX4Game:drawGrid(ruleset)
self.grid:draw()
end
function MarathonAX4Game:getHighscoreData()
return {
lines = self.lines,
frames = self.frames,
}
end
function MarathonAX4Game:drawScoringInfo()
MarathonAX4Game.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("TIME LEFT", 240, 250, 80, "left")
love.graphics.printf("LINES", 240, 320, 40, "left")
local current_section = math.floor(self.lines / 10) + 1
self:drawSectionTimesWithSplits(current_section)
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.lines, 240, 340, 40, "right")
love.graphics.printf(self.clear and self.lines or self:getSectionEndLines(), 240, 370, 40, "right")
-- draw time left, flash red if necessary
local time_left = self.section_time_limit - math.max(self:getSectionTime(), 0)
if not self.game_over and not self.clear and time_left < frameTime(0,10) and time_left % 4 < 2 then
love.graphics.setColor(1, 0.3, 0.3, 1)
end
love.graphics.printf(formatTime(time_left), 240, 270, 160, "left")
love.graphics.setColor(1, 1, 1, 1)
end
function MarathonAX4Game:getSectionEndLines()
return math.floor(self.lines / 10 + 1) * 10
end
function MarathonAX4Game:getBackground()
return math.floor(self.lines / 10)
end
return MarathonAX4Game

View File

@@ -156,7 +156,7 @@ function MarathonC89Game:drawScoringInfo()
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
strTrueValues(self.prev_inputs)
st(self.prev_inputs)
)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("LINES", 240, 120, 40, "left")

View File

@@ -0,0 +1,133 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local MarathonWCBGame = GameMode:extend()
MarathonWCBGame.name = "Marathon WCB"
MarathonWCBGame.hash = "MarathonWCB"
MarathonWCBGame.tagline = "When all the pieces slip right to their destinations... can you keep up?"
function MarathonWCBGame:new()
MarathonWCBGame.super:new()
self.pieces = 0
self.randomizer = History6RollsRandomizer()
self.lock_drop = true
self.lock_hard_drop = true
self.instant_hard_drop = true
self.instant_soft_drop = true
self.enable_hold = false
self.next_queue_length = 3
self.piece_is_active = false
end
function MarathonWCBGame:getDropSpeed()
return 20
end
function MarathonWCBGame:getARR()
return 0
end
function MarathonWCBGame:getARE()
return 0
end
function MarathonWCBGame:getLineARE()
return 0
end
function MarathonWCBGame:getDasLimit()
return 0
end
function MarathonWCBGame:getLineClearDelay()
return 0
end
function MarathonWCBGame:getLockDelay()
return math.huge
end
function MarathonWCBGame:getGravity()
return self.piece_is_active and 20 or 0
end
function MarathonWCBGame:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames > 150 then
self.completed = true
end
return false
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
end
return true
end
function MarathonWCBGame:onAttemptPieceMove()
if self.piece ~= nil then
-- don't let the piece move before it's finished dropping
self.piece:dropToBottom(self.grid)
end
self.piece_is_active = true
end
function MarathonWCBGame:onAttemptPieceRotate()
self.piece_is_active = true
end
function MarathonWCBGame:onPieceLock()
self.piece_is_active = false
self.pieces = self.pieces + 1
end
function MarathonWCBGame:onLineClear(cleared_row_count)
if not self.clear then
self.lines = self.lines + cleared_row_count
end
end
function MarathonWCBGame:drawGrid(ruleset)
self.grid:draw()
if self.piece ~= nil then
self:drawGhostPiece(ruleset)
end
end
function MarathonWCBGame:getHighscoreData()
return {
pieces = self.pieces,
frames = self.frames,
}
end
function MarathonWCBGame:drawScoringInfo()
MarathonWCBGame.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("NEXT", 64, 40, 40, "left")
love.graphics.printf("lines", text_x, 160, 80, "left")
love.graphics.printf("pieces", text_x, 220, 80, "left")
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.lines, text_x, 180, 80, "left")
love.graphics.printf(self.pieces, text_x, 240, 80, "left")
end
function MarathonWCBGame:getBackground()
return (math.floor(self.pieces / 50) % 20)
end
return MarathonWCBGame

View File

@@ -61,15 +61,15 @@ function PhantomManiaGame:getGravity()
end
function PhantomManiaGame:hitTorikan(old_level, new_level)
if old_level < 300 and new_level >= 300 and self.frames > frameTime(2,28) then
if old_level < 300 and new_level >= 300 and self.frames > sp(2,28) then
self.level = 300
return true
end
if old_level < 500 and new_level >= 500 and self.frames > frameTime(3,38) then
if old_level < 500 and new_level >= 500 and self.frames > sp(3,38) then
self.level = 500
return true
end
if old_level < 800 and new_level >= 800 and self.frames > frameTime(5,23) then
if old_level < 800 and new_level >= 800 and self.frames > sp(5,23) then
self.level = 800
return true
end

View File

@@ -86,19 +86,19 @@ function PhantomMania2Game:getNextPiece(ruleset)
end
function PhantomMania2Game:hitTorikan(old_level, new_level)
if old_level < 300 and new_level >= 300 and self.frames > frameTime(2,02) then
if old_level < 300 and new_level >= 300 and self.frames > sp(2,02) then
self.level = 300
return true
end
if old_level < 500 and new_level >= 500 and self.frames > frameTime(3,03) then
if old_level < 500 and new_level >= 500 and self.frames > sp(3,03) then
self.level = 500
return true
end
if old_level < 800 and new_level >= 800 and self.frames > frameTime(4,45) then
if old_level < 800 and new_level >= 800 and self.frames > sp(4,45) then
self.level = 800
return true
end
if old_level < 1000 and new_level >= 1000 and self.frames > frameTime(5,38) then
if old_level < 1000 and new_level >= 1000 and self.frames > sp(5,38) then
self.level = 1000
return true
end
@@ -156,7 +156,7 @@ function PhantomMania2Game:onLineClear(cleared_row_count)
end
self:advanceBottomRow(-cleared_row_count)
else
self.roll_points = self.roll_points + cleared_row_points[cleared_row_count / 2]
self.roll_points = self.roll_points + cleared_row_points[cleared_row_count]
if self.roll_points >= 100 then
self.roll_points = self.roll_points - 100
self.grade = self.grade + 1
@@ -188,15 +188,15 @@ end
local cool_cutoffs = {
frameTime(0,36), frameTime(0,36), frameTime(0,36), frameTime(0,36), frameTime(0,36),
frameTime(0,30), frameTime(0,30), frameTime(0,30), frameTime(0,30), frameTime(0,30),
frameTime(0,27), frameTime(0,27), frameTime(0,27),
sp(0,36), sp(0,36), sp(0,36), sp(0,36), sp(0,36),
sp(0,30), sp(0,30), sp(0,30), sp(0,30), sp(0,30),
sp(0,27), sp(0,27), sp(0,27),
}
local regret_cutoffs = {
frameTime(0,50), frameTime(0,50), frameTime(0,50), frameTime(0,50), frameTime(0,50),
frameTime(0,40), frameTime(0,40), frameTime(0,40), frameTime(0,40), frameTime(0,40),
frameTime(0,35), frameTime(0,35), frameTime(0,35),
sp(0,50), sp(0,50), sp(0,50), sp(0,50), sp(0,50),
sp(0,40), sp(0,40), sp(0,40), sp(0,40), sp(0,40),
sp(0,35), sp(0,35), sp(0,35),
}
function PhantomMania2Game:updateSectionTimes(old_level, new_level)
@@ -234,7 +234,7 @@ PhantomMania2Game.garbageOpacityFunction = function(age)
end
function PhantomMania2Game:drawGrid()
if not (self.game_over or (self.clear and self.level < 1300)) then
if not (self.game_over) then
self.grid:drawInvisible(self.rollOpacityFunction, self.garbageOpacityFunction)
else
self.grid:draw()

View File

@@ -25,7 +25,7 @@ function Race40Game:new()
self.lock_drop = true
self.lock_hard_drop = true
self.instant_hard_drop = true
self.instant_soft_drop = false
self.instant_soft_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
@@ -39,11 +39,11 @@ function Race40Game:getARR()
end
function Race40Game:getARE()
return 0
return 4
end
function Race40Game:getLineARE()
return self:getARE()
return 2
end
function Race40Game:getDasLimit()
@@ -51,15 +51,15 @@ function Race40Game:getDasLimit()
end
function Race40Game:getLineClearDelay()
return 0
return 2
end
function Race40Game:getLockDelay()
return 15
return 30
end
function Race40Game:getGravity()
return 1/64
return 20
end
function Race40Game:advanceOneFrame()

View File

@@ -102,15 +102,15 @@ function Survival2020Game:getNextPiece(ruleset)
end
function Survival2020Game:hitTorikan(old_level, new_level)
if old_level < 500 and new_level >= 500 and self.frames > frameTime(3,00) then
if old_level < 500 and new_level >= 500 and self.frames > sp(3,00) then
self.level = 500
return true
end
if old_level < 1000 and new_level >= 1000 and self.frames > frameTime(5,00) then
if old_level < 1000 and new_level >= 1000 and self.frames > sp(5,00) then
self.level = 1000
return true
end
if old_level < 1500 and new_level >= 1500 and self.frames > frameTime(7,00) then
if old_level < 1500 and new_level >= 1500 and self.frames > sp(7,00) then
self.level = 1500
return true
end
@@ -193,7 +193,7 @@ function Survival2020Game:updateSectionTimes(old_level, new_level)
section_time = self.frames - self.section_start_time
table.insert(self.section_times, section_time)
self.section_start_time = self.frames
if section_time <= frameTime(0,30) then
if section_time <= sp(0,30) then
self.grade = self.grade + 2
else
self.grade = self.grade + 1

View File

@@ -25,12 +25,6 @@ function SurvivalA1Game:new()
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.lock_drop = false
@@ -131,15 +125,15 @@ end
function SurvivalA1Game: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
if self.score > 12000 and self.frames <= sp(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
if self.score > 40000 and self.frames <= sp(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
if self.score > 126000 and self.frames <= sp(13,30) then
self.gm_conditions["level900"] = true
end
end
@@ -157,17 +151,13 @@ function SurvivalA1Game:drawScoringInfo()
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
strTrueValues(self.prev_inputs)
st(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
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.score, 240, 220, 90, "left")
@@ -179,9 +169,6 @@ function SurvivalA1Game:drawScoringInfo()
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
love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")

View File

@@ -19,12 +19,6 @@ function SurvivalA2Game:new()
self.roll_frames = 0
self.combo = 1
self.randomizer = History6RollsRandomizer()
self.SGnames = {
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"GM"
}
self.lock_drop = true
end
@@ -68,7 +62,7 @@ function SurvivalA2Game:getGravity()
end
function SurvivalA2Game:hitTorikan(old_level, new_level)
if old_level < 500 and new_level >= 500 and self.frames > frameTime(3,25) then
if old_level < 500 and new_level >= 500 and self.frames > sp(3,25) then
self.level = 500
return true
end
@@ -138,25 +132,18 @@ function SurvivalA2Game:drawScoringInfo()
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
strTrueValues(self.prev_inputs)
st(self.prev_inputs)
)
love.graphics.printf("NEXT", 64, 40, 40, "left")
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
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.score, text_x, 220, 90, "left")
love.graphics.printf(self:getLetterGrade(), text_x, 140, 90, "left")
love.graphics.printf(self.level, text_x, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), text_x, 370, 40, "right")
if sg >= 5 then
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
end
end
function SurvivalA2Game:getSectionEndLevel()

View File

@@ -24,19 +24,10 @@ function SurvivalA3Game:new()
self.roll_frames = 0
self.combo = 1
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.enable_hold = true
self.next_queue_length = 3
self.coolregret_message = "COOL!!"
self.coolregret_timer = 0
end
function SurvivalA3Game:getARE()
@@ -95,11 +86,11 @@ function SurvivalA3Game:getNextPiece(ruleset)
end
function SurvivalA3Game:hitTorikan(old_level, new_level)
if old_level < 500 and new_level >= 500 and self.frames > frameTime(2,28) then
if old_level < 500 and new_level >= 500 and self.frames > sp(2,28) then
self.level = 500
return true
end
if old_level < 1000 and new_level >= 1000 and self.frames > frameTime(4,56) then
if old_level < 1000 and new_level >= 1000 and self.frames > sp(4,56) then
self.level = 1000
return true
end
@@ -141,10 +132,10 @@ function SurvivalA3Game:onLineClear(cleared_row_count)
if new_level >= 1300 or self:hitTorikan(self.level, new_level) then
if new_level >= 1300 then
self.level = 1300
self.big_mode = true
end
self.clear = true
self.grid:clear()
self.big_mode = true
self.roll_frames = -150
else
self.level = math.min(new_level, 1300)
@@ -177,11 +168,8 @@ function SurvivalA3Game:updateSectionTimes(old_level, new_level)
section_time = self.frames - self.section_start_time
table.insert(self.section_times, section_time)
self.section_start_time = self.frames
if section_time <= frameTime(1,00) then
if section_time <= sp(1,00) then
self.grade = self.grade + 1
else
self.coolregret_message = "REGRET!!"
self.coolregret_timer = 300
end
end
end
@@ -219,15 +207,6 @@ function SurvivalA3Game:drawScoringInfo()
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:drawSectionTimesWithSplits(current_section)
@@ -241,9 +220,6 @@ function SurvivalA3Game:drawScoringInfo()
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
function SurvivalA3Game:getBackground()

View File

@@ -0,0 +1,76 @@
require 'funcs'
local MarathonAX = require 'tetris.modes.marathon_ax'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local SurvivalAXGame = MarathonAX:extend()
SurvivalAXGame.name = "Survival AX"
SurvivalAXGame.hash = "SurvivalAX"
SurvivalAXGame.tagline = "Can you clear the time hurdles when the game goes this fast?"
function SurvivalAXGame:new()
SurvivalAXGame.super:new()
self.roll_frames = 0
self.randomizer = History6RollsRandomizer()
self.section_time_limit = 3600
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_clear = false
self.lock_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function SurvivalAXGame:getSectionTimeLimit()
return 3600
end
function SurvivalAXGame:getARE()
if self.lines < 10 then return 18
elseif self.lines < 40 then return 14
elseif self.lines < 60 then return 12
elseif self.lines < 70 then return 10
elseif self.lines < 80 then return 8
elseif self.lines < 90 then return 7
else return 6 end
end
function SurvivalAXGame:getLineARE()
return self:getARE()
end
function SurvivalAXGame:getDasLimit()
if self.lines < 20 then return 10
elseif self.lines < 50 then return 9
elseif self.lines < 70 then return 8
else return 7 end
end
function SurvivalAXGame:getLineClearDelay()
if self.lines < 10 then return 14
elseif self.lines < 30 then return 8
else return 5 end
end
function SurvivalAXGame:getLockDelay()
if self.lines < 10 then return 30
elseif self.lines < 20 then return 26
elseif self.lines < 30 then return 24
elseif self.lines < 40 then return 22
elseif self.lines < 50 then return 20
elseif self.lines < 70 then return 16
else return 15 end
end
function SurvivalAXGame:getGravity()
return 20
end
return SurvivalAXGame

View File

@@ -0,0 +1,59 @@
require 'funcs'
local MarathonAX2 = require 'tetris.modes.marathon_ax2'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local SurvivalAX2Game = MarathonAX2:extend()
SurvivalAX2Game.name = "Survival AX2"
SurvivalAX2Game.hash = "SurvivalAX2"
SurvivalAX2Game.tagline = "Can you clear the time hurdles when the game goes this fast?"
function SurvivalAX2Game:new()
SurvivalAX2Game.super:new()
self.roll_frames = 0
self.randomizer = History6RollsRandomizer()
self.section_time_limit = 3600
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_clear = false
self.lock_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function SurvivalAX2Game:getSectionTimeLimit()
return 3600
end
function SurvivalAX2Game:getARE()
return 6
end
function SurvivalAX2Game:getLineARE()
return self:getARE()
end
function SurvivalAX2Game:getDasLimit()
return 7
end
function SurvivalAX2Game:getLineClearDelay()
return 5
end
function SurvivalAX2Game:getLockDelay()
return 15
end
function SurvivalAX2Game:getGravity()
return 20
end
return SurvivalAX2Game

View File

@@ -1,17 +0,0 @@
local Randomizer = require 'tetris.randomizers.randomizer'
local Bag5Randomizer = Randomizer:extend()
function Bag5Randomizer:initialize()
self.bag = {"I", "J", "L", "O", "T"}
end
function Bag5Randomizer:generatePiece()
if next(self.bag) == nil then
self.bag = {"I", "J", "L", "O", "T"}
end
local x = math.random(table.getn(self.bag))
return table.remove(self.bag, x)
end
return Bag5Randomizer

View File

@@ -1,24 +0,0 @@
local Randomizer = require 'tetris.randomizers.randomizer'
local Bag5AltRandomizer = Randomizer:extend()
function Bag5AltRandomizer:initialize()
self.bag = {"I", "J", "L", "O", "T"}
self.prev = nil
end
function Bag5AltRandomizer:generatePiece()
if next(self.bag) == nil then
self.bag = {"I", "J", "L", "O", "T"}
end
local x = math.random(table.getn(self.bag))
local temp = table.remove(self.bag, x)
if temp == self.prev then
local y = math.random(table.getn(self.bag))
temp = table.remove(self.bag, y)
end
self.prev = temp
return temp
end
return Bag5AltRandomizer

View File

@@ -1,31 +0,0 @@
local Randomizer = require 'tetris.randomizers.randomizer'
local Bag7NoSZOStartRandomizer = Randomizer:extend()
function Bag7NoSZOStartRandomizer:shuffleBag()
local b = self.bag
local ln = #b
for i = 1, ln do
local j = math.random(i, ln)
b[i], b[j] = b[j], b[i]
end
end
local function isnotSZO(x) return not(x == "S" or x == "Z" or x == "O") end
function Bag7NoSZOStartRandomizer:initialize()
self.bag = {"I", "J", "L", "O", "S", "T", "Z"}
repeat
self:shuffleBag()
until isnotSZO(self.bag[7])
end
function Bag7NoSZOStartRandomizer:generatePiece()
if #self.bag == 0 then
self.bag = {"I", "J", "L", "O", "S", "T", "Z"}
self:shuffleBag()
end
return table.remove(self.bag)
end
return Bag7NoSZOStartRandomizer

View File

@@ -1,28 +0,0 @@
local Randomizer = require 'tetris.randomizers.randomizer'
local BagKonoha = Randomizer:extend()
function BagKonoha:initialize()
self.bag = {"I", "J", "L", "O", "T"}
self.prev = nil
self.allowrepeat = false
self.generated = 0
end
function BagKonoha:generatePiece()
self.generated = self.generated + 1
if #self.bag == 0 then
self.bag = {"I", "J", "L", "O", "T"}
end
local x = math.random(#self.bag)
local temp = table.remove(self.bag, x)
if temp == self.prev and not self.allowrepeat then
local y = math.random(#self.bag)
table.insert(self.bag, temp) -- should insert at the end of the bag, bag[y] doesnt change
temp = table.remove(self.bag, y)
end
self.prev = temp
return temp
end
return BagKonoha

View File

@@ -3,23 +3,17 @@ local Randomizer = require 'tetris.randomizers.randomizer'
local History4RollsRandomizer = Randomizer:extend()
function History4RollsRandomizer:initialize()
self.history = {"Z", "Z", "Z", "Z"}
self.first = true
self.history = {"Z", "S", "Z", "S"}
end
function History4RollsRandomizer:generatePiece()
if self.first then
self.first = false
return self:updateHistory(({"L", "J", "I", "T"})[math.random(4)])
else
local shapes = {"I", "J", "L", "O", "S", "T", "Z"}
for i = 1, 4 do
local x = math.random(7)
if not inHistory(shapes[x], self.history) or i == 4 then
return self:updateHistory(shapes[x])
end
end
end
local shapes = {"I", "J", "L", "O", "S", "T", "Z"}
for i = 1, 4 do
local x = math.random(7)
if not inHistory(shapes[x], self.history) or i == 4 then
return self:updateHistory(shapes[x])
end
end
end
function History4RollsRandomizer:updateHistory(shape)

View File

@@ -4,22 +4,16 @@ local History6RollsRandomizer = Randomizer:extend()
function History6RollsRandomizer:initialize()
self.history = {"Z", "S", "Z", "S"}
self.first = true
end
function History6RollsRandomizer:generatePiece()
if self.first then
self.first = false
return self:updateHistory(({"L", "J", "I", "T"})[math.random(4)])
else
local shapes = {"I", "J", "L", "O", "S", "T", "Z"}
for i = 1, 6 do
local x = math.random(7)
if not inHistory(shapes[x], self.history) or i == 6 then
return self:updateHistory(shapes[x])
end
end
end
local shapes = {"I", "J", "L", "O", "S", "T", "Z"}
for i = 1, 6 do
local x = math.random(7)
if not inHistory(shapes[x], self.history) or i == 6 then
return self:updateHistory(shapes[x])
end
end
end
function History6RollsRandomizer:updateHistory(shape)

View File

@@ -1,70 +1,38 @@
local Randomizer = require 'tetris.randomizers.randomizer'
local History6Rolls35PoolRandomizer = Randomizer:extend()
local History6RollsRandomizer = Randomizer:extend()
function History6Rolls35PoolRandomizer:initialize()
self.first = true
function History6RollsRandomizer:initialize()
self.history = {"Z", "S", "Z", "S"}
self.pool = {
"I", "I", "I", "I", "I",
"T", "T", "T", "T", "T",
"L", "L", "L", "L", "L",
"J", "J", "J", "J", "J",
"S", "S", "S", "S", "S",
"Z", "Z", "Z", "Z", "Z",
"O", "O", "O", "O", "O",
self.bag_counts = {
I = 5, J = 5, L = 5, O = 5, S = 3, T = 5, Z = 3
}
self.droughts = {
I = 0,
T = 0,
L = 0,
J = 0,
S = 0,
Z = 0,
O = 0,
}
end
function History6Rolls35PoolRandomizer:generatePiece()
local index, x
if self.first then
local prevent = {"S", "Z", "O"}
repeat
index = math.random(#self.pool)
x = self.pool[index]
until not inHistory(x, prevent)
self.first = false
else
for i = 1, 6 do
index = math.random(#self.pool)
x = self.pool[index]
if not inHistory(x, self.history) or i == 6 then
break
end
end
end
self.pool[index] = self:updateHistory(x)
return x
function History6RollsRandomizer:getBagPiece(n)
for shape, count in pairs(self.bag_counts) do
n = n - count
if n <= 0 then
return shape
end
end
end
function History6Rolls35PoolRandomizer:updateHistory(shape)
table.remove(self.history, 1)
function History6RollsRandomizer:generatePiece()
for i = 1, 6 do
local x = self:getBagPiece(math.random(31))
if not inHistory(x, self.history) or i == 6 then
return self:updateHistory(x)
end
end
end
function History6RollsRandomizer:updateHistory(shape)
self.bag_counts[shape] = self.bag_counts[shape] - 1
local replaced_piece = table.remove(self.history, 1)
table.insert(self.history, shape)
local highdrought
local highdroughtcount = 0
for k, v in pairs(self.droughts) do
if k == shape then
self.droughts[k] = 0
else
self.droughts[k] = v + 1
if v >= highdroughtcount then
highdrought = k
highdroughtcount = v
end
end
end
return highdrought
self.bag_counts[replaced_piece] = self.bag_counts[replaced_piece] + 1
return shape
end
function inHistory(piece, history)
@@ -76,4 +44,4 @@ function inHistory(piece, history)
return false
end
return History6Rolls35PoolRandomizer
return History6RollsRandomizer

View File

@@ -4,10 +4,15 @@ local Randomizer = Object:extend()
function Randomizer:new()
self:initialize()
self.next_queue = {}
for i = 1, 30 do
table.insert(self.next_queue, self:generatePiece())
end
end
function Randomizer:nextPiece()
return self:generatePiece()
table.insert(self.next_queue, self:generatePiece())
return table.remove(self.next_queue, 1)
end
function Randomizer:initialize()

View File

@@ -81,19 +81,11 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
piece.shape == "J" or piece.shape == "T" or piece.shape == "L"
) and (
piece.rotation == 0 or piece.rotation == 2
) then
local offsets = new_piece:getBlockOffsets()
table.sort(offsets, function(A, B) return A.y < B.y or A.y == B.y and A.x < B.y end)
for index, offset in pairs(offsets) do
if grid:isOccupied(piece.position.x + offset.x, piece.position.y + offset.y) then
if offset.x == 0 then
return
else
break
end
end
end
end
) and (
grid:isOccupied(piece.position.x, piece.position.y) or
grid:isOccupied(piece.position.x, piece.position.y - 1) or
grid:isOccupied(piece.position.x, piece.position.y - 2)
) then return end
-- kick right, kick left
if (grid:canPlacePiece(new_piece:withOffset({x=1, y=0}))) then

166
tetris/rulesets/arika_ace.lua Executable file → Normal file
View File

@@ -1,151 +1,14 @@
local Piece = require 'tetris.components.piece'
local Ruleset = require 'tetris.rulesets.ruleset'
local ArikaTI = require 'tetris.rulesets.arika_ti'
local ARS = Ruleset:extend()
local ARS = ArikaTI:extend()
ARS.name = "ACE-ARS"
ARS.hash = "ArikaACE"
ARS.spawn_positions = {
I = { x=5, y=2 },
J = { x=4, y=3 },
L = { x=4, y=3 },
O = { x=5, y=3 },
S = { x=4, y=3 },
T = { x=4, y=3 },
Z = { x=4, y=3 },
}
ARS.big_spawn_positions = {
I = { x=2, y=0 },
J = { x=2, y=1 },
L = { x=2, y=1 },
O = { x=2, y=1 },
S = { x=2, y=1 },
T = { x=2, y=1 },
Z = { x=2, y=1 },
}
ARS.block_offsets = {
I={
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
},
J={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1} },
{ {x=0, y=-1}, {x=1, y=-2}, {x=0, y=-2}, {x=0, y=0} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=1, y=0} },
{ {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=-1, y=0} },
},
L={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1} },
{ {x=0, y=-2}, {x=0, y=-1}, {x=1, y=0}, {x=0, y=0} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=-1, y=0} },
{ {x=0, y=-1}, {x=-1, y=-2}, {x=0, y=-2}, {x=0, y=0} },
},
O={
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
},
S={
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=-1, y=-2}, {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0} },
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=-1, y=-2}, {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0} },
},
T={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} },
{ {x=0, y=-1}, {x=0, y=0}, {x=1, y=-1}, {x=0, y=-2} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=0, y=0} },
{ {x=0, y=-1}, {x=0, y=0}, {x=-1, y=-1}, {x=0, y=-2} },
},
Z={
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=0}, {x=0, y=0} },
{ {x=0, y=-1}, {x=0, y=0}, {x=1, y=-2}, {x=1, y=-1} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=0}, {x=0, y=0} },
{ {x=0, y=-1}, {x=0, y=0}, {x=1, y=-2}, {x=1, y=-1} },
}
}
-- Component functions.
function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
-- O doesn't kick
if (piece.shape == "O") then return end
-- center column rule
if (
piece.shape == "J" or piece.shape == "T" or piece.shape == "L"
) and (
piece.rotation == 0 or piece.rotation == 2
) then
local offsets = new_piece:getBlockOffsets()
table.sort(offsets, function(A, B) return A.y < B.y or A.y == B.y and A.x < B.y end)
for index, offset in pairs(offsets) do
if grid:isOccupied(piece.position.x + offset.x, piece.position.y + offset.y) then
if offset.x == 0 then
return
else
break
end
end
end
end
if piece.shape == "I" then
-- special kick rules for I
if new_piece.rotation == 0 or new_piece.rotation == 2 then
-- kick right, right2, left
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=2, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=2, y=0})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
self:onPieceRotate(piece, grid)
end
elseif piece:isDropBlocked(grid) and (new_piece.rotation == 1 or new_piece.rotation == 3) and piece.floorkick == 0 then
-- kick up, up2
if grid:canPlacePiece(new_piece:withOffset({x=0, y=-1})) then
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
self:onPieceRotate(piece, grid)
piece.floorkick = 1
elseif grid:canPlacePiece(new_piece:withOffset({x=0, y=-2})) then
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-2})
self:onPieceRotate(piece, grid)
piece.floorkick = 1
end
end
else
-- kick right, kick left
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
elseif piece.shape == "T"
and new_piece.rotation == 1
and piece.floorkick == 0
and grid:canPlacePiece(new_piece:withOffset({x=0, y=-1}))
then
-- T floorkick
piece.floorkick = piece.floorkick + 1
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
end
end
end
ARS.name = "Ace-ARS"
ARS.hash = "ArikaAce"
function ARS:onPieceCreate(piece, grid)
piece.floorkick = 0
piece.manipulations = 0
piece.rotate_counter = 0
piece.move_counter = 0
end
function ARS:onPieceDrop(piece, grid)
@@ -155,24 +18,15 @@ end
function ARS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 127 then
piece.move_counter = piece.move_counter + 1
if piece.move_counter >= 128 then
piece.locked = true
end
end
end
function ARS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 127 then
piece.locked = true
end
end
self:onPieceMove(piece, grid)
end
function ARS:get180RotationValue() return config["reverse_rotate"] and 1 or 3 end
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
return ARS
return ARS

View File

@@ -1,177 +0,0 @@
local Piece = require 'tetris.components.piece'
local Ruleset = require 'tetris.rulesets.ruleset'
local SRS = Ruleset:extend()
SRS.name = "ACE-SRS"
SRS.hash = "ACE Standard"
SRS.enable_IRS_wallkicks = true
SRS.spawn_positions = {
I = { x=5, y=2 },
J = { x=4, y=3 },
L = { x=4, y=3 },
O = { x=5, y=3 },
S = { x=4, y=3 },
T = { x=4, y=3 },
Z = { x=4, y=3 },
}
SRS.big_spawn_positions = {
I = { x=2, y=0 },
J = { x=2, y=1 },
L = { x=2, y=1 },
O = { x=2, y=1 },
S = { x=2, y=1 },
T = { x=2, y=1 },
Z = { x=2, y=1 },
}
SRS.block_offsets = {
I={
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
{ {x=0, y=1}, {x=-1, y=1}, {x=-2, y=1}, {x=1, y=1} },
{ {x=-1, y=0}, {x=-1, y=-1}, {x=-1, y=1}, {x=-1, y=2} },
},
J={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1} , {x=1, y=-1} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=1, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=1} },
},
L={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=1} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=-1, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=-1} },
},
O={
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
},
S={
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=1, y=1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=-1} },
{ {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0} },
{ {x=-1, y=-1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=1} },
},
T={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=0} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=0, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=0} },
},
Z={
{ {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=1, y=0} },
{ {x=1, y=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1} },
{ {x=1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=-1, y=1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1} },
}
}
SRS.wallkicks_3x3 = {
[0]={
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
[2]={{x=0, y=1}, {x=0, y=-1}},
[3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
},
[1]={
[0]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}},
[2]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}},
[3]={{x=0, y=1}, {x=0, y=-1}},
},
[2]={
[0]={{x=0, y=1}, {x=0, y=-1}},
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
[3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
},
[3]={
[0]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
[1]={{x=0, y=1}, {x=0, y=-1}},
[2]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
},
};
SRS.wallkicks_line = {
[0]={
[1]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
[2]={},
[3]={{x=-1, y=0}, {x=2, y=0}, {x=-1, y=-2}, {x=2, y=1}},
},
[1]={
[0]={{x=2, y=0}, {x=-1, y=0}, {x=2, y=-1}, {x=-1, y=2}},
[2]={{x=-1, y=0}, {x=2, y=0}, {x=-1, y=-2}, {x=2, y=1}},
[3]={{x=0, y=1}, {x=0, y=-1}, {x=0, y=2}, {x=0, y=-2}},
},
[2]={
[0]={},
[1]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}},
[3]={{x=2, y=0}, {x=-1, y=0}, {x=2, y=-1}, {x=-1, y=2}},
},
[3]={
[0]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}},
[1]={{x=0, y=1}, {x=0, y=-1}, {x=0, y=2}, {x=0, y=-2}},
[2]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
},
};
-- Component functions.
function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
local kicks
if piece.shape == "O" then
return
elseif piece.shape == "I" then
kicks = SRS.wallkicks_line[piece.rotation][new_piece.rotation]
else
kicks = SRS.wallkicks_3x3[piece.rotation][new_piece.rotation]
end
assert(piece.rotation ~= new_piece.rotation)
for idx, offset in pairs(kicks) do
kicked_piece = new_piece:withOffset(offset)
if grid:canPlacePiece(kicked_piece) then
piece:setRelativeRotation(rot_dir)
piece:setOffset(offset)
self:onPieceRotate(piece, grid)
return
end
end
end
function SRS:onPieceCreate(piece, grid)
piece.manipulations = 0
end
function SRS:onPieceDrop(piece, grid)
piece.lock_delay = 0 -- step reset
end
function SRS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 127 then
piece.locked = true
end
end
end
function SRS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 127 then
piece.locked = true
end
end
end
return SRS

View File

@@ -84,19 +84,11 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
piece.shape == "J" or piece.shape == "T" or piece.shape == "L"
) and (
piece.rotation == 0 or piece.rotation == 2
) then
local offsets = new_piece:getBlockOffsets()
table.sort(offsets, function(A, B) return A.y < B.y or A.y == B.y and A.x < B.y end)
for index, offset in pairs(offsets) do
if grid:isOccupied(piece.position.x + offset.x, piece.position.y + offset.y) then
if offset.x == 0 then
return
else
break
end
end
end
end
) and (
grid:isOccupied(piece.position.x, piece.position.y) or
grid:isOccupied(piece.position.x, piece.position.y - 1) or
grid:isOccupied(piece.position.x, piece.position.y - 2)
) then return end
if piece.shape == "I" then
-- special kick rules for I
@@ -124,21 +116,13 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
piece.floorkick = 1
end
end
else
-- kick right, kick left
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
elseif piece.shape == "T"
and new_piece.rotation == 1
and piece.floorkick == 0
and grid:canPlacePiece(new_piece:withOffset({x=0, y=-1}))
then
-- T floorkick
piece.floorkick = piece.floorkick + 1
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
end
elseif piece.shape ~= "I" then
-- kick right, kick left
if (grid:canPlacePiece(new_piece:withOffset({x=1, y=0}))) then
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
elseif (grid:canPlacePiece(new_piece:withOffset({x=-1, y=0}))) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
end
end
end

View File

@@ -398,6 +398,7 @@ function CRS:onPieceDrop(piece, grid)
end
function CRS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
piece.move_counter = piece.move_counter + 1
if piece.move_counter >= 24 then
@@ -407,6 +408,7 @@ function CRS:onPieceMove(piece, grid)
end
function CRS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
piece.rotate_counter = piece.rotate_counter + 1
if piece.rotate_counter >= 12 then
@@ -415,6 +417,4 @@ function CRS:onPieceRotate(piece, grid)
end
end
function CRS:getDefaultOrientation() return 1 end -- downward facing pieces by default
return CRS

View File

@@ -56,16 +56,16 @@ function Ruleset:attemptWallkicks(piece, new_piece, rot_dir, grid)
-- do nothing in default ruleset
end
function Ruleset:movePiece(piece, grid, move)
function Ruleset:movePiece(piece, grid, move, instant)
local x = piece.position.x
if move == "left" then
piece:moveInGrid({x=-1, y=0}, 1, grid)
elseif move == "speedleft" then
piece:moveInGrid({x=-1, y=0}, 10, grid)
piece:moveInGrid({x=-1, y=0}, 1, grid, false)
elseif move == "right" then
piece:moveInGrid({x=1, y=0}, 1, grid)
piece:moveInGrid({x=1, y=0}, 1, grid, false)
elseif move == "speedleft" then
piece:moveInGrid({x=-1, y=0}, 10, grid, instant)
elseif move == "speedright" then
piece:moveInGrid({x=1, y=0}, 10, grid)
piece:moveInGrid({x=1, y=0}, 10, grid, instant)
end
if piece.position.x ~= x then
self:onPieceMove(piece, grid)
@@ -138,7 +138,7 @@ function Ruleset:processPiece(
hard_drop_enabled, additive_gravity
)
self:rotatePiece(inputs, piece, grid, prev_inputs, false)
self:movePiece(piece, grid, move)
self:movePiece(piece, grid, move, gravity >= 20)
self:dropPiece(
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
hard_drop_enabled, additive_gravity

View File

@@ -3,29 +3,29 @@ local Ruleset = require 'tetris.rulesets.ruleset'
local SRS = Ruleset:extend()
SRS.name = "Guideline SRS"
SRS.name = "SRS"
SRS.hash = "Standard"
SRS.enable_IRS_wallkicks = true
SRS.spawn_positions = {
I = { x=5, y=2 },
J = { x=4, y=3 },
L = { x=4, y=3 },
O = { x=5, y=3 },
S = { x=4, y=3 },
T = { x=4, y=3 },
Z = { x=4, y=3 },
I = { x=5, y=4 },
J = { x=4, y=5 },
L = { x=4, y=5 },
O = { x=5, y=5 },
S = { x=4, y=5 },
T = { x=4, y=5 },
Z = { x=4, y=5 },
}
SRS.big_spawn_positions = {
I = { x=2, y=0 },
J = { x=2, y=1 },
L = { x=2, y=1 },
O = { x=2, y=1 },
S = { x=2, y=1 },
T = { x=2, y=1 },
Z = { x=2, y=1 },
I = { x=2, y=2 },
J = { x=2, y=3 },
L = { x=2, y=3 },
O = { x=2, y=3 },
S = { x=2, y=3 },
T = { x=2, y=3 },
Z = { x=2, y=3 },
}
SRS.block_offsets = {

View File

@@ -0,0 +1,30 @@
local Standard = require 'tetris.rulesets.standard'
local SRS = Standard:extend()
SRS.name = "Ti-SRS"
SRS.hash = "StandardTI"
function SRS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
piece.move_counter = piece.move_counter + 1
if piece.move_counter >= 10 then
piece.locked = true
end
end
end
function SRS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
if piece:isDropBlocked(grid) then
piece.rotate_counter = piece.rotate_counter + 1
if piece.rotate_counter >= 8 then
piece.locked = true
end
end
end
function SRS:get180RotationValue() return config["reverse_rotate"] and 1 or 3 end
return SRS

View File

@@ -1,177 +0,0 @@
local Piece = require 'tetris.components.piece'
local Ruleset = require 'tetris.rulesets.ruleset'
local SRS = Ruleset:extend()
SRS.name = "Ti-World"
SRS.hash = "Bad I-kicks"
SRS.enable_IRS_wallkicks = true
SRS.spawn_positions = {
I = { x=5, y=2 },
J = { x=4, y=3 },
L = { x=4, y=3 },
O = { x=5, y=3 },
S = { x=4, y=3 },
T = { x=4, y=3 },
Z = { x=4, y=3 },
}
SRS.big_spawn_positions = {
I = { x=2, y=0 },
J = { x=2, y=1 },
L = { x=2, y=1 },
O = { x=2, y=1 },
S = { x=2, y=1 },
T = { x=2, y=1 },
Z = { x=2, y=1 },
}
SRS.block_offsets = {
I={
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
{ {x=0, y=1}, {x=-1, y=1}, {x=-2, y=1}, {x=1, y=1} },
{ {x=-1, y=0}, {x=-1, y=-1}, {x=-1, y=1}, {x=-1, y=2} },
},
J={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1} , {x=1, y=-1} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=1, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=1} },
},
L={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=1} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=-1, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=-1} },
},
O={
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
},
S={
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=1, y=1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=-1} },
{ {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0} },
{ {x=-1, y=-1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=1} },
},
T={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=0} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=0, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=0} },
},
Z={
{ {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=1, y=0} },
{ {x=1, y=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1} },
{ {x=1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=-1, y=1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1} },
}
}
SRS.wallkicks_3x3 = {
[0]={
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
[2]={{x=0, y=1}, {x=0, y=-1}},
[3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
},
[1]={
[0]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}},
[2]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}},
[3]={{x=0, y=1}, {x=0, y=-1}},
},
[2]={
[0]={{x=0, y=1}, {x=0, y=-1}},
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
[3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
},
[3]={
[0]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
[1]={{x=0, y=1}, {x=0, y=-1}},
[2]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
},
};
SRS.wallkicks_line = {
[0]={
[1]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
[2]={},
[3]={{x=-1, y=0}, {x=2, y=0}, {x=-1, y=-2}, {x=2, y=1}},
},
[1]={
[0]={{x=2, y=0}, {x=-1, y=0}, {x=2, y=-1}, {x=-1, y=2}},
[2]={{x=-1, y=0}, {x=2, y=0}, {x=-1, y=-2}, {x=2, y=1}},
[3]={{x=0, y=1}, {x=0, y=-1}, {x=0, y=2}, {x=0, y=-2}},
},
[2]={
[0]={},
[1]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}},
[3]={{x=2, y=0}, {x=-1, y=0}, {x=2, y=-1}, {x=-1, y=2}},
},
[3]={
[0]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}},
[1]={{x=0, y=1}, {x=0, y=-1}, {x=0, y=2}, {x=0, y=-2}},
[2]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
},
};
-- Component functions.
function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
local kicks
if piece.shape == "O" then
return
elseif piece.shape == "I" then
kicks = SRS.wallkicks_line[piece.rotation][new_piece.rotation]
else
kicks = SRS.wallkicks_3x3[piece.rotation][new_piece.rotation]
end
assert(piece.rotation ~= new_piece.rotation)
for idx, offset in pairs(kicks) do
kicked_piece = new_piece:withOffset(offset)
if grid:canPlacePiece(kicked_piece) then
piece:setRelativeRotation(rot_dir)
piece:setOffset(offset)
self:onPieceRotate(piece, grid)
return
end
end
end
function SRS:onPieceCreate(piece, grid)
piece.manipulations = 0
end
function SRS:onPieceDrop(piece, grid)
piece.lock_delay = 0 -- step reset
end
function SRS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 8 then
piece.locked = true
end
end
end
function SRS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 8 then
piece.locked = true
end
end
end
return SRS