Created
February 4, 2026 17:23
-
-
Save Hammer2900/3f897416c7dd9775051aae290baa0d24 to your computer and use it in GitHub Desktop.
slot map in odin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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