| 42 | }; |
| 43 | |
| 44 | class Grid { |
| 45 | constructor(w, h) { |
| 46 | this.w = w; |
| 47 | this.h = h; |
| 48 | this.cells = Array.from({ length: h }, () => Array(w).fill(' ')); |
| 49 | } |
| 50 | |
| 51 | put(x, y, char) { |
| 52 | if (x >= 0 && x < this.w && y >= 0 && y < this.h) { |
| 53 | this.cells[y][x] = char; |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | text(x, y, str) { |
| 58 | for (let i = 0; i < str.length; i++) { |
| 59 | this.put(x + i, y, str[i]); |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | box(x, y, w, h, label, style = 'single') { |
| 64 | const s = STYLES[style] || STYLES.single; |
| 65 | |
| 66 | // Corners |
| 67 | this.put(x, y, s.tl); |
| 68 | this.put(x + w - 1, y, s.tr); |
| 69 | this.put(x, y + h - 1, s.bl); |
| 70 | this.put(x + w - 1, y + h - 1, s.br); |
| 71 | |
| 72 | // Top and bottom borders |
| 73 | for (let i = 1; i < w - 1; i++) { |
| 74 | this.put(x + i, y, s.h); |
| 75 | this.put(x + i, y + h - 1, s.h); |
| 76 | } |
| 77 | |
| 78 | // Side borders |
| 79 | for (let j = 1; j < h - 1; j++) { |
| 80 | this.put(x, y + j, s.v); |
| 81 | this.put(x + w - 1, y + j, s.v); |
| 82 | } |
| 83 | |
| 84 | // Label (centered, supports multiline) |
| 85 | const lines = label.split('\n'); |
| 86 | const startY = y + Math.floor((h - lines.length) / 2); |
| 87 | for (let li = 0; li < lines.length; li++) { |
| 88 | const line = lines[li]; |
| 89 | const startX = x + Math.floor((w - line.length) / 2); |
| 90 | this.text(startX, startY + li, line); |
| 91 | } |
| 92 | |
| 93 | return { x, y, w, h }; |
| 94 | } |
| 95 | |
| 96 | hArrow(x1, x2, y, label, labelY) { |
| 97 | const dir = x2 > x1 ? 1 : -1; |
| 98 | for (let x = x1; x !== x2; x += dir) { |
| 99 | this.put(x, y, '-'); |
| 100 | } |
| 101 | this.put(x2, y, dir > 0 ? '>' : '<'); |
nothing calls this directly
no outgoing calls
no test coverage detected