diff --git a/skills/odin/LICENSE.md b/skills/odin/LICENSE.md new file mode 100644 index 0000000..10396cc --- /dev/null +++ b/skills/odin/LICENSE.md @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2026 codevoyant contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/skills/odin/SKILL.md b/skills/odin/SKILL.md new file mode 100644 index 0000000..2c2e287 --- /dev/null +++ b/skills/odin/SKILL.md @@ -0,0 +1,74 @@ +--- +name: odin +description: 'Odin programming language recipe lookup. Triggers on: "odin procedures", "odin allocators", "odin error handling", "odin unions", "odin strings", "odin dynamic arrays", "odin matrix math", "odin cli", "odin polymorphism", "how do I ... in odin", "odin recipe", "odin cheatsheet".' +license: MIT +compatibility: Works on Claude Code and any platform. No external tools required — a deterministic read-and-print reference. +argument-hint: '[topic]' +--- + +# odin + +Fast recipe lookup for the [Odin](https://odin-lang.org) language. Each topic is a small self-contained file under `references/recipes/`. Resolve the query to **one** file, then **read it and print it verbatim** — do not summarize, re-derive, or add commentary. This is a lookup, not a synthesis task. + +## Step 0: Parse query + +```bash +QUERY="$*" # everything after /odin +``` + +## Step 1: Route to one recipe and print it + +Resolve in this order, then `Read references/recipes/{slug}.md` and print its contents verbatim: + +1. **Exact topic name.** Lowercase `QUERY` and replace spaces with hyphens. If it equals a recipe slug (`procedures`, `returns`, `structs`, `conversions`, `defer`, `allocators`, `errors`, `unions`, `data-structures`, `math`, `strings`, `terminal`, `polymorphism`, `cli`, `resources`), use that file. (So `/odin defer`, `/odin cli`, `/odin data structures` route directly.) +2. **Keyword match.** Otherwise pick the **first** row whose keyword appears in `QUERY` as a **whole word/phrase** (word-boundary, case-insensitive — never a bare substring, so `def` never matches `defer`). + +| Keywords in query (whole-word) | Recipe file (`references/recipes/…`) | +|---|---| +| procedure, function, named return, default arg | `procedures.md` | +| return, tuple, multiple result, destructure | `returns.md` | +| struct, using, field, packed | `structs.md` | +| convert, conversion, cast, transmute, distinct, coerce | `conversions.md` | +| defer, cleanup, scope exit | `defer.md` | +| alloc, allocator, memory, free, delete, arena, context, temp, leak, tracking | `allocators.md` | +| error, or_return, or_else, result | `errors.md` | +| union, maybe, tagged, variant, type switch | `unions.md` | +| dynamic array, slice, map, append, data structure | `data-structures.md` | +| vector, matrix, linalg, math, dot, cross, normalize, swizzle | `math.md` | +| string, cstring, strconv, builder, rune, split, concat | `strings.md` | +| print, printf, terminal, tui, board, ansi, fullscreen, screen, color, fmt, stdin, input | `terminal.md` | +| polymorph, generic, parametric, where clause, proc group, any, overload | `polymorphism.md` | +| cli, command line, args, os.args, flags, main, build | `cli.md` | +| resource, learn, book, tutorial, link | `resources.md` | + +**No query, or `list` / `index` / `help` / `topics`:** print the index below (do not read any recipe file). + +**`all` / `everything` / `cheatsheet`:** print the index and say "pass a topic to print that recipe" — do not dump every file. + +### Index (print for no-query) + +``` +odin — recipe lookup. Usage: /odin + + procedures proc decl, default args, named returns + returns multiple returns & destructuring + structs struct literals, using, #packed + conversions cast / transmute / distinct; implicit vs explicit + defer defer & LIFO cleanup + allocators context allocator, temp, arena, tracking (freeing) + errors or_return / or_else, (value, Error) idiom + unions tagged unions, type switch, Maybe(T) + data-structures [dynamic]T, slices, map[K]V + math component-wise vectors, matrix[R,C]T, linalg + strings string/cstring, Builder, strconv, allocation notes + terminal fmt printing + full-screen ANSI board loop + polymorphism $T generics, where clauses, proc groups, any + cli os.args + core:flags + resources vetted docs, book, blogs, codebases + + e.g. /odin allocators · /odin matrix math · /odin cli + +Docs: https://odin-lang.org/docs/overview/ +``` + +If a query matches nothing, print the index and ask which topic. diff --git a/skills/odin/references/recipes/allocators.md b/skills/odin/references/recipes/allocators.md new file mode 100644 index 0000000..9568f34 --- /dev/null +++ b/skills/odin/references/recipes/allocators.md @@ -0,0 +1,41 @@ +# Odin — allocators & freeing + +`context.allocator` is implicit; most core procs take `allocator := context.allocator`. Pair every alloc with exactly one free. + +```odin +p := new(int); defer free(p) // single value: new ↔ free +s := make([]int, 6); defer delete(s) // slice/map/dynarray/string: make ↔ delete + +// Scratch memory: temp allocator + one bulk reclaim (no per-item delete) +for { // e.g. a frame loop + defer free_all(context.temp_allocator) + msg := fmt.tprintf("frame %d", i) // tprintf uses the temp allocator + scratch := make([]u8, 1024, context.temp_allocator) + _ = msg; _ = scratch +} + +// Arena: bump-allocate into a fixed buffer, reset all at once +import "core:mem" +buf: [4096]u8 +arena: mem.Arena +mem.arena_init(&arena, buf[:]) +context.allocator = mem.arena_allocator(&arena) +// ... allocate freely ... +free_all(context.allocator) // resets the bump pointer +``` + +Leak detection (dev builds) — tracking allocator: + +```odin +track: mem.Tracking_Allocator +mem.tracking_allocator_init(&track, context.allocator) +context.allocator = mem.tracking_allocator(&track) +defer { + for _, e in track.allocation_map do fmt.eprintfln("leak %v @ %v", e.size, e.location) + mem.tracking_allocator_destroy(&track) +} +``` + +`free` = one pointer · `delete` = slice/map/dynarray/string · `free_all` = everything from an arena/temp at once (don't also free/delete those items — double free). + +Docs: https://odin-lang.org/docs/overview/#implicit-context-system · https://pkg.odin-lang.org/core/mem/ diff --git a/skills/odin/references/recipes/cli.md b/skills/odin/references/recipes/cli.md new file mode 100644 index 0000000..ad271ee --- /dev/null +++ b/skills/odin/references/recipes/cli.md @@ -0,0 +1,36 @@ +# Odin — CLI programs + +`os.args` for raw args (`os.args[0]` is the program name); `core:flags` for declarative parsing. + +```odin +package main +import "core:fmt" +import "core:os" + +main :: proc() { + for arg, i in os.args do fmt.printf("[%d] %s\n", i, arg) +} +``` + +Declarative parsing with `core:flags` (struct + field tags): + +```odin +import "core:flags"; import "core:os" + +Options :: struct { + width: int `args:"name=w,required" usage:"board width"`, + name: string `usage:"player name"`, + verbose: bool `usage:"verbose"`, + file: string `args:"pos=0,required" usage:"input file"`, +} + +main :: proc() { + opt: Options + flags.parse_or_exit(&opt, os.args) // prints usage + exits on -h or error + fmt.printf("w=%d name=%s\n", opt.width, opt.name) +} +``` + +`.Odin` style = `-w:8 -verbose`; pass `style = .Unix` for `--w=8`. No subcommands — dispatch on `os.args[1]` yourself, then parse the rest. Build: `odin build . -out:game` (or `odin run .`). + +Docs: https://pkg.odin-lang.org/core/flags/ diff --git a/skills/odin/references/recipes/conversions.md b/skills/odin/references/recipes/conversions.md new file mode 100644 index 0000000..29faabe --- /dev/null +++ b/skills/odin/references/recipes/conversions.md @@ -0,0 +1,17 @@ +# Odin — type conversions + +`T(x)` or `cast(T)x` to convert; `transmute` to reinterpret bits; `distinct` for a new nominal type. + +```odin +i: int = 123 +f := f64(i) // T(x) — explicit conversion +u := cast(u32)f // cast(T)x — same effect, different spelling +bits := transmute(u32)f32(1) // reinterpret bits (same size) + +My_Int :: distinct int // new nominal type; My_Int != int, needs My_Int(x) +``` + +- **Implicit:** untyped constants (`X :: 42`, `1.0`, `"s"`) convert to any compatible target when exact — `x: f32 = 42`. +- **Explicit required:** between two *typed* values of different types (unlike C). `auto_cast v` forces it but is prototyping-only. + +Docs: https://odin-lang.org/docs/overview/#type-conversion diff --git a/skills/odin/references/recipes/data-structures.md b/skills/odin/references/recipes/data-structures.md new file mode 100644 index 0000000..32adad0 --- /dev/null +++ b/skills/odin/references/recipes/data-structures.md @@ -0,0 +1,30 @@ +# Odin — dynamic arrays, slices, maps + +Dynamic arrays grow (append/remove take `&arr`); slices are `low:high` (high exclusive); maps use comma-ok lookup. + +```odin +// dynamic array +xs: [dynamic]int +defer delete(xs) +append(&xs, 1, 2, 3) // variadic +ordered_remove(&xs, 0) // keep order, O(n) +unordered_remove(&xs, 0) // swap last in, O(1) +last := pop(&xs) +for v, i in xs { fmt.println(i, v) } // value first, index second +clear(&xs) // len→0, keeps cap + +// fixed array + slice (high bound exclusive) +fixed := [?]int{1, 2, 3, 4, 5} // [?] infers length +mid := fixed[1:4] // {2,3,4} +heap := make([]int, 6); defer delete(heap) + +// map +m := make(map[string]int); defer delete(m) +m["bob"] = 2 +v, ok := m["bob"] // comma-ok lookup +_ = "bob" in m // presence test +delete_key(&m, "bob") +for k, val in m { fmt.println(k, val) } +``` + +Docs: https://odin-lang.org/docs/overview/#dynamic-arrays · https://odin-lang.org/docs/overview/#maps diff --git a/skills/odin/references/recipes/defer.md b/skills/odin/references/recipes/defer.md new file mode 100644 index 0000000..d466881 --- /dev/null +++ b/skills/odin/references/recipes/defer.md @@ -0,0 +1,16 @@ +# Odin — defer + +`defer stmt` runs at scope exit, in **LIFO** order. Put cleanup right next to acquisition. + +```odin +f, _ := os.open("x.txt") +defer os.close(f) // runs at scope exit + +defer fmt.println("1") // LIFO → prints 3, 2, 1 +defer fmt.println("2") +defer fmt.println("3") + +defer { a(); b() } // defer a block +``` + +Docs: https://odin-lang.org/docs/overview/#defer diff --git a/skills/odin/references/recipes/errors.md b/skills/odin/references/recipes/errors.md new file mode 100644 index 0000000..2b2c2e3 --- /dev/null +++ b/skills/odin/references/recipes/errors.md @@ -0,0 +1,26 @@ +# Odin — error handling + +Errors are plain values: return `(value, Error)` where `Error`'s zero value means success. `or_return` propagates; `or_else` supplies a default. + +```odin +Error :: enum { None, Bad } // .None == success (zero value) + +step :: proc() -> (int, Error) { return 1, .None } + +run :: proc() -> (n: int, err: Error) { + n = step() or_return // if err != nil, return (0, err) now + n += 1 + return +} + +// propagate a file read, then default a map lookup +load :: proc(path: string) -> (data: []u8, err: os.Error) { + data = os.read_entire_file(path) or_return + return +} +port := config["port"] or_else 8080 // or_else = fallback value +``` + +See also: `unions.md` (`Maybe(T)` / `.?`). + +Docs: https://odin-lang.org/docs/overview/#or_return-operator diff --git a/skills/odin/references/recipes/math.md b/skills/odin/references/recipes/math.md new file mode 100644 index 0000000..5fc2ae8 --- /dev/null +++ b/skills/odin/references/recipes/math.md @@ -0,0 +1,34 @@ +# Odin — vector & matrix math + +Fixed arrays are component-wise; `matrix[R,C]T` is built in and `*` is real matrix multiply. Dot/cross/length live in `core:math/linalg`. + +```odin +import "core:math/linalg" +Vec3 :: [3]f32 + +a := Vec3{1, 4, 9} +b := Vec3{2, 4, 8} +c := a + b // {3, 8, 17} component-wise +d := a * b // {2, 16, 72} component-wise (NOT dot) +e := a * 2 // scalar broadcast +xy := a.xy // swizzle + +dp := linalg.dot(a, b) // scalar +cp := linalg.cross(a, b) // Vec3 +n := linalg.normalize(a) // unit vector +len := linalg.length(a) + +// matrices +m := matrix[2, 2]f32{1, 2, 3, 4} +v := [2]f32{5, 6} +mv := m * v // matrix × vector → [2]f32 + +id := linalg.MATRIX4F32_IDENTITY +T := linalg.matrix4_translate(Vec3{1, 2, 3}) +// linalg also has matrix4_rotate(angle_rad, axis) / matrix4_scale; compose with `*` +world := T * id +``` + +Note: `matrix4_rotate` argument order is conventionally `(angle_radians, axis)` — confirm against your compiler's `pkg.odin-lang.org`. + +Docs: https://odin-lang.org/docs/overview/#array-programming · https://pkg.odin-lang.org/core/math/linalg/ diff --git a/skills/odin/references/recipes/polymorphism.md b/skills/odin/references/recipes/polymorphism.md new file mode 100644 index 0000000..8c0d32e --- /dev/null +++ b/skills/odin/references/recipes/polymorphism.md @@ -0,0 +1,33 @@ +# Odin — polymorphism + +No methods/inheritance. Three tools: parametric generics (`$`), `where` constraints, and proc groups (overloading). + +```odin +// $T inferred from args; works on any addable type +add :: proc(a, b: $T) -> T { return a + b } + +// $T: typeid — pass a *type*; $N — compile-time constant +make_pair :: proc($T: typeid) -> [2]T { return {} } +first_n :: proc(s: []$E, $N: int) -> [N]E { r: [N]E; for i in 0.. T where intrinsics.type_is_numeric(T) { + total: T + for v in s do total += v + return total +} + +// proc group = compile-time overloading, dispatched by arg types +show_int :: proc(x: int) { fmt.println("int", x) } +show_str :: proc(x: string) { fmt.println("str", x) } +show :: proc{show_int, show_str} +// show(3) → show_int ; show("hi") → show_str + +// `any` = runtime dynamic value (ptr + typeid), for logging etc. +dump :: proc(v: any) { fmt.printf("%T = %v\n", v, v) } +``` + +Subtype-style reuse is `using` in a struct (see `structs.md`). + +Docs: https://odin-lang.org/docs/overview/#parametric-polymorphism diff --git a/skills/odin/references/recipes/procedures.md b/skills/odin/references/recipes/procedures.md new file mode 100644 index 0000000..426624f --- /dev/null +++ b/skills/odin/references/recipes/procedures.md @@ -0,0 +1,26 @@ +# Odin — procedures + +`name :: proc(params) -> ret { }`. Default args, named returns (naked `return` allowed), named args at call site. + +```odin +package main +import "core:fmt" + +add :: proc(a, b: int) -> int { return a + b } + +// default args + named returns +stats :: proc(x: int, scale := 2) -> (lo, hi: int) { + lo = x + hi = x * scale + return +} + +main :: proc() { + fmt.println(add(2, 3)) // 5 + lo, hi := stats(4) // positional + _ = stats(x = 4, scale = 3) // named args at call site + fmt.println(lo, hi) +} +``` + +Docs: https://odin-lang.org/docs/overview/#procedures diff --git a/skills/odin/references/recipes/resources.md b/skills/odin/references/recipes/resources.md new file mode 100644 index 0000000..5ee217f --- /dev/null +++ b/skills/odin/references/recipes/resources.md @@ -0,0 +1,37 @@ +# Odin — vetted resources + +All links fetched/searched and confirmed live (2026-07). Authority noted. + +## Official (start here) +- **Language Overview** — https://odin-lang.org/docs/overview/ — the canonical tour (syntax → types → procedures → the `context`/allocator system). Read end-to-end first. +- **Package docs** — https://pkg.odin-lang.org/ — browsable API for `base`/`core`/`vendor`. +- **FAQ / rationale** — https://odin-lang.org/docs/faq/ — why no methods, no exceptions, no package manager, manual memory. (There is no separate formal spec — Overview + FAQ + `core/` are it.) +- **`demo.odin`** — https://github.com/odin-lang/Odin/blob/master/examples/demo/demo.odin — one heavily-commented file covering nearly every feature. Skim after the overview. +- **`core/` source** — https://github.com/odin-lang/Odin/tree/master/core — best large idiomatic codebase; read it to learn conventions. +- **`examples/`** — https://github.com/odin-lang/Odin/tree/master/examples — small runnable snippets. +- **Repo / showcase** — https://github.com/odin-lang/Odin · https://odin-lang.org/showcase/ + +## Books +- **Understanding the Odin Programming Language — Karl Zylinski (2024, updated 2026)** — https://odinbook.com/ (buy: store.zylinski.se · zylinski.itch.io/odinbook). The definitive structured resource; use it as your spine. *(You already have it in `~/…/Books/Computer Science/Odin/`.)* + +## Tutorials / blogs +- **gingerBill's articles** (Odin's creator) — https://www.gingerbill.org/article/ — especially the 6-part **"Memory Allocation Strategies"** series; internalizing this is the biggest shift from GC languages. +- **Karl Zylinski's blog** — https://zylinski.se/posts/ — practical patterns (arenas, handle-based maps, bindings). Free written intro: https://zylinski.se/posts/introduction-to-odin/ +- **Karl Zylinski — YouTube** — https://www.youtube.com/@karl_zylinski — games without an engine, Odin + Raylib. +- **Odin News** — https://odin-lang.org/news/ — releases/announcements. + +## Community +- **Discord** (primary hub) — https://discord.gg/vafXTdubwr +- **Forum** — https://forum.odin-lang.org/ · **community index** — https://odin-lang.org/community/ +- **awesome-odin** — https://github.com/jakubtomsu/awesome-odin — curated libs/bindings/tutorials. + +## Reference codebases (learn by reading) +- **Odin `core/`** — the canonical idioms (above). +- **Raylib hot-reload template — Karl Zylinski** — https://github.com/karl-zylinski/odin-raylib-hot-reload-game-template — standard game scaffold (shipped CAT & ONION). +- **karl2d** — https://github.com/karl-zylinski/karl2d — readable beginner 2D lib. +- **breakout / snake tutorials** — https://github.com/karl-zylinski/breakout · https://github.com/karl-zylinski/snake-tutorial-code — step-by-step games + videos. +- **Dungeon of Quake** — https://github.com/jakubtomsu/dungeon-of-quake — complete retro FPS. +- **VirtualXT** — https://github.com/virtualxt/virtualxt — PC/XT emulator (systems example). + +## Suggested path (experienced programmer) +1. Read the **Overview** end to end. 2. Skim **demo.odin**. 3. Work through **Karl's book** (you own it). 4. Read gingerBill's **Memory Allocation Strategies**. 5. Read **`core/`** for idioms. 6. Build with the **Raylib hot-reload template**; keep **Discord** open. diff --git a/skills/odin/references/recipes/returns.md b/skills/odin/references/recipes/returns.md new file mode 100644 index 0000000..d2e0ddc --- /dev/null +++ b/skills/odin/references/recipes/returns.md @@ -0,0 +1,17 @@ +# Odin — multiple returns & destructuring + +Return >1 value; destructure at the call site; `_` ignores. Two idiomatic shapes: `(value, ok)` and `(value, error)`. + +```odin +swap :: proc(x, y: int) -> (int, int) { return y, x } + +a, b := swap(1, 2) // destructure both +_, second := swap(1, 2) // `_` ignores a value + +find :: proc() -> (val: int, ok: bool) { return 3, true } // (value, ok) +load :: proc() -> (val: int, err: Error) { return 3, .None } // (value, error) +``` + +See also: `errors.md` (`or_return` / `or_else`). + +Docs: https://odin-lang.org/docs/overview/#multiple-results diff --git a/skills/odin/references/recipes/strings.md b/skills/odin/references/recipes/strings.md new file mode 100644 index 0000000..690901f --- /dev/null +++ b/skills/odin/references/recipes/strings.md @@ -0,0 +1,37 @@ +# Odin — strings + +`string` = immutable UTF-8 byte view (ptr+len); `cstring` = NUL-terminated. Iterating yields **runes**. Watch which calls allocate. + +```odin +import "core:strings" +import "core:strconv" + +s := "Hellope" +for r in s { fmt.println(r) } // r is a rune +for r, i in s { fmt.println(i, r) } + +// no allocation: +_ = strings.contains(s, "ll") +_ = strings.has_prefix(s, "He") +t := strings.trim_space(" hi ") // subslice view + +// allocates → delete the result +parts, _ := strings.split("a,b,c", ","); defer delete(parts) +joined := strings.concatenate([]string{"a","b"}); defer delete(joined) +msg := fmt.aprintf("x=%d", 42); defer delete(msg) // tprintf = temp, no delete + +// Builder — accumulate then read a view +b := strings.builder_make(); defer strings.builder_destroy(&b) +strings.write_string(&b, "hello ") +strings.write_string(&b, "world") +out := strings.to_string(b) // view into builder, no alloc + +n := strconv.atoi("123") // string → int +buf: [8]u8 +str := strconv.itoa(buf[:], 42) // int → string (into buf) +// atoi/itoa are being deprecated → prefer strconv.parse_int / strconv.append_int +``` + +Allocating (delete/destroy): `split`, `concatenate`, `clone`, `builder_make`, `fmt.aprintf`. Non-allocating: `trim_space`, `to_string`, `contains`, `has_prefix`, `fmt.tprintf`. + +Docs: https://pkg.odin-lang.org/core/strings/ diff --git a/skills/odin/references/recipes/structs.md b/skills/odin/references/recipes/structs.md new file mode 100644 index 0000000..0cdcaf5 --- /dev/null +++ b/skills/odin/references/recipes/structs.md @@ -0,0 +1,22 @@ +# Odin — structs + +`Name :: struct { … }`. Named-field literals use `=` (not `:`). `using` promotes fields — Odin's substitute for inheritance. + +```odin +Vec3 :: struct { x, y, z: f32 } + +v1 := Vec3{x = 1, y = 2, z = 3} // named fields use `=` +v2 := Vec3{1, 2, 3} // positional (all or none) +v3 := Vec3{} // zero value (every field zeroed) + +// `using` promotes fields: e.x reaches pos.x +Entity :: struct { + using pos: Vec3, + hp: int, +} +e: Entity; e.x = 10 // promoted access + +Packed :: struct #packed { a: u8, b: u32 } // no padding +``` + +Docs: https://odin-lang.org/docs/overview/#structs diff --git a/skills/odin/references/recipes/terminal.md b/skills/odin/references/recipes/terminal.md new file mode 100644 index 0000000..cc2aa07 --- /dev/null +++ b/skills/odin/references/recipes/terminal.md @@ -0,0 +1,45 @@ +# Odin — printing & a full-screen board + +```odin +import "core:fmt" +fmt.println("a", "b") // space-separated + newline +fmt.printf("%d %v %s %t\n", 42, x, "hi", true) // %v default, %d int, %s str, %t bool, %x hex, %T type +fmt.eprintln("to stderr") +// aprintf → heap string (delete it); tprintf → temp string; NOTE: there is no fmt.sprintf +``` + +Full-screen TUI (e.g. a board game) via standard ANSI escapes printed with `fmt` — enter the alternate screen, then each frame clear→home→draw: + +```odin +import "core:fmt" +CLEAR :: "\e[2J"; HOME :: "\e[H" +ALT_ON :: "\e[?1049h"; ALT_OFF :: "\e[?1049l"; HIDE :: "\e[?25l"; SHOW :: "\e[?25h" +move :: proc(r, c: int) { fmt.printf("\e[%d;%dH", r, c) } // 1-based cursor move + +render :: proc(board: ^[8][8]rune) { + fmt.print(CLEAR, HOME) + for row in board { + for cell in row do fmt.printf("%c ", cell) + fmt.print("\r\n") + } + fmt.printf("\e[32mgreen\e[0m\n") // \e[31m..\e[0m for color +} + +main :: proc() { + fmt.print(ALT_ON, HIDE) + defer fmt.print(SHOW, ALT_OFF) // always restore the terminal + board: [8][8]rune + // loop: render(&board); read input; update... +} +``` + +Caveat: stdin is **line-buffered/echoed** by default. Single-keypress input (arrow keys, no echo) needs raw mode — `core:sys/posix` termios (`tcsetattr`, clear `ECHO`/`ICANON`) or `core:sys/windows` `SetConsoleMode`; there is no stdlib `enable_raw_mode`. Line input: + +```odin +import "core:bufio"; import "core:os" +sc: bufio.Scanner +bufio.scanner_init(&sc, os.to_stream(os.stdin)) // classic API: os.stream_from_handle(os.stdin) +for bufio.scanner_scan(&sc) { fmt.println(bufio.scanner_text(&sc)) } +``` + +Docs: https://pkg.odin-lang.org/core/fmt/ · https://pkg.odin-lang.org/core/bufio/ diff --git a/skills/odin/references/recipes/unions.md b/skills/odin/references/recipes/unions.md new file mode 100644 index 0000000..29b64a3 --- /dev/null +++ b/skills/odin/references/recipes/unions.md @@ -0,0 +1,30 @@ +# Odin — unions + +`Name :: union { … }` is a tagged union (nil when unset). Branch with a type switch; extract with a type assertion. + +```odin +Value :: union { int, string, bool } // tagged; nil when unset +v: Value = "hi" + +switch x in v { // type switch binds x +case int: fmt.println("int", x) +case string: fmt.println("str", x) +case: fmt.println("nil/other") +} + +s := v.(string) // type assert — panics if wrong +s, ok := v.(string) // safe form: ok=false instead of panic + +Flag :: union #no_nil { bool, string } // no nil state; first variant is zero value +``` + +`Maybe(T)` is a one-variant union — the idiomatic optional: + +```odin +m: Maybe(int) // nil +m = 7 +x, ok := m.? // safe unwrap +y := m.? or_else -1 // default unwrap +``` + +Docs: https://odin-lang.org/docs/overview/#unions