generic tetromino game

Lua modding API reference

Board

An array of rows, top to bottom. Each column is two bits, from left to right going from least to most significant. 00 is empty, and 01, 10, and 11 are different tiles. Note that the indices are from 0 to 19; i.e. they don't start at 1.

type Board = [0...19]integer

Board:allempty

Checks whether a piece fits into a board at a given location.

Board:allempty(t: Tetromino, rotation: integer, x: integer, y: integer) -> boolean

Board:stamp

Writes a piece onto a board at a given location.

Board:stamp(t: Tetromino, rotation: integer, x: integer, y: integer) -> boolean

CoreOption

A core option. New values for these options can be defined by adding functions to the table. The parameters passed into the function and when the function is called depend on the specific option.

type CoreOption = table

CoreOption.name

The name of a core option, as used in game.options.

CoreOption.name: string

CoreOption.value

The value of a core option. TODO: allow mutation

CoreOption.value: string

GameState

The state of the game for one player.

type GameState = table

GameState:bclear

Initiates the b-type clear sequence. This may invoke other mod functions if they listen to game.events.BCLEAR. This may be called even from a-type, but it can't be called in a multiplayer game. Returns true if the call was successful, or false if the b-type clear was cancelled by a mod.

GameState:bclear() -> boolean

GameState.board

The currently active board. May be mutated.

GameState.board: Board

GameState.completed

Represents which lines, if any, were just cleared. Each of the lower 4 bits represents one line, from top to bottom going from least to most significant. If you just want to get a count of how many lines were cleared, use GameState:completedcount instead.

GameState.completed: integer

GameState:completedcount

The number of lines that were just cleared.

GameState:completedcount() -> integer

GameState.cur

The currently active tetromino.

GameState.cur: Tetromino

GameState.das

The current DAS charge.

GameState.das: integer

GameState.doubles

The total number of doubles cleared.

GameState.doubles: integer

GameState.dropdelay

The amount of time that must pass before the next piece will spawn. If zero, a piece is currently active.

GameState.dropdelay: integer

GameState.drought

The number of non-I pieces that have spawned consecutively (the "drought").

GameState.drought: integer

GameState:efficiency

The current efficiency.

GameState:efficiency() -> integer

GameState.fallamt

Counts up to game.gravity(level) before game.events.GRAVITY is triggered and the current piece is either moved down one or locked into place.

GameState.fallamt: integer

GameState.flip

The direction that the player wants to flip the piece. This is 0 if a rotation wasn't requested, -1 if a counter-clockwise rotation was requested, or 1 if a clockwise rotation was requested.

GameState.flip: integer

GameState.level

The current level.

GameState.level: integer

GameState:lines

The total number of lines cleared.

GameState:lines() -> integer

GameState:lock

Locks the currently active piece into place. This may invoke other mod functions if they listen to game.events.PRE_LOCK or game.events.LOCKED. Returns true if the piece actually locked; this may return false is a mod function cancels the lock.

GameState:lock() -> boolean

GameState.next

The tetromino up next.

GameState.next: Tetromino

GameState.oldrotation

The previous rotation. Only really used by the game to check whether the piece was just rotated, but it may be useful to set this sometimes.

GameState.oldrotation: integer

GameState.oldx

The previous x position. Only really used by the game to check whether the piece was just moved, but it may be useful to set this sometimes.

GameState.oldx: integer

GameState.oldy

The previous y position. Only really used by the game to check whether the piece was just moved, but it may be useful to set this sometimes.

GameState.oldy: integer

GameState.over

Whether the player has topped out.

GameState.over: boolean

GameState.player

The player for this game state: either 1 or 2.

GameState.player: integer

GameState.pushdownpts

Accumulated push-down points when soft-dropping. This value is mutable.

GameState.pushdownpts: integer

GameState.quads

The total number of tetrises (4-line clears) cleared.

GameState.quads: integer

GameState:redraw

Redraws the board. This may invoke other mod functions if they listen to game.events.REDRAW.

GameState:redraw()

GameState.rotation

The current rotation.

GameState.rotation: integer

GameState.score

The current score.

GameState.score: integer

GameState.singles

The total number of singles cleared.

GameState.singles: integer

GameState.softdrop

If negative, this will increase, and GameState.fallamt will remain frozen until this hits zero. If positive, the piece is being soft-dropped.

GameState.softdrop: integer

GameState.triples

The number of triples cleared.

GameState.triples: integer

GameState:trtrate

The percentage of line clears that were tetrises/quads (the "tetris rate").

GameState:trtrate() -> integer

GameState.waiting

If positive, this will decrease until it hits zero, and nothing else will happen in the main game loop. This is used when the game first starts (to emulate the delay in NES Tetris), and when b-type is cleared.

GameState.waiting: integer

GameState.x

The current x position.

GameState.x: integer

GameState.y

The current y position. Note that y increases from top to bottom.

GameState.y: integer

Image

An image created by game.newimg.

type Image = userdata

Leaderboard

A leaderboard, usually obtained from game.leaderboards. Its contents are mutable.

type Leaderboard = [10]LeaderboardEntry

Leaderboard.cur

When game.scr == game.screen.LEADERBOARD, this points to the leaderboard entry that's currently being edited. It's an error to access this value when on any other screen.

Leaderboard.cur: LeaderboardEntry

Leaderboard.type

The type of this leaderboard, either "a" or "b".

Leaderboard.type: string

LeaderboardEntry

An entry on a leaderboard. TODO: allow mutation

type LeaderboardEntry = table

LeaderboardEntry.height

The height of the b-type game this entry describes, or nil if the game wasn't played in b-type. This can be used to determine whether an entry is on the a-type or b-type leaderboard.

LeaderboardEntry.height: integer | nil

LeaderboardEntry.level

The final level of the game this entry describes.

LeaderboardEntry.level: integer

LeaderboardEntry.name

The name of the player who played the game this entry describes. The name is always exactly eight characters.

LeaderboardEntry.name: string

LeaderboardEntry.rank

The rank of an entry on its leaderboard, where first place is rank 1.

LeaderboardEntry.rank: integer

LeaderboardEntry.score

The final score of the game this entry describes.

LeaderboardEntry.score: integer

Music

Music loaded by game.loadmusic.

type Music = integer

Option

An option registered by a mod.

type Option = table

Option.default

The default value of this option, if no value is supplied in the config file.

Option.default: string

Option.value

The current value of this option.

Option.value: string

Screen

A screen, such as game.screen.LVLSELECT or game.screen.GAME.

type Screen = integer

Sfx

A sound effect loaded by game.loadsfx.

type Sfx = integer

Tetromino

A tetromino, represented by a single character string: "T", "J", "Z", "O", "S", "L", "I".

type Tetromino = string

Texture

A texture that can be drawn to. Note that not all textures support transparency.

type Texture = table

Texture:clear

Clears a texture.

Texture:clear()

Texture:cleartetromino

Clears the space a tetromino would occupy on a texture.

Texture:cleartetromino(t: Tetromino, rotation: integer, x: integer, y: integer)

Texture:drawbrick

Draws a tetromino brick on a texture. 'id' must be a non-negative number less than 4. If 0, this is a no-op. Otherwise, 'id' represents the tile to draw. If 'opacity' is specified, it must be an integer between 0 and 255 (defaults to 255).

Texture:drawbrick(
    id: integer,
    x: integer,
    y: integer,
    level: integer,
    opacity?: integer
)

Texture:drawimg

Draws an image on a texture. Note that x and y are in pixels (before the texture is scaled by SDL), not tiles as they are in most other texture functions.

Texture:drawimg(
    img: Image,
    x: integer,
    y: integer,
    opts?: {
        flip: nil | "h" | "v",
        rotation: number,
        scale: integer,
    }
)

Texture:drawrect

Draws a rectangle on a texture.

Texture:drawrect(x: integer, y: integer, w: integer, h: integer, color: integer)

Texture:drawrectpx

Similar to Texture:drawrect, but x and y are in pixels (before the texture is scaled by SDL) rather than tiles.

Texture:drawrectpx(x: integer, y: integer, w: integer, h: integer, color: integer)

Texture:drawtetromino

