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) }