1 package fibs
2
3 import (
4 "bytes"
5 "fmt"
6 "log"
7 "strconv"
8 "strings"
9 "sync"
10 )
11
12 const (
13 BoxDrawingsLightVertical = '|'
14 )
15
16 const (
17 StateLength = iota
18 StatePlayerScore
19 StateOpponentScore
20 StateBoardSpace0
21 )
22
23 const (
24 StatePlayerName = iota
25 StateOpponentName
26 )
27
28 const (
29 StateTurn = 29 + iota
30 StatePlayerDice1
31 StatePlayerDice2
32 StateOpponentDice1
33 StateOpponentDice2
34 StateDoublingValue
35 StatePlayerMayDouble
36 StateOpponentMayDouble
37 StateWasDoubled
38 StatePlayerColor
39 StateDirection
40 StateObsoleteHome
41 StateObsoleteBar
42 StatePlayerHome
43 StateOpponentHome
44 StatePlayerBar
45 StateOpponentBar
46 StateMovablePieces
47 StateObsoletePlayerForced
48 StateObsoleteOpponentForced
49 StateRedoubles
50 )
51
52 const (
53 SpaceUnknown = -1
54 )
55
56 const initialState = "FIBS:Welcome:5:0:2:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:-1:0:0:0:0:1:1:1:0:1:-1:0:25:0:0:0:0:4:0:0:0"
57
58 var boardTopWhite = []byte("+13-14-15-16-17-18-+---+19-20-21-22-23-24-+")
59 var boardBottomWhite = []byte("+12-11-10--9--8--7-+---+-6--5--4--3--2--1-+")
60
61 var boardTopBlack = []byte("+-1--2--3--4--5--6-+---+-7--8--9-10-11-12-+")
62 var boardBottomBlack = []byte("+24-23-22-21-20-19-+---+18-17-16-15-14-13-+")
63
64 type Board struct {
65 client *Client
66
67 state string
68
69 s []string
70 v []int
71
72 moves [][2]int
73 movesColor int
74
75 validMoves map[int][][]int
76
77 from map[int]int
78 to map[int]int
79
80 selectedNum int
81 selectedSpace int
82
83 premove [][2]int
84 Premovefrom map[int]int
85 Premoveto map[int]int
86
87 dragFromX int
88 dragFromY int
89
90 sync.Mutex
91 }
92
93 func NewBoard(client *Client) *Board {
94 b := &Board{
95 client: client,
96 s: make([]string, 52),
97 v: make([]int, 50),
98 }
99
100 b.ResetMoves()
101 b.ResetPreMoves()
102
103 b.SetState(initialState)
104
105
106
115
116 return b
117 }
118
119
120 func (b *Board) GetStringState() []string {
121 b.Lock()
122 defer b.Unlock()
123 return b.s
124 }
125
126 func (b *Board) GetIntState() []int {
127 b.Lock()
128 defer b.Unlock()
129 return b.v
130 }
131
132 func (b *Board) resetSelection() {
133 b.selectedSpace = 0
134 b.selectedNum = 0
135 }
136
137 func (b *Board) autoSendMoves() {
138 movable := 2
139 if b.v[StatePlayerDice1] > 0 && b.v[StatePlayerDice1] == b.v[StatePlayerDice2] {
140 movable = 4
141 }
142 if b.v[StateMovablePieces] > 0 {
143 movable = b.v[StateMovablePieces]
144 }
145 if len(b.premove) < movable {
146 return
147 }
148
149 moveCommand := []byte("move")
150 for j := 0; j < 2; j++ {
151 for i := range b.premove {
152 var from string
153 if b.premove[i][0] == 0 || b.premove[i][0] == 25 {
154 from = "bar"
155 } else {
156 from = strconv.Itoa(b.premove[i][0])
157 }
158
159 if (j == 0) != (from == "bar") {
160 continue
161 }
162
163 var to string
164 if b.premove[i][1] == b.PlayerBearOffSpace() {
165 to = "off"
166 } else {
167 to = strconv.Itoa(b.premove[i][1])
168 }
169
170 moveCommand = append(moveCommand, []byte(" "+from+"-"+to)...)
171 }
172
173 }
174
175 b.client.Out <- moveCommand
176 }
177
178 func (b *Board) GetState() string {
179 var s = strings.Join(b.s, ":")
180 for i := range b.v {
181 s += ":" + strconv.Itoa(b.v[i])
182 }
183 return s
184 }
185
186 func (b *Board) SetState(state string) {
187 b.Lock()
188
189 s := strings.Split(state, ":")
190 newPlayers := s[StatePlayerName] != b.s[StatePlayerName] || s[StateOpponentName] != b.s[StateOpponentName]
191 copy(b.s, s)
192
193 v := make([]int, 50)
194 var err error
195 for i := 0; i < 50; i++ {
196 v[i], err = strconv.Atoi(b.s[i+2])
197 if err != nil {
198 log.Fatal(err)
199 }
200 }
201
202 newTurn := v[StateTurn] != b.v[StateTurn]
203
204
205 if !newPlayers && !newTurn {
206 copyDice := []int{
207 StatePlayerDice1,
208 StatePlayerDice2,
209 StateOpponentDice1,
210 StateOpponentDice2,
211 }
212 for _, vi := range copyDice {
213 if v[vi] == 0 {
214 v[vi] = b.v[vi]
215 }
216 }
217 }
218
219 copy(b.v, v)
220
221 b.ResetPreMoves()
222
223 b.Unlock()
224 b.Draw()
225 }
226
227 func (b *Board) Draw() {
228 b.client.Event <- &EventDraw{}
229 }
230
231 func (b *Board) renderSpace(index int, spaceValue int) []byte {
232 var playerColor = "x"
233 var opponentColor = "o"
234 if b.v[StatePlayerColor] == 1 {
235 playerColor = "o"
236 opponentColor = "x"
237 }
238
239 var pieceColor string
240 value := b.v[StateBoardSpace0+index]
241 if index == b.PlayerBarSpace() {
242 value = b.v[StatePlayerBar]
243 pieceColor = playerColor
244 } else if index == 25-b.PlayerBarSpace() {
245 value = b.v[StateOpponentBar]
246 pieceColor = opponentColor
247 } else {
248 if value < 0 {
249 pieceColor = "x"
250 } else if value > 0 {
251 pieceColor = "o"
252 } else {
253 pieceColor = playerColor
254 }
255 }
256
257 abs := value
258 if value < 0 {
259 abs = value * -1
260 }
261
262 top := index <= 12
263 if b.v[StatePlayerColor] == 1 {
264 top = !top
265 }
266
267 firstDigit := 4
268 secondDigit := 5
269 if !top {
270 firstDigit = 5
271 secondDigit = 4
272 }
273
274 var firstNumeral string
275 var secondNumeral string
276 if abs > 5 {
277 if abs > 9 {
278 firstNumeral = "1"
279 } else {
280 firstNumeral = strconv.Itoa(abs)
281 }
282 if abs > 9 {
283 secondNumeral = strconv.Itoa(abs - 10)
284 }
285
286 if spaceValue == firstDigit && (!top || abs > 9) {
287 pieceColor = firstNumeral
288 } else if spaceValue == secondDigit && abs > 9 {
289 pieceColor = secondNumeral
290 } else if top && spaceValue == secondDigit {
291 pieceColor = firstNumeral
292 }
293 }
294
295 if abs > 5 {
296 abs = 5
297 }
298
299 var r []byte
300 foregroundColor := "#FFFFFF"
301 backgroundColor := "#000000"
302 if index != 0 && index != 25 {
303 if true {
304 if index%2 == 0 {
305 backgroundColor = "#303030"
306 } else {
307 backgroundColor = "#101010"
308 }
309 } else {
310 foregroundColor = "#000000"
311 switch index % 6 {
312 case 1:
313 backgroundColor = "#FF0000"
314 case 2:
315 backgroundColor = "#FFA500"
316 case 3:
317 backgroundColor = "#FFFF00"
318 case 4:
319 backgroundColor = "#008000"
320 case 5:
321 backgroundColor = "#0000FF"
322 case 0:
323 backgroundColor = "#4B0082"
324 }
325 }
326 }
327
328 highlightSpace := b.ValidMove(b.selectedSpace, index)
329 highlightSpace = false
330 if b.selectedNum > 0 && highlightSpace && index != 25 && index != 0 {
331 foregroundColor = "black"
332 backgroundColor = "yellow"
333 }
334 if abs > 0 && spaceValue <= abs {
335 r = []byte(pieceColor)
336 } else {
337 r = []byte(" ")
338 }
339
340 rightArrowFrom := (b.v[StateDirection] == b.movesColor) == (index > 12)
341 if b.selectedSpace == index && b.selectedNum > 0 && spaceValue <= abs && spaceValue > abs-b.selectedNum {
342 r = []byte("*")
343 } else if b.Premovefrom[index] > 0 && spaceValue > (abs+b.Premoveto[index])-b.Premovefrom[index] && spaceValue <= abs+b.Premoveto[index] {
344 if index == 25-b.PlayerBarSpace() {
345 r = []byte("▾")
346 } else if index == b.PlayerBarSpace() {
347 r = []byte("▴")
348 } else if rightArrowFrom {
349 r = []byte("▸")
350 } else {
351 r = []byte("◂")
352 }
353 foregroundColor = "yellow"
354 } else if b.Premoveto[index] > 0 && spaceValue > abs && spaceValue <= abs+(b.Premoveto[index]) {
355 r = []byte(playerColor)
356 foregroundColor = "yellow"
357 } else if b.from[index] > 0 && spaceValue > abs && spaceValue <= abs+b.from[index] {
358 if rightArrowFrom {
359 r = []byte("▸")
360 } else {
361 r = []byte("◂")
362 }
363 if b.movesColor == b.v[StatePlayerColor] {
364 foregroundColor = "yellow"
365 } else {
366 foregroundColor = "green"
367 }
368 } else if b.to[index] > 0 && spaceValue > abs-(b.to[index]+b.from[index]) {
369 if b.movesColor == b.v[StatePlayerColor] {
370 foregroundColor = "yellow"
371 } else {
372 foregroundColor = "green"
373 }
374 }
375
376 return append(append([]byte(fmt.Sprintf("[\"space-%d\"][%s:%s:b] ", index, foregroundColor, backgroundColor)), r...), []byte(" [-:-:-][\"\"]")...)
377 }
378
379 func (b *Board) ResetMoves() {
380 b.moves = nil
381 b.movesColor = 0
382 b.validMoves = make(map[int][][]int)
383 b.from = make(map[int]int)
384 b.to = make(map[int]int)
385 }
386
387 func (b *Board) ResetPreMoves() {
388 b.premove = nil
389 b.Premovefrom = make(map[int]int)
390 b.Premoveto = make(map[int]int)
391 }
392
393 func (b *Board) PlayerHomeSpaces() (int, int) {
394 homeBoardStart := 1
395 homeBoardEnd := 6
396 if (b.v[StateDirection] == -1) == (b.v[StatePlayerColor] == -1) {
397 homeBoardStart = 19
398 homeBoardEnd = 24
399 }
400 return homeBoardStart, homeBoardEnd
401 }
402
403 func (b *Board) PlayerPieceAreHome() bool {
404 homeBoardStart, homeBoardEnd := b.PlayerHomeSpaces()
405 hasPlayerPiece := func(index int) bool {
406 if index < 0 || index > 25 {
407 return false
408 }
409 value := b.v[StateBoardSpace0+index]
410
411
412 mod := b.v[StatePlayerColor]
413 value -= b.client.Board.Premovefrom[index] * mod
414
415 if b.v[StatePlayerColor] == -1 {
416 return value < 0
417 }
418 return value > 0
419 }
420 for i := 1; i < 24; i++ {
421 if i >= homeBoardStart && i <= homeBoardEnd {
422 continue
423 }
424 if hasPlayerPiece(i) {
425 return false
426 }
427 }
428 return true
429 }
430
431 func (b *Board) spaceAvailable(index int) bool {
432 if index < 0 || index > 25 {
433 return false
434 }
435 if index == 0 || index == 25 {
436 return b.PlayerPieceAreHome()
437 }
438 return (b.v[StatePlayerColor] == 1 && b.v[StateBoardSpace0+index] >= -1) ||
439 (b.v[StatePlayerColor] == -1 && b.v[StateBoardSpace0+index] <= 1)
440 }
441
442 func (b *Board) GetValidMoves(from int) [][]int {
443 if validMoves, ok := b.validMoves[from]; ok {
444 return validMoves
445 }
446
447 var validMoves [][]int
448 defer func() {
449 b.validMoves[from] = validMoves
450 }()
451
452 if b.v[StateTurn] != b.v[StatePlayerColor] || b.v[StatePlayerDice1] == 0 || b.v[StatePlayerDice2] == 0 {
453 return validMoves
454 }
455
456 trySpaces := [][]int{
457 {b.v[StatePlayerDice1]},
458 {b.v[StatePlayerDice2]},
459 {b.v[StatePlayerDice1], b.v[StatePlayerDice2]},
460 {b.v[StatePlayerDice2], b.v[StatePlayerDice1]},
461 }
462 if b.v[StatePlayerDice1] == b.v[StatePlayerDice2] {
463 trySpaces = append(trySpaces,
464 []int{b.v[StatePlayerDice1], b.v[StatePlayerDice1], b.v[StatePlayerDice1]},
465 []int{b.v[StatePlayerDice1], b.v[StatePlayerDice1], b.v[StatePlayerDice1], b.v[StatePlayerDice1]})
466 }
467
468 if b.PlayerPieceAreHome() {
469 homeSpace := b.PlayerBearOffSpace()
470 spacesHome := from - homeSpace
471 if spacesHome < 0 {
472 spacesHome *= -1
473 }
474 if spacesHome <= b.v[StatePlayerDice1] || spacesHome <= b.v[StatePlayerDice2] {
475 trySpaces = append(trySpaces, []int{spacesHome})
476 }
477 }
478 foundMoves := make(map[int]bool)
479 CHECKSPACES:
480 for i := range trySpaces {
481 checkSpace := 0
482 for _, space := range trySpaces[i] {
483 checkSpace += space
484 if !b.spaceAvailable(from + (checkSpace * b.v[StateDirection])) {
485 continue CHECKSPACES
486 }
487 }
488 space := from + (checkSpace * b.v[StateDirection])
489 if _, value := foundMoves[space]; !value {
490 foundMoves[space] = true
491 validMoves = append(validMoves, trySpaces[i])
492 }
493 }
494
495 return validMoves
496 }
497
498 func (b *Board) PlayerBarSpace() int {
499 return 25 - b.PlayerBearOffSpace()
500 }
501
502 func (b *Board) PlayerBearOffSpace() int {
503 if b.v[StateDirection] == -1 {
504 return 0
505 }
506 return 25
507 }
508
509 func (b *Board) ValidMove(f int, t int) bool {
510 if b.v[StateTurn] != b.v[StatePlayerColor] || b.v[StatePlayerDice1] == 0 || b.v[StatePlayerDice2] == 0 {
511 return false
512 }
513
514 if t == b.PlayerBearOffSpace() {
515
516 return b.PlayerPieceAreHome()
517 }
518
519 validMoves := b.GetValidMoves(f)
520 CHECKVALID:
521 for i := range validMoves {
522 checkSpace := 0
523 for _, space := range validMoves[i] {
524 checkSpace += space
525 if !b.spaceAvailable(f + (checkSpace * b.v[StateDirection])) {
526 continue CHECKVALID
527 }
528 }
529 if f+(checkSpace*b.v[StateDirection]) == t {
530 return true
531 }
532 }
533 return false
534 }
535
536 func (b *Board) parseMoveString(player int, s string) int {
537 space, err := strconv.Atoi(s)
538 if err != nil {
539 space = SpaceUnknown
540 if s == "bar" {
541 barSpace := b.PlayerBarSpace()
542 if b.v[StatePlayerColor] == player {
543 space = barSpace
544 } else {
545 space = 25 - barSpace
546 }
547 } else if s == "off" {
548 space = b.PlayerBearOffSpace()
549 }
550 }
551 return space
552 }
553
554 func (b *Board) Move(player int, f string, t string) {
555 from := b.parseMoveString(player, f)
556 to := b.parseMoveString(player, t)
557
558 if from == SpaceUnknown || to == SpaceUnknown {
559 lf("WARNING: Unknown move %s-%s", f, t)
560 return
561 }
562
563 b.moves = append(b.moves, [2]int{from, to})
564 b.movesColor = player
565
566 b.from[from]++
567 b.to[to]++
568
569 spaceValue := b.v[StateBoardSpace0+to]
570
571
572 if (spaceValue == -1 && player == 1) || (spaceValue == 1 && player == -1) {
573 bar := 25 - b.PlayerBarSpace()
574 if player == b.v[StatePlayerColor] {
575 bar = b.PlayerBarSpace()
576 }
577
578 b.v[StateBoardSpace0+bar] -= player
579 }
580
581 b.v[StateBoardSpace0+from] -= player
582 b.v[StateBoardSpace0+to] += player
583
584 b.v[StateTurn] = player * -1
585
586 b.validMoves = make(map[int][][]int)
587 b.ResetPreMoves()
588 }
589
590 func (b *Board) SimplifyMoves() {
591 for i := range b.moves {
592 for j := range b.moves {
593 if b.moves[i][1] == b.moves[j][0] {
594
595 b.moves[j][0] = b.moves[i][0]
596 b.moves = append(b.moves[:i], b.moves[i+1:]...)
597 b.SimplifyMoves()
598 return
599 } else if b.moves[i][0] == b.moves[j][1] {
600
601 b.moves[j][1] = b.moves[i][1]
602 b.moves = append(b.moves[:i], b.moves[i+1:]...)
603 b.SimplifyMoves()
604 return
605 }
606 }
607 }
608 }
609
610 func (b *Board) GetSelection() (num int, space int) {
611 return b.selectedNum, b.selectedSpace
612 }
613
614 func (b *Board) SetSelection(num int, space int) {
615 b.selectedNum, b.selectedSpace = num, space
616 }
617
618 func (b *Board) ResetSelection() {
619 b.selectedNum, b.selectedSpace = 0, 0
620 }
621
622 func (b *Board) addPreMove(from int, to int, num int) bool {
623
624 if to == 0 || to == 25 {
625 to = b.PlayerBearOffSpace()
626 }
627
628
629 moves := b.client.Board.GetValidMoves(from)
630
631 CHECKPREMOVES:
632 for i := range moves {
633 checkSpace := 0
634 for _, space := range moves[i] {
635 checkSpace += space
636 if !b.spaceAvailable(from + (checkSpace * b.v[StateDirection])) {
637 continue CHECKPREMOVES
638 }
639 }
640 if (from+(checkSpace*b.v[StateDirection]) == to) && len(moves[i]) > 1 {
641 for j := 0; j < num; j++ {
642 checkSpace = 0
643 lastSpace := 0
644 for _, space := range moves[i] {
645 checkSpace += space
646 if !b.addPreMove(from+(lastSpace*b.v[StateDirection]), from+(checkSpace*b.v[StateDirection]), 1) {
647 return false
648 }
649 lastSpace = checkSpace
650 }
651 }
652 return true
653 }
654 }
655
656 if !b.ValidMove(from, to) {
657 return false
658 }
659
660 for i := 0; i < num; i++ {
661 b.premove = append(b.premove, [2]int{from, to})
662 b.Premovefrom[from]++
663 b.Premoveto[to]++
664 }
665 return true
666 }
667
668 func (b *Board) AddPreMove(from int, to int) bool {
669 if !b.addPreMove(from, to, b.selectedNum) {
670 return false
671 }
672
673 b.resetSelection()
674 b.autoSendMoves()
675 return true
676 }
677
678 func (b *Board) GetPreMoves() [][2]int {
679 return b.premove
680 }
681
682 func (b *Board) Render() []byte {
683 b.Lock()
684
685 var white bool
686 if b.v[StatePlayerColor] == 1 {
687 white = true
688 }
689
690 var opponentName = b.s[1]
691 var playerName = b.s[0]
692
693 var playerColor = "x"
694 var opponentColor = "o"
695 if white {
696 playerColor = "o"
697 opponentColor = "x"
698 }
699
700 var t bytes.Buffer
701 t.WriteString("[\"space-off\"] [\"\"]\n")
702 t.WriteString("[\"space-off\"] [\"\"]\n")
703 t.WriteString("[\"space-off\"] ")
704 if white {
705 t.Write(boardTopWhite)
706 } else {
707 t.Write(boardTopBlack)
708 }
709 t.WriteString("[\"\"] ")
710 t.WriteByte('\n')
711
712 space := func(i int, j int) []byte {
713 spaceValue := i + 1
714 if i > 5 {
715 spaceValue = 5 - (i - 6)
716 }
717
718 if j == -1 {
719 if i <= 4 {
720 return b.renderSpace(25-b.PlayerBarSpace(), spaceValue)
721 }
722 return b.renderSpace(b.PlayerBarSpace(), spaceValue)
723 }
724
725 var index int
726 if !white {
727 if i < 6 {
728 j = 12 - j
729 } else {
730 j = 11 - j
731 }
732
733 index = 12 + j
734 if i > 5 {
735 index = 12 - j
736 }
737 } else {
738 index = 12 + j
739 if i > 5 {
740 index = 11 - j
741 }
742 }
743 if !white {
744 index = 24 - index
745 }
746 index++
747
748 if i == 5 {
749 return []byte("[-:#000000] [-:-]")
750 }
751
752 return b.renderSpace(index, spaceValue)
753 }
754
755 for i := 0; i < 11; i++ {
756 t.Write([]byte("[\"space-off\"]"))
757
758 if i == 5 && b.v[StateDoublingValue] > 1 {
759 t.WriteString(fmt.Sprintf("%2d ", b.v[StateDoublingValue]))
760 if b.v[StatePlayerMayDouble] == 1 {
761 t.WriteByte('v')
762 } else {
763 t.WriteByte('^')
764 }
765 } else {
766 t.WriteByte(' ')
767 t.WriteByte(' ')
768 t.WriteByte(' ')
769 t.WriteByte(' ')
770 }
771
772 t.WriteRune(BoxDrawingsLightVertical)
773 t.Write([]byte("[\"\"]"))
774 for j := 0; j < 12; j++ {
775 t.Write(space(i, j))
776
777 if j == 5 {
778 t.WriteRune(BoxDrawingsLightVertical)
779 t.Write(space(i, -1))
780 t.WriteRune(BoxDrawingsLightVertical)
781 }
782 }
783
784 t.Write([]byte("[\"space-off\"]" + string(BoxDrawingsLightVertical) + " "))
785
786 playerRollColor := "yellow"
787 playerBold := "b"
788 opponentRollColor := "white"
789 opponentBold := ""
790 if b.v[StateTurn] != b.v[StatePlayerColor] {
791 playerRollColor = "white"
792 opponentRollColor = "green"
793 playerBold = ""
794 opponentBold = "b"
795 }
796
797 if i == 0 {
798 t.Write([]byte("[" + opponentRollColor + "::" + opponentBold + "]" + opponentColor + " " + opponentName + " (" + b.s[4] + ")"))
799 if b.v[StateOpponentHome] > 0 {
800 t.Write([]byte(fmt.Sprintf(" %d off", b.v[StateOpponentHome])))
801 }
802 t.Write([]byte("[-::-]"))
803 } else if i == 2 {
804 if b.v[StateOpponentDice1] > 0 {
805 t.Write([]byte(fmt.Sprintf(" [%s::%s]%d %d[-::-] ", opponentRollColor, opponentBold, b.v[StateOpponentDice1], b.v[StateOpponentDice2])))
806 } else {
807 t.Write([]byte(fmt.Sprintf(" [%s]- -[-] ", opponentRollColor)))
808 }
809 } else if i == 8 {
810 if b.v[StatePlayerDice1] > 0 {
811 t.Write([]byte(fmt.Sprintf(" [%s::%s]%d %d[-::-] ", playerRollColor, playerBold, b.v[StatePlayerDice1], b.v[StatePlayerDice2])))
812 } else {
813 t.Write([]byte(fmt.Sprintf(" [%s]- -[-] ", playerRollColor)))
814 }
815 } else if i == 10 {
816 t.Write([]byte("[" + playerRollColor + "::" + playerBold + "]" + playerColor + " " + playerName + " (" + b.s[3] + ")"))
817 if b.v[StatePlayerHome] > 0 {
818 t.Write([]byte(fmt.Sprintf(" %d off", b.v[StatePlayerHome])))
819 }
820 t.Write([]byte("[-::-]"))
821 }
822
823 t.Write([]byte("[\"\"] "))
824 t.WriteByte('\n')
825 }
826
827 t.WriteString("[\"space-off\"] ")
828 if white {
829 t.Write(boardBottomWhite)
830 } else {
831 t.Write(boardBottomBlack)
832 }
833 t.WriteString(" [\"\"]\n")
834 t.WriteString("[\"space-off\"] [\"\"]")
835
836 b.Unlock()
837
838 return t.Bytes()
839 }
840
View as plain text