Draws a tetromino on a texture. If 'opacity' is specified, it must be an integer between 0 and 255 (defaults to 255).

Texture:drawtetromino(
    t: Tetromino,
    rotation: integer,
    x: integer,
    y: integer,
    level: integer,
    opacity?: integer
)

Texture:fillrect

Draws a rectangle on a texture, and fills it in with the given color.

Texture:fillrect(x: integer, y: integer, w: integer, h: integer, color: integer)

Texture:fillrectpx

Similar to Texture:fillrect, but x and y are in pixels (before the texture is scaled by SDL) rather than tiles.

Texture:fillrectpx(x: integer, y: integer, w: integer, h: integer, color: integer)

Texture:write

Writes a string or number to the screen. 'width' must only be supplied if writing a number, but it can always be omitted.

Texture:write(msg: string | integer, x: integer, y: integer, width?: integer, color: integer)

game

local game = require"game"

game.TILEHEIGHT

The height of the canvas, in tiles. Each tile is 32x32 pixels.

game.TILEHEIGHT: integer

game.TILEWIDTH

The width of the canvas, in tiles. Each tile is 32x32 pixels.

game.TILEWIDTH: integer

game.__useroptions

game.__useroptions: table

game.bg

The background texture. This is cleared whenever the screen is switched, and is intended to be mostly static and infrequently updated. Transparency is not supported on this texture.

game.bg: Texture

game.chdir

Changes the current working directory, as in chdir(2). On POSIX systems, the PWD environment variable is also updated to the absolute path of the new working directory. A Lua error is raised if an error occurs.

game.chdir(path: string)

game.color.BLACK

The color black.

game.color.BLACK: integer

game.color.BLUE

A blue color from the NES color palette: #3032ECFF

game.color.BLUE: integer

game.color.GREEN

A green color from the NES color palette: #287200FF

game.color.GREEN: integer

game.color.LIGHTBLUEISH

A light blue-ish color from the NES color palette: #BCBCECFF

game.color.LIGHTBLUEISH: integer

game.color.LIGHTGRAY

A light gray color from the NES color palette: #ECEEECFF

game.color.LIGHTGRAY: integer

game.color.LIGHTGREEN

A light green color from the NES color palette: #38CC6CFF

game.color.LIGHTGREEN: integer

game.color.PURPLE

A purple color from the NES color palette: #E454ECFF

game.color.PURPLE: integer

game.color.RED

A red color from the NES color palette: #982220FF

game.color.RED: integer

game.color.WHITE

The color white.

game.color.WHITE: integer

game.color.YELLOW

A yellow color from the NES color palette: #A0AA00FF

game.color.YELLOW: integer

game.color.invert

Inverts the background and foreground of a color.

game.color.invert(color: integer) -> integer

game.emptyboard

A board which is completely empty; useful when combined with Board:allempty to check if a piece is entirely within the playfield. You shouldn't mutate this board; that would be very impolite to the other mods using it. Don't be impolite.

game.emptyboard: Board

game.events.BCLEAR

Triggers before a b-type clear. Setting GameState.waiting to 0 will cancel the clear.

game.events.BCLEAR: integer

game.events.END_GAME

Triggers immediately before the game ends.

game.events.END_GAME: integer

game.events.FIRST_FRAME

Triggers on the first frame that a piece spawns.

game.events.FIRST_FRAME: integer

game.events.FLIP

Triggers when the player attempts to rotate a piece, immediately before the rotation is attempted. Setting GameState.flip to 0 will cancel the rotation. This is useful if you'd like to implement your own rotation system.

game.events.FLIP: integer

game.events.FRAME

Triggers every frame, unconditionally.

game.events.FRAME: integer

game.events.GRAVITY

Triggers before a piece is shifted down or locked due to gravity. Setting GameState.fallamt to a non-zero value or setting GameState.softdrop to a negative value will cancel this.

game.events.GRAVITY: integer

game.events.LOCKED

Triggers after a piece locks. Data about the piece such as its position and rotation is lost; if you need this, use game.events.PRE_LOCK.

game.events.LOCKED: integer

game.events.LVLUP

Triggers after the level is incremented.

game.events.LVLUP: integer

