[003] Tetris
[003] Tetris
- game rules are based on the Tetris Guideline [tetris.wiki/Tetris_Guideline]
- Soft Drop, DAS & ARE are based on NES Tetris [tetris.wiki/Tetris_(NES,_Nintendo)]
- game updates at 60 frames per second [gamedev.stackexchange.com/q/159835]
- uses marathon speed for each level [tetris.wiki/Marathon]
TODO: sounds and effects (+ BG)
TODO: score multipliers (i.e. combos, T-spin, etc.)
TODO: not sure if I-piece "kick" is correct
Game Mechanics
[rules]
- playfield (matrix) is 10 cells wide and 20 cells tall
- pieces can be manipulated at the top of the playfield (on spawn)
- each of the 7 pieces are randomized in a 'bag' and are dealt out one by one
- can hold pieces, once taken out, piece must lock down first before hold can be used again
- there is a ghost piece (preview) to show where a piece will go to
Scoring
[rules]
single = 100 * level
double = 300 * level
triple = 500 * level
tetris = 800 * level
+1 score on each grid cell a piece is soft dropped
+2 score on each grid cell a piece is hard dropped
level advances by one for each 10 lines cleared
Movement
[rules]
- on L/R key press, move the piece one cell
- [DAS] stop 16 frames (0.26667), then move again every 6 frames (0.1)
- tapping L/R key instantly moves the piece one cell
- the piece automatically moves down at intervals based on level
- on each level, has a 'Frames per Gridcell' (converted to Godot delta)
- soft drop (pressing down) is 1/2 G
- ARE (appearance delay) depending on where piece is locked
- 10 frames at index [0 - 1] rows (0.16667)
- 12 frames at index [2 - 5] rows (0.20000)
- 14 frames at index [6 - 9] rows (0.23334)
- 16 frames at index [10 - 13] rows (0.26667)
- 18 frames at index [14 - 19] rows (0.30000)
- additional 20 frames on line clear (0.33333)
Drop Speed
[rules]
- based on the Tetris Guide [tetris.wiki/Marathon]
- each level has a 'Cell per Frame'
- due to how _process() is in Godot, i had to convert this to 'Time per Cell'
- keeping in mind that each second is 60 frames
frames_per_cell = 1 / cell_per_frame second_per_cell = frames_per_cell / 60 _cell_per_frame = [0.01667, 0.021017, 0.026977, 0.035256, 0.04693, 0.06361, 0.0879, 0.1236, 0.1775, 0.2598, 0.388, 0.59, 0.92, 1.46, 2.36, 3.91, 6.61, 11.43, 20] _frames_per_cell = [59.98800, 47.58053, 37.06861, 28.36397, 21.30833, 15.72080, 11.37656, 8.09061, 5.63380, 3.84911, 2.57732, 1.69491, 1.08696, 0.68493, 0.42372, 0.25575, 0.15129, 0.08749, 0.05] _second_per_cell = [1.0, 0.79300, 0.61781, 0.47273, 0.35514, 0.26201, 0.18961, 0.13484, 0.09390, 0.06415, 0.04296, 0.02825, 0.01812, 0.01142, 0.00706, 0.00426, 0.00252, 0.00146, 0.00083]
Rotation & Kicks
[rules]
- uses guideline Super Rotation System [tetris.wiki/Super_Rotation_System]
- unobstructed basic rotation are unaffected
- if basic rotation is blocked, 4 other positions are tested
- if the 4 other positions are all blocked, the rotation fails
- tested position are based on initial state and desired final rotation state
- adjust Y position for Godot Grid system (Y is reversed)
- piece O have no rotation
- piece I have a different rotation offset
Test 1 Test 2 Test 3 Test 4 Test 5 0 ( 0, 0) (-1, 0) (+2, 0) (-1, 0) (+2, 0) L ( 0,-1) ( 0,-1) ( 0,-1) ( 0,+1) ( 0,-2) 2 (-1,-1) (+1,-1) (-2,-1) (+1, 0) (-2, 0) R (-1, 0) ( 0, 0) ( 0, 0) ( 0,-1) ( 0,+2) - all other pieces share the same rotation offset Test 1 Test 2 Test 3 Test 4 Test 5 0 ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 0) L ( 0, 0) (-1, 0) (-1,+1) ( 0,-2) (-1,-2) 2 ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 0) R ( 0, 0) (+1, 0) (+1,+1) ( 0,-2) (+1,-2)
- values are subtracted based on rotation
- 0 > R = 0 - R
- Test 1 = ( 0, 0) - ( 0, 0) = ( 0, 0)
- Test 2 = ( 0, 0) - (+1, 0) = (-1, 0)
- Test 3 = ( 0, 0) - (+1,-1) = (-1,+1)
- Test 4 = ( 0, 0) - ( 0,+2) = ( 0,-2)
- Test 5 = ( 0, 0) - (+1,+2) = (-1,-2)
Manual Tilemap Detection
[pseudocode]
- did not use any Collision systems
- manually track a 'core' tile within a Tetromino piece
- using tilemap logic, checks the neighboring tiles
- left = index - 1, right = index + 1, bottom = index + map_width, top = index - map_width
- when moving left or right, stop if a tile is hit
- when moving down, check if a tile is hit
- if a tile is hit, build the tetromino using the 'core' tile
- on each row being built on, check if it is 'full'
- if full, clear row and use logic to repack the map
Ghost Piece
[pseudocode]
- use of sprite outlines for each type of Tetromino
- rotate and reposition each ghost sprite when rotating the Tetromino
- ghost.position_x = tetromino.position_x
- starting at tetromino.position_y
- check each row to see if any blocks is hit (until outside map)
if row is empty, ghost.position_y + 1
once a block is hit, stop the checks
Piece Generation
[pseudocode]
var pieces: Array[int] = [0, 1, 2, 3, 4, 5, 6] var next_index: int var next_value: int on piece lock next_index += 1 if next_index == 7: pieces.shuffle() next_index = 0 piece = next_value next_value = pieces[next_idx]
Status | Released |
Platforms | HTML5 |
Author | QuietGodot |
Made with | Godot |
Leave a comment
Log in with itch.io to leave a comment.