github.com/gorgonia/agogo@v0.1.1/game/asciirender.go (about) 1 package game 2 3 import ( 4 "fmt" 5 "image" 6 "image/color" 7 "image/draw" 8 "image/gif" 9 "io" 10 "math" 11 "strings" 12 13 "github.com/golang/freetype/truetype" 14 "golang.org/x/image/font" 15 "golang.org/x/image/font/gofont/gomono" 16 "golang.org/x/image/math/fixed" 17 ) 18 19 var tt font.Face 20 var regular *truetype.Font 21 22 const ( 23 dpi = 144.0 24 fontsize = 12.0 25 lineheight = 1.2 26 dummyLongString = `Epoch 100000, Game Number: 10000` 27 ) 28 29 func init() { 30 var err error 31 if regular, err = truetype.Parse(gomono.TTF); err != nil { 32 panic(err) 33 } 34 35 tt = truetype.NewFace(regular, &truetype.Options{ 36 Size: fontsize, 37 DPI: dpi, 38 Hinting: font.HintingFull, 39 }) 40 } 41 42 var globPalette = color.Palette{ 43 color.Gray{0}, 44 color.Gray{253}, 45 } 46 47 type GifEncoder struct { 48 H, W int 49 font.Drawer 50 51 out *gif.GIF 52 io.Writer 53 face font.Face 54 55 maxH, maxW int // maxHeight and maxWidth 56 padH, padW int // padding so everything don't start at the topleft 57 fontsize float64 58 initialized bool 59 } 60 61 func NewGifEncoder(h, w int) *GifEncoder { 62 return &GifEncoder{ 63 H: -1, 64 W: -1, 65 maxH: h, 66 maxW: w, 67 padH: 10, 68 padW: 10, 69 70 Drawer: font.Drawer{ 71 Src: image.Black, 72 }, 73 out: &gif.GIF{LoopCount: -1}, 74 } 75 } 76 77 func (enc *GifEncoder) Encode(ms MetaState) error { 78 g := ms.State() 79 gameNum := ms.GameNumber() 80 gameName := ms.Name() 81 epoch := ms.Epoch() 82 repr := fmt.Sprintf("%s", g) 83 84 if !enc.initialized { 85 // lazy init of specifications 86 enc.face = truetype.NewFace(regular, &truetype.Options{ 87 Size: fontsize, 88 DPI: dpi, 89 Hinting: font.HintingFull, 90 }) 91 enc.Drawer.Src = image.Black 92 enc.Drawer.Face = enc.face 93 94 // first calculate how long the max length will be 95 splits := strings.Split(repr, "\n") 96 oneline := splits[0] 97 maxW := maxInt(font.MeasureString(enc.Face, oneline).Ceil(), font.MeasureString(enc.Face, dummyLongString).Ceil()) 98 dy := int(math.Ceil(fontsize * lineheight * dpi / 72)) 99 w := maxW + 2*enc.padW 100 h := (len(splits)+3)*dy + 2*enc.padH // + 3 is for the 3 extra lines: game name, state, and winner 101 102 w = minInt(w, enc.maxW) 103 h = minInt(h, enc.maxH) 104 105 if w == enc.maxW { 106 enc.padW = 0 107 } 108 if h == enc.maxH { 109 enc.padH = 0 110 } 111 112 enc.H = h 113 enc.W = w 114 enc.initialized = true 115 } 116 117 x := 0 118 y := 0 119 120 bg := image.White 121 im := image.NewPaletted(image.Rect(0, 0, enc.W, enc.H), globPalette) 122 draw.Draw(im, im.Bounds(), bg, image.ZP, draw.Src) 123 dy := int(math.Ceil(fontsize * lineheight * dpi / 72)) 124 enc.Dot = fixed.Point26_6{ 125 X: fixed.I(x + enc.padW), 126 Y: fixed.I(y + enc.padH), 127 } 128 y += dy 129 text := strings.Split(repr, "\n") 130 enc.Dst = im 131 for _, s := range text { 132 enc.Dot = fixed.P(0+enc.padW, y) 133 enc.DrawString(s) 134 y += dy 135 } 136 enc.Dot = fixed.P(0+enc.padW, y) 137 enc.DrawString(gameName) 138 y += dy 139 140 enc.Dot = fixed.P(0+enc.padW, y) 141 enc.DrawString(fmt.Sprintf("Epoch %d, Game Number: %d ", epoch, gameNum)) 142 y += dy 143 144 var delay int 145 if ok, winner := g.Ended(); ok { 146 delay = 300 147 enc.Dot = fixed.P(0+enc.padW, y) 148 enc.DrawString(fmt.Sprintf("Winner: %s", winner)) 149 } 150 enc.out.Image = append(enc.out.Image, im) 151 enc.out.Delay = append(enc.out.Delay, delay) 152 return nil 153 } 154 155 func (enc *GifEncoder) Flush() error { return gif.EncodeAll(enc.Writer, enc.out) }