@tanagram/cli 0.4.14 → 0.4.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/npm/darwin-arm64/LICENSE +21 -0
- package/dist/npm/darwin-arm64/README.md +267 -0
- package/dist/npm/darwin-arm64/tanagram +0 -0
- package/dist/npm/darwin-x64/LICENSE +21 -0
- package/dist/npm/darwin-x64/README.md +267 -0
- package/dist/npm/darwin-x64/tanagram +0 -0
- package/dist/npm/linux-arm64/LICENSE +21 -0
- package/dist/npm/linux-arm64/README.md +267 -0
- package/dist/npm/linux-arm64/tanagram +0 -0
- package/dist/npm/linux-x64/LICENSE +21 -0
- package/dist/npm/linux-x64/README.md +267 -0
- package/dist/npm/linux-x64/tanagram +0 -0
- package/dist/npm/tanagram_0.4.18_darwin_amd64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.18_darwin_arm64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.18_linux_amd64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.18_linux_arm64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.18_windows_amd64.zip +0 -0
- package/dist/npm/win32-x64/LICENSE +21 -0
- package/dist/npm/win32-x64/README.md +267 -0
- package/dist/npm/win32-x64/tanagram.exe +0 -0
- package/install.js +176 -22
- package/main.go +0 -11
- package/package.json +5 -4
- package/tui/welcome.go +1 -13
- package/tui/puzzle.go +0 -694
- package/tui/renderer.go +0 -359
package/tui/renderer.go
DELETED
|
@@ -1,359 +0,0 @@
|
|
|
1
|
-
package tui
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"encoding/json"
|
|
5
|
-
"math"
|
|
6
|
-
"os"
|
|
7
|
-
|
|
8
|
-
"github.com/charmbracelet/lipgloss"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
// TanagramRenderer handles rendering tangram pieces to an ASCII canvas
|
|
12
|
-
type TanagramRenderer struct {
|
|
13
|
-
width int
|
|
14
|
-
height int
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// NewTanagramRenderer creates a new renderer with the given dimensions
|
|
18
|
-
func NewTanagramRenderer(width, height int) *TanagramRenderer {
|
|
19
|
-
return &TanagramRenderer{
|
|
20
|
-
width: width,
|
|
21
|
-
height: height,
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// RenderPiecesToCanvas renders a slice of pieces to an ASCII canvas
|
|
26
|
-
func (r *TanagramRenderer) RenderPiecesToCanvas(pieces []TangramPiece) [][]int {
|
|
27
|
-
canvas := make([][]int, r.height)
|
|
28
|
-
for i := range canvas {
|
|
29
|
-
canvas[i] = make([]int, r.width)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Render each piece
|
|
33
|
-
for idx, piece := range pieces {
|
|
34
|
-
r.renderPieceToCanvas(&canvas, piece, idx)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return canvas
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// renderPieceToCanvas renders a single piece to the canvas
|
|
41
|
-
func (r *TanagramRenderer) renderPieceToCanvas(canvas *[][]int, piece TangramPiece, pieceIndex int) {
|
|
42
|
-
points := r.getPiecePoints(piece)
|
|
43
|
-
|
|
44
|
-
for _, point := range points {
|
|
45
|
-
x := piece.Position.X + point.X
|
|
46
|
-
y := piece.Position.Y + point.Y
|
|
47
|
-
|
|
48
|
-
if y >= 0 && y < len(*canvas) && x >= 0 && x < len((*canvas)[0]) {
|
|
49
|
-
(*canvas)[y][x] = pieceIndex + 1
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// getPiecePoints generates all points for a piece (handles rotation, flip, aspect ratio)
|
|
55
|
-
func (r *TanagramRenderer) getPiecePoints(piece TangramPiece) []Position {
|
|
56
|
-
var basePoints []Position
|
|
57
|
-
|
|
58
|
-
// Generate pieces in "true" square proportions
|
|
59
|
-
switch piece.Type {
|
|
60
|
-
case SmallTriangle:
|
|
61
|
-
height := 8
|
|
62
|
-
for y := 0; y < height; y++ {
|
|
63
|
-
for x := 0; x <= height-1-y; x++ {
|
|
64
|
-
basePoints = append(basePoints, Position{x, y})
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
case MediumTriangle:
|
|
68
|
-
height := 10
|
|
69
|
-
for y := 0; y < height; y++ {
|
|
70
|
-
for x := 0; x <= height-1-y; x++ {
|
|
71
|
-
basePoints = append(basePoints, Position{x, y})
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
case LargeTriangle:
|
|
75
|
-
height := 14
|
|
76
|
-
for y := 0; y < height; y++ {
|
|
77
|
-
for x := 0; x <= height-1-y; x++ {
|
|
78
|
-
basePoints = append(basePoints, Position{x, y})
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
case Square:
|
|
82
|
-
size := 9
|
|
83
|
-
for y := 0; y < size; y++ {
|
|
84
|
-
for x := 0; x < size; x++ {
|
|
85
|
-
basePoints = append(basePoints, Position{x, y})
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
case Parallelogram:
|
|
89
|
-
height := 5
|
|
90
|
-
baseLength := 11
|
|
91
|
-
for y := 0; y < height; y++ {
|
|
92
|
-
offset := height - 1 - y
|
|
93
|
-
for x := 0; x < baseLength; x++ {
|
|
94
|
-
basePoints = append(basePoints, Position{x + offset, y})
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Apply rotation
|
|
100
|
-
rotatedPoints := r.rotatePoints(basePoints, piece.Rotation)
|
|
101
|
-
|
|
102
|
-
// Fill gaps
|
|
103
|
-
rotatedPoints = r.fillShape(rotatedPoints)
|
|
104
|
-
|
|
105
|
-
// Apply flip
|
|
106
|
-
if piece.Flipped {
|
|
107
|
-
rotatedPoints = r.flipPoints(rotatedPoints)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Apply aspect ratio correction
|
|
111
|
-
aspectCorrectedPoints := make([]Position, 0, len(rotatedPoints)*2)
|
|
112
|
-
for _, p := range rotatedPoints {
|
|
113
|
-
aspectCorrectedPoints = append(aspectCorrectedPoints, Position{p.X * 2, p.Y})
|
|
114
|
-
aspectCorrectedPoints = append(aspectCorrectedPoints, Position{p.X*2 + 1, p.Y})
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return aspectCorrectedPoints
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// rotatePoints rotates points by the given angle
|
|
121
|
-
func (r *TanagramRenderer) rotatePoints(points []Position, angle int) []Position {
|
|
122
|
-
if angle == 0 {
|
|
123
|
-
return points
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
var rotated []Position
|
|
127
|
-
rad := float64(angle) * math.Pi / 180.0
|
|
128
|
-
cos := math.Cos(rad)
|
|
129
|
-
sin := math.Sin(rad)
|
|
130
|
-
|
|
131
|
-
var sumX, sumY int
|
|
132
|
-
for _, p := range points {
|
|
133
|
-
sumX += p.X
|
|
134
|
-
sumY += p.Y
|
|
135
|
-
}
|
|
136
|
-
centerX := float64(sumX) / float64(len(points))
|
|
137
|
-
centerY := float64(sumY) / float64(len(points))
|
|
138
|
-
|
|
139
|
-
for _, p := range points {
|
|
140
|
-
x := float64(p.X) - centerX
|
|
141
|
-
y := float64(p.Y) - centerY
|
|
142
|
-
|
|
143
|
-
newX := x*cos - y*sin
|
|
144
|
-
newY := x*sin + y*cos
|
|
145
|
-
|
|
146
|
-
rotated = append(rotated, Position{
|
|
147
|
-
X: int(math.Round(newX + centerX)),
|
|
148
|
-
Y: int(math.Round(newY + centerY)),
|
|
149
|
-
})
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Normalize to positive coordinates
|
|
153
|
-
minX, minY := rotated[0].X, rotated[0].Y
|
|
154
|
-
for _, p := range rotated {
|
|
155
|
-
if p.X < minX {
|
|
156
|
-
minX = p.X
|
|
157
|
-
}
|
|
158
|
-
if p.Y < minY {
|
|
159
|
-
minY = p.Y
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
for i := range rotated {
|
|
164
|
-
rotated[i].X -= minX
|
|
165
|
-
rotated[i].Y -= minY
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return rotated
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// fillShape fills gaps in rotated shapes
|
|
172
|
-
func (r *TanagramRenderer) fillShape(points []Position) []Position {
|
|
173
|
-
if len(points) == 0 {
|
|
174
|
-
return points
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
minX, maxX := points[0].X, points[0].X
|
|
178
|
-
minY, maxY := points[0].Y, points[0].Y
|
|
179
|
-
for _, p := range points {
|
|
180
|
-
if p.X < minX {
|
|
181
|
-
minX = p.X
|
|
182
|
-
}
|
|
183
|
-
if p.X > maxX {
|
|
184
|
-
maxX = p.X
|
|
185
|
-
}
|
|
186
|
-
if p.Y < minY {
|
|
187
|
-
minY = p.Y
|
|
188
|
-
}
|
|
189
|
-
if p.Y > maxY {
|
|
190
|
-
maxY = p.Y
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
filledMap := make(map[Position]bool)
|
|
195
|
-
|
|
196
|
-
// Fill horizontally
|
|
197
|
-
for y := minY; y <= maxY; y++ {
|
|
198
|
-
var xCoords []int
|
|
199
|
-
for _, p := range points {
|
|
200
|
-
if p.Y == y {
|
|
201
|
-
xCoords = append(xCoords, p.X)
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if len(xCoords) == 0 {
|
|
205
|
-
continue
|
|
206
|
-
}
|
|
207
|
-
rowMinX, rowMaxX := xCoords[0], xCoords[0]
|
|
208
|
-
for _, x := range xCoords {
|
|
209
|
-
if x < rowMinX {
|
|
210
|
-
rowMinX = x
|
|
211
|
-
}
|
|
212
|
-
if x > rowMaxX {
|
|
213
|
-
rowMaxX = x
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
for x := rowMinX; x <= rowMaxX; x++ {
|
|
217
|
-
filledMap[Position{x, y}] = true
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Fill vertically
|
|
222
|
-
for x := minX; x <= maxX; x++ {
|
|
223
|
-
var yCoords []int
|
|
224
|
-
for p := range filledMap {
|
|
225
|
-
if p.X == x {
|
|
226
|
-
yCoords = append(yCoords, p.Y)
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
if len(yCoords) == 0 {
|
|
230
|
-
continue
|
|
231
|
-
}
|
|
232
|
-
colMinY, colMaxY := yCoords[0], yCoords[0]
|
|
233
|
-
for _, y := range yCoords {
|
|
234
|
-
if y < colMinY {
|
|
235
|
-
colMinY = y
|
|
236
|
-
}
|
|
237
|
-
if y > colMaxY {
|
|
238
|
-
colMaxY = y
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
for y := colMinY; y <= colMaxY; y++ {
|
|
242
|
-
filledMap[Position{x, y}] = true
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
filledPoints := make([]Position, 0, len(filledMap))
|
|
247
|
-
for p := range filledMap {
|
|
248
|
-
filledPoints = append(filledPoints, p)
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return filledPoints
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// flipPoints flips points horizontally
|
|
255
|
-
func (r *TanagramRenderer) flipPoints(points []Position) []Position {
|
|
256
|
-
if len(points) == 0 {
|
|
257
|
-
return points
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
maxX := points[0].X
|
|
261
|
-
for _, p := range points {
|
|
262
|
-
if p.X > maxX {
|
|
263
|
-
maxX = p.X
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
var flipped []Position
|
|
268
|
-
for _, p := range points {
|
|
269
|
-
flipped = append(flipped, Position{
|
|
270
|
-
X: maxX - p.X,
|
|
271
|
-
Y: p.Y,
|
|
272
|
-
})
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return flipped
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// CanvasToString converts a canvas to a styled string
|
|
279
|
-
func (r *TanagramRenderer) CanvasToString(canvas [][]int, pieces []TangramPiece) string {
|
|
280
|
-
var lines []string
|
|
281
|
-
for y, row := range canvas {
|
|
282
|
-
var line string
|
|
283
|
-
for x := range row {
|
|
284
|
-
char, color := r.getCellDisplay(canvas, x, y, pieces)
|
|
285
|
-
if color != "" {
|
|
286
|
-
style := lipgloss.NewStyle().Foreground(color)
|
|
287
|
-
line += style.Render(string(char))
|
|
288
|
-
} else {
|
|
289
|
-
line += string(char)
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
lines = append(lines, line)
|
|
293
|
-
}
|
|
294
|
-
return lipgloss.JoinVertical(lipgloss.Left, lines...)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// getCellDisplay returns the character and color for a cell
|
|
298
|
-
func (r *TanagramRenderer) getCellDisplay(canvas [][]int, x, y int, pieces []TangramPiece) (rune, lipgloss.Color) {
|
|
299
|
-
pieceID := canvas[y][x]
|
|
300
|
-
|
|
301
|
-
if pieceID == 0 {
|
|
302
|
-
return ' ', ""
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
pieceIndex := pieceID - 1
|
|
306
|
-
if pieceIndex >= len(pieces) {
|
|
307
|
-
return ' ', ""
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
piece := pieces[pieceIndex]
|
|
311
|
-
return '█', piece.Color
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// LoadTanagramConfig loads a tangram configuration from a JSON file
|
|
315
|
-
func LoadTanagramConfig(filename string) ([]TangramPiece, error) {
|
|
316
|
-
data, err := os.ReadFile(filename)
|
|
317
|
-
if err != nil {
|
|
318
|
-
return nil, err
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
type SavedPiece struct {
|
|
322
|
-
ID int `json:"id"`
|
|
323
|
-
Type string `json:"type"`
|
|
324
|
-
Position Position `json:"position"`
|
|
325
|
-
Rotation int `json:"rotation"`
|
|
326
|
-
Flipped bool `json:"flipped"`
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
var savedPieces []SavedPiece
|
|
330
|
-
err = json.Unmarshal(data, &savedPieces)
|
|
331
|
-
if err != nil {
|
|
332
|
-
return nil, err
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Color mapping for each piece ID
|
|
336
|
-
colors := map[int]lipgloss.Color{
|
|
337
|
-
1: "#BA68C8", // Purple
|
|
338
|
-
2: "#FFB74D", // Orange
|
|
339
|
-
3: "#42A5F5", // Blue
|
|
340
|
-
4: "#EF4444", // Red
|
|
341
|
-
5: "#FF9800", // Orange
|
|
342
|
-
6: "#7E57C2", // Purple
|
|
343
|
-
7: "#66BB6A", // Green
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
var pieces []TangramPiece
|
|
347
|
-
for _, saved := range savedPieces {
|
|
348
|
-
pieces = append(pieces, TangramPiece{
|
|
349
|
-
ID: saved.ID,
|
|
350
|
-
Type: PieceType(saved.Type),
|
|
351
|
-
Color: colors[saved.ID],
|
|
352
|
-
Position: saved.Position,
|
|
353
|
-
Rotation: saved.Rotation,
|
|
354
|
-
Flipped: saved.Flipped,
|
|
355
|
-
})
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return pieces, nil
|
|
359
|
-
}
|