game.events.PAUSE

Triggers when the game is paused or unpaused. Check game.pause to determine which.

game.events.PAUSE: integer

game.events.PRE_LOCK

Triggers before a piece locks. Line clears aren't computed at this point; if you need this, use game.events.LOCKED.

game.events.PRE_LOCK: integer

game.events.RAW_KEYDOWN

Triggers when a key on the keyboard is pressed. A third argument is supplied to the mod function for this event: a string of the key that was pressed. This differs from game.events.RAW_TEXTINPUT in that it only sends single key presses, regardless of any modifiers (e.g. shift) being used. You shouldn't use this unless you have a very specific use-case which calls for it.

game.events.RAW_KEYDOWN: integer

game.events.RAW_KEYUP

Triggers when a key on the keyboard is released. A third argument is supplied to the mod function for this event: a string of the key that was released. You shouldn't use this unless you have a very specific use-case which calls for it.

game.events.RAW_KEYUP: integer

game.events.RAW_MOUSEDOWN

Triggers when a button on the mouse is pressed. A third argument is supplied to the mod function for this event: a string of the button that was pressed ("Left", "Middle", "Right", "X1", "X2", or "Invalid"). You shouldn't use this unless you have a very specific use-case which calls for it.

game.events.RAW_MOUSEDOWN: integer

game.events.RAW_MOUSEMOVE

Triggers when the mouse is moved. A third argument is supplied to the mod function for this event: a table containing two integer values, which are respectively the x and y position of the cursor. You shouldn't use this unless you have a very specific use-case which calls for it.

game.events.RAW_MOUSEMOVE: integer

game.events.RAW_MOUSEUP

Triggers when a button on the mouse is released. A third argument is supplied to the mod function for this event: a string of the button that was released ("Left", "Middle", "Right", "X1", "X2", or "Invalid"). You shouldn't use this unless you have a very specific use-case which calls for it.

game.events.RAW_MOUSEDOWN: integer

game.events.RAW_MOUSEWHEEL

Triggers when the mouse wheel is scrolled. A third argument is supplied to the mod function for this event: a table containing two integer values, which are respectively the amount scrolled horizontally (positive to the right) and the amount scrolled vertically (positive up). You shouldn't use this unless you have a very specific use-case which calls for it.

game.events.RAW_MOUSEWHEEL: integer

game.events.RAW_TEXTINPUT

Triggers when something is typed on the keyboard. A third argument is supplied to the mod function for this event: a string of what was typed. This differs from game.events.RAW_KEYDOWN in that it sends a string of what was actually typed by the user, taking modifiers (e.g. shift) into account, and supporting composition (e.g. for non-ASCII characters, but note that the builtin font only supports ASCII). You shouldn't use this unless you have a very specific use-case which calls for it.

game.events.RAW_TEXTINPUT: integer

game.events.REDRAW

Triggers after the board is redrawn.

game.events.REDRAW: integer

game.events.ROTATE

Triggers after the piece is rotated.

game.events.ROTATE: integer

game.events.SHIFT

Triggers after the piece is shifted in the x direction.

game.events.SHIFT: integer

game.events.STARTUP

Triggers when generic tetromino game is first launched. This is the first event that's ever triggered, and it's only triggered once. Additional "flags" can be supplied as command-line arguments, for instance, foo.bar=baz will set the flag "bar" to "baz" for the option named "foo", foo.bar will set the flag "bar" to boolean true, and !foo.bar will set the flag "bar" to boolean false. The flags are passed to the mod function in the third parameter, as a table. The table is empty if no flags are given. Most mods won't need to use flags at all; their use case is when additional info needs to be provided to a mod that can't be provided through the option value alone (e.g. a file path).

game.events.STARTUP: integer

game.events.START_GAME

Triggers when the game starts, immediately after the current and next piece are first chosen. Note that this isn't the first event that's triggered in a game, since game.events.TICK triggers even during the initial start delay.

game.events.START_GAME: integer

game.events.SWITCH_SCREEN

Triggers when the active screen is switched. Check game.scr to determine which screen is now active. For most use cases you probably don't want to use this event; use a more specific event such as game.events.END_GAME instead.

