Skip to content

Instantly share code, notes, and snippets.

@Hammer2900
Created February 4, 2026 17:23
Show Gist options
  • Select an option

  • Save Hammer2900/3f897416c7dd9775051aae290baa0d24 to your computer and use it in GitHub Desktop.

Select an option

Save Hammer2900/3f897416c7dd9775051aae290baa0d24 to your computer and use it in GitHub Desktop.
slot map in odin
package main
import "core:fmt"
import "core:math"
import "core:time"
// =======================================================
// StableVector (Slot Map implementation)
// =======================================================
ID :: distinct u64
InvalidID :: ID(max(u64))
Handle :: struct(T: typeid) {
id : ID,
gen : u32,
vec : ^StableVector(T),
}
StableVector :: struct(T: typeid) {
data : [dynamic]T, // Плотный массив данных
dense_to_sparse : [dynamic]ID, // Плотный массив: Индекс данных -> ID
sparse_to_dense : [dynamic]int, // Разреженный массив: ID -> Индекс данных
generations : [dynamic]u32, // Разреженный массив: ID -> Поколение (версия)
free_ids : [dynamic]ID, // Стек свободных ID
}
stablevec_init :: proc(vec: ^StableVector($T), capacity: int = 0) {
if capacity > 0 {
reserve(&vec.data, capacity)
reserve(&vec.dense_to_sparse, capacity)
reserve(&vec.sparse_to_dense, capacity)
reserve(&vec.generations, capacity)
reserve(&vec.free_ids, capacity)
}
}
stablevec_destroy :: proc(vec: ^StableVector($T)) {
delete(vec.data)
delete(vec.dense_to_sparse)
delete(vec.sparse_to_dense)
delete(vec.generations)
delete(vec.free_ids)
}
// Получение нового ID (из пула или создание нового)
stablevec_alloc_id :: proc(vec: ^StableVector($T)) -> ID {
if len(vec.free_ids) > 0 {
return pop(&vec.free_ids)
}
id := ID(len(vec.sparse_to_dense))
append(&vec.sparse_to_dense, -1) // Пока не указывает на данные
append(&vec.generations, 0)
return id
}
stablevec_push :: proc(vec: ^StableVector($T), value: T) -> ID {
id := stablevec_alloc_id(vec)
// Индекс, куда положим данные (конец плотного массива)
idx := len(vec.data)
append(&vec.data, value)
append(&vec.dense_to_sparse, id)
// Связываем ID с индексом данных
vec.sparse_to_dense[id] = idx
return id
}
stablevec_is_valid :: proc(vec: ^StableVector($T), id: ID, gen: u32) -> bool {
if int(id) >= len(vec.generations) {
return false
}
// Проверяем поколение и что элемент не удален (индекс != -1)
return vec.generations[id] == gen && vec.sparse_to_dense[id] != -1
}
handle_is_valid :: proc(h: Handle($T)) -> bool {
if h.vec == nil {
return false
}
return stablevec_is_valid(h.vec, h.id, h.gen)
}
// Получение указателя по Handle
handle_ptr :: proc(h: Handle($T)) -> ^T {
if !handle_is_valid(h) {
return nil
}
idx := h.vec.sparse_to_dense[h.id]
return &h.vec.data[idx]
}
// Получение указателя напрямую по ID (нужно для логики игры, где нет Handle под рукой)
stablevec_get_ptr :: proc(vec: ^StableVector($T), id: ID) -> ^T {
if int(id) >= len(vec.sparse_to_dense) { return nil }
idx := vec.sparse_to_dense[id]
if idx == -1 { return nil }
return &vec.data[idx]
}
stablevec_handle :: proc(vec: ^StableVector($T), id: ID) -> Handle(T) {
if int(id) >= len(vec.generations) || vec.sparse_to_dense[id] == -1 {
return Handle(T){}
}
return Handle(T){
id = id,
gen = vec.generations[id],
vec = vec,
}
}
stablevec_erase :: proc(vec: ^StableVector($T), id: ID) {
if int(id) >= len(vec.sparse_to_dense) { return }
idx := vec.sparse_to_dense[id]
if idx == -1 { return } // Уже удален
last_idx := len(vec.data) - 1
// Если удаляемый элемент не последний, меняем его местами с последним (Swap & Pop)
if idx != last_idx {
last_id := vec.dense_to_sparse[last_idx]
// Перемещаем данные
vec.data[idx] = vec.data[last_idx]
vec.dense_to_sparse[idx] = last_id
// Обновляем ссылку для перемещенного элемента
vec.sparse_to_dense[last_id] = idx
}
// Удаляем хвосты
pop(&vec.data)
pop(&vec.dense_to_sparse)
// Помечаем ID как свободный
vec.sparse_to_dense[id] = -1
vec.generations[id] += 1 // Инвалидируем старые Handle
append(&vec.free_ids, id)
}
// =======================================================
// TETRIS
// =======================================================
BOARD_W :: 10
BOARD_H :: 20
Block :: struct {
x, y : int,
}
blocks : StableVector(Block)
grid : [BOARD_H][BOARD_W]ID
clear_grid :: proc() {
for y in 0..<BOARD_H {
for x in 0..<BOARD_W {
grid[y][x] = InvalidID
}
}
}
spawn_I :: proc() -> [4]Handle(Block) {
base_x := 3
base_y := 0
hs: [4]Handle(Block)
for i in 0..<4 {
id := stablevec_push(&blocks, Block{
x = base_x + i,
y = base_y,
})
h := stablevec_handle(&blocks, id)
hs[i] = h
grid[base_y][base_x + i] = id
}
return hs
}
can_move_down :: proc(hs: []Handle(Block)) -> bool {
for h in hs {
if !handle_is_valid(h) {
return false
}
b := handle_ptr(h)
ny := b.y + 1
// 1. Выход за границы
if ny >= BOARD_H {
return false
}
// 2. Коллизия с другой клеткой
target_id := grid[ny][b.x]
if target_id != InvalidID {
// Проверяем, не является ли препятствие частью самой фигуры
is_self := false
for self_h in hs {
if self_h.id == target_id {
is_self = true
break
}
}
if !is_self {
return false
}
}
}
return true
}
move_down :: proc(hs: []Handle(Block)) {
// Сначала очищаем текущие позиции в сетке
for h in hs {
b := handle_ptr(h)
grid[b.y][b.x] = InvalidID
b.y += 1
}
// Затем ставим новые
for h in hs {
b := handle_ptr(h)
grid[b.y][b.x] = h.id
}
}
clear_lines :: proc() {
y := BOARD_H - 1
for y >= 0 {
full := true
for x in 0..<BOARD_W {
if grid[y][x] == InvalidID {
full = false
break
}
}
if full {
// Удаляем блоки из вектора
for x in 0..<BOARD_W {
stablevec_erase(&blocks, grid[y][x])
grid[y][x] = InvalidID
}
// Сдвигаем все линии выше вниз
for yy := y - 1; yy >= 0; yy -= 1 {
for x in 0..<BOARD_W {
id := grid[yy][x]
grid[yy+1][x] = id
if id != InvalidID {
// Обновляем координату Y внутри блока
ptr := stablevec_get_ptr(&blocks, id)
if ptr != nil {
ptr.y += 1
}
}
}
}
// Очищаем самую верхнюю линию
for x in 0..<BOARD_W {
grid[0][x] = InvalidID
}
// Не уменьшаем y, так как текущая линия теперь заполнена тем, что упало сверху
} else {
y -= 1
}
}
}
draw :: proc() {
// Возврат курсора в начало (без очистки экрана, чтобы не мерцало)
fmt.print("\x1b[H")
for y in 0..<BOARD_H {
fmt.print("|")
for x in 0..<BOARD_W {
if grid[y][x] == InvalidID {
fmt.print(" .") // Точка для наглядности сетки
} else {
fmt.print("[]")
}
}
fmt.print("|\n")
}
fmt.println("+--------------------+")
fmt.printf("Blocks count: %d \n", len(blocks.data))
}
// =======================================================
// MAIN
// =======================================================
main :: proc() {
stablevec_init(&blocks, 256)
defer stablevec_destroy(&blocks)
clear_grid()
current := spawn_I()
fmt.print("\x1b[2J") // Полная очистка экрана один раз
for {
draw()
time.sleep(200 * time.Millisecond)
if can_move_down(current[:]) {
move_down(current[:])
} else {
clear_lines()
current = spawn_I()
valid_spawn := true
for h in current {
b := handle_ptr(h)
if grid[b.y][b.x] != h.id {
valid_spawn = false
}
}
if !valid_spawn {
fmt.println("GAME OVER")
break
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment