Lesson 08
Rotating a piece is a coordinate trick: spin each offset 90 degrees, then test the result against the board. If it collides with a wall we nudge it back on — a simplified wall kick — so rotation feels forgiving.
To rotate an offset (x, y) clockwise around the origin: the new point is (-y, x). That's the entire math. Add a rotation helper to piece.gd that produces the candidate offsets without committing them yet:
# Return what the offsets WOULD be after a clockwise rotation.
# We don't apply them until Main confirms the move is legal.
func rotated_offsets() -> Array:
var result: Array = []
for off in offsets:
# (x, y) -> (-y, x) rotates 90 deg clockwise
result.append(Vector2i(-off.y, off.x))
return result
# Commit a new set of offsets and redraw.
func apply_offsets(new_offsets: Array) -> void:
offsets = new_offsets
_redraw()The O piece
The square (O) piece rotates onto itself, so rotation looks like nothing happens — that's correct. Every other shape visibly turns.
In main.gd, handle the rotate action. We build the candidate cells from the rotated offsets and check them. If they fit, commit; if not, try small horizontal nudges (wall kicks) before giving up.
func _input(event: InputEvent) -> void:
if active_piece == null:
return
if event.is_action_pressed("hard_drop"):
_hard_drop()
elif event.is_action_pressed("rotate_cw"):
_try_rotate()func _try_rotate() -> void:
var candidate: Array = active_piece.rotated_offsets()
# Kicks to attempt in order: no shift, right 1, left 1, right 2, left 2.
var kicks: Array = [
Vector2i(0, 0), Vector2i(1, 0), Vector2i(-1, 0),
Vector2i(2, 0), Vector2i(-2, 0),
]
for kick in kicks:
if _offsets_fit(candidate, active_piece.origin + kick):
active_piece.origin += kick
active_piece.apply_offsets(candidate)
return
# No kick worked — rotation is blocked, do nothing.
# Do these offsets fit if the piece origin were at test_origin?
func _offsets_fit(test_offsets: Array, test_origin: Vector2i) -> bool:
for off in test_offsets:
var cell: Vector2i = test_origin + off
if not board.is_free(cell.x, cell.y):
return false
return trueTest before you commit
Notice we compute the candidate offsets and only apply them once a kick fits. If you rotate first and check second, a blocked rotation leaves the piece stuck in a wall. Always validate, then commit.
Without kicks, rotating an I piece flat against the left wall would push cells to column -1 and simply fail — frustrating. The kick list tries shifting the rotated piece right or left by 1–2 cells so it snaps back onto the board. This is a simplified version of the SRS kick system real puzzle games use.
Checkpoint — expected state
(-y, x) offset transform.