game.events.SWITCH_SCREEN: integer

game.events.TICK

Triggers every frame for all players in-game.

game.events.TICK: integer

game.events.TOPOUT

Triggers after the player tops out.

game.events.TOPOUT: integer

game.fg

The foreground texture. This is cleared whenever the screen is switched, and is intended for screen-dependent graphics that are frequently updated. This texture supports transparency.

game.fg: Texture

game.frames

The number of frames that have elapsed since startup. This effects the timing of line clears (for compatibility with NES Tetris). This value is mutable.

game.frames: integer

game.freeze

Completely freezes all game logic. The only events that are triggered when the game is frozen are game.events.FRAME and all RAW_ events. When the game is frozen, input is read, but not acted upon; it follows that the same button can be pressed immediately before the game is frozen, and immediately after it's frozen, and it will be interpreted as two separate consecutive button presses, rather than holding the button down (this will break replays, however). This function can be safely called more than once; subsequent calls will have no effect. Call game.unfreeze to resume game logic.

game.freeze()

game.gravity

Gets the gravity of a level; i.e. the value that GameState.fallamt must reach before the current piece moves down or locks.

game.gravity(level: integer) -> integer

game.haltmusic

Halts the currently playing music if there is any. A fade-out can be optionally supplied (in milliseconds). Once the music halts, the callback function is called, if one is supplied. The callback may call game.playmusic. Only one callback may be active at a time; if a new callback is registered while music is still fading out, the old callback will be overwritten. The callback is removed after it returns.

game.haltmusic(fadeout?: integer, callback?: function ())

game.haltsfx

Halts all currently playing sound effects.

game.haltsfx()

game.height

The B-type height, or nil if not playing on B-type. This value is mutable, but mutating it when not on game.screen.LVLSELECT when game.type is "b" won't have any effect. This value will never be greater than 8.

game.height: integer | nil

game.leaderboards.a

The a-type leaderboard.

game.leaderboards.a: Leaderboard

game.leaderboards.b

The b-type leaderboard.

game.leaderboards.b: Leaderboard

game.listen

Registers a listener for events in the same manner as game.newoption, except that no visible/modifiable option is created. This should be used when the actual mod logic is implemented elsewhere, but an additional listener is needed. Mods should prefer game.newoption whenever possible.

game.listen(events: integer, func: function (ev: integer, player: integer))

game.loadmusic

Loads music from the given path and returns an object that can be played with game.playmusic. The working directory is set to the directory that mod.lua resides in when the file is first run, so you can use a relative path here.

game.loadmusic(path: string) -> Music

game.loadsfx

Loads a sound effect from the given path and returns an object that can be played with game.playsfx. The working directory is set to the directory that mod.lua resides in when the file is first run, so you can use a relative path here.

game.loadsfx(path: string) -> Sfx

game.multiplayer

Whether or not the game is being played multiplayer, either local or online.

game.multiplayer: boolean

game.newboard

Creates a new board, which can be safely mutated.

game.newboard() -> Board

game.newimg

Creates a new image from the given pixel data. The data table is a table of colors (32-bit unsigned integers), and its length must be equal to width times height. The image can be drawn with Texture:drawimg.

game.newimg(data: []integer, width: integer, height: integer) -> Image

game.newoption

Registers a new option. The second argument is a table, which must have a 'default' key, whose value is the default value of the option, and an 'events' key, whose value is the bitwise OR of all events that functions for this option will listen to. Register values for the new option by creating functions within the option table, i.e. for option foo with value bar: function foo.bar(ev, player). ev is the event that caused this function to be called, and player is the player this event is associated with (either 1 or 2). Some events will supply a third argument to the function; these are documented for specific events.

game.newoption(name: string, {default: string, events: integer}) -> Option

game.options.input_handler

The input_handler core option. User-defined functions are called everytime input is received. The purpose of the input handler is to return inputs which are possibly derived from the actual received inputs. The function is called with three arguments: the first argument is always 0, the second argument is the player the received input is from (either 1 or 2), and the third argument is the actual inputs received. The inputs passed into and returned from the function have the same format as those in the replay format, except the repeat bit is ignored.

game.options.input_handler: CoreOption

game.options.lines_per_level

The lines_per_level core option. TODO: actually implement

game.options.lines_per_level: CoreOption

game.options.music

The music core option. The user may define functions here (the same as with all other core options), which are called with no arguments whenever game music is to be played, or the user may define fields as Music values instead, in which case the given music is played.

game.options.music: CoreOption

game.options.rng

The rng core option. User-defined functions are called everytime a new RNG value is needed. The function is called with two arguments: the first argument is always 0, and the second argument is the player that the RNG value is being generated for (either 1 or 2). The return value of the function is used as the new RNG value.

game.options.rng: CoreOption

game.overlay

The overlay texture. This is intended for screen-independent graphics. This texture supports transparency.

game.overlay: Texture

game.pause

Whether or not the game is currently paused. This value is mutable.

game.pause: boolean

game.pausemusic

Pauses the currently playing music if there is any. The music can be resumed with game.resumemusic.

game.pausemusic()

game.players

Game states for both players. If game.multiplayer is false, index 2 is invalid here.

game.players: [2]GameState

game.playgamemusic

Plays game music according to the value of the "music" core option.

game.playgamemusic()

game.playmusic

Plays music.

game.playmusic(mus: Music)

game.playsfx

Plays a sound effect. The 'pan' argument only has an effect when playing multiplayer. If 'pan' is negative, the sound plays moreso to the left, if positive, it plays moreso to the right. If zero, the sound plays equally left and right (i.e. no position changes). The default pan is zero.

game.playsfx(sfx: Sfx, pan?: integer)

game.prompt

Displays a prompt containing one or two choices. When a choice is selected, the respective callback function is called. This should, at minimum, set game.scr to a different screen.

game.prompt(
    msg: string,
    choice1: string,
    action1: function (),
    choice2?: string,
    action2?: function ()
)

game.resumemusic

Resumes music that was paused by game.pausemusic, or does nothing if no music was paused.

game.resumemusic()

game.rng.misc

A miscellaneous 16-bit unsigned integer used by the selected RNG algorithm. The exact meaning of this number differs based on the active RNG algorithm. This value is mutable; if your mod implements an RNG algorithm that preserves data between games, this should be used so games replay correctly.

game.rng.misc: integer

game.rng.next

Runs the PRNG function to get the next random number and returns the result.

game.rng.next() -> integer

game.rng.value

The most recently generated random number.

game.rng.value: integer

game.scr

The currently active screen. This can be mutated to switch to a different screen, potentially invoking other mod functions that listen to game.events.SWITCH_SCREEN. game.screen.PROMPT is NOT a valid argument for this function; use game.prompt instead.

game.scr: Screen

game.screen.CREDITS

The credits screen.

game.screen.CREDITS: Screen

game.screen.GAME

The game screen.

game.screen.GAME: Screen

game.screen.INPUT

The input mapping screen.

game.screen.INPUT: Screen

game.screen.LEADERBOARD

The leaderboard screen, where the player enters their name to save their score.

game.screen.LEADERBOARD: Screen

game.screen.LVLSELECT

The level select screen.

game.screen.LVLSELECT: Screen

game.screen.ONLINE

The online screen.

game.screen.ONLINE: Screen

game.screen.OPTIONS

The options screen.

game.screen.OPTIONS: Screen

game.screen.PROMPT

The prompt screen, used for user input and error messages.

game.screen.PROMPT: Screen

game.screen.REPLAY

The replay selection screen.

game.screen.REPLAY: Screen

game.screen.ROCKET

The rocket (statistics) screen.

game.screen.ROCKET: Screen

game.screen.TITLE

The title screen.

game.screen.TITLE: Screen

game.startlevel

The level the game was started at, or the level the game will start at if not currently in a game. This value is mutable, but mutating it when not on game.screen.LVLSELECT won't have any effect.

game.startlevel: integer

game.type

The type of the game: either "a", "b", or nil if inapplicable.

game.type: string | nil

game.unfreeze

Resumes game logic if the game was frozen by game.freeze. This function is a no-op if the game isn't frozen.

game.unfreeze()