github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/exp/shiny/example/goban/board.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build ignore
     6  //
     7  // This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
     8  // install this example program. Use "go run main.go draw.go xy.go" to run it.
     9  
    10  package main
    11  
    12  import (
    13  	"image"
    14  	"image/color"
    15  	_ "image/jpeg"
    16  	_ "image/png"
    17  	"log"
    18  	"math/rand"
    19  	"os"
    20  	"path/filepath"
    21  	"strings"
    22  
    23  	"golang.org/x/image/draw"
    24  )
    25  
    26  const maxBoard = 21 // Maximum board size we can handle.
    27  
    28  var ZP = image.ZP // For brevity.
    29  
    30  type stoneColor uint8
    31  
    32  const (
    33  	blank stoneColor = iota // Unused
    34  	black
    35  	white
    36  )
    37  
    38  // Piece represents a stone on the board. A nil Piece is "blank".
    39  // The delta records pixel offset from the central dot.
    40  type Piece struct {
    41  	stone *Stone
    42  	ij    IJ
    43  	delta image.Point
    44  	color stoneColor
    45  }
    46  
    47  type Board struct {
    48  	Dims
    49  	pieces         []*Piece // The board. Dimensions are 1-indexed. 1, 1 is the lower left corner.
    50  	image          *image.RGBA
    51  	stone          []Stone // All the black stones, followed by all the white stones.
    52  	numBlackStones int
    53  	numWhiteStones int
    54  }
    55  
    56  type Stone struct {
    57  	originalImage *image.RGBA
    58  	originalMask  *image.Alpha
    59  	image         *image.RGBA
    60  	mask          *image.Alpha
    61  }
    62  
    63  func NewBoard(dim, percent int) *Board {
    64  	switch dim {
    65  	case 9, 13, 19, 21:
    66  	default:
    67  		return nil
    68  	}
    69  	boardTexture := get("goboard.jpg", 0)
    70  	b := new(Board)
    71  	b.Dims.Init(dim, 100)
    72  	b.pieces = make([]*Piece, maxBoard*maxBoard)
    73  	b.image = image.NewRGBA(boardTexture.Bounds())
    74  	draw.Draw(b.image, b.image.Bounds(), boardTexture, ZP, draw.Src)
    75  	dir, err := os.Open("asset")
    76  	if err != nil {
    77  		log.Fatal(err)
    78  	}
    79  	defer dir.Close()
    80  	names, err := dir.Readdirnames(0)
    81  	if err != nil {
    82  		log.Fatal(err)
    83  	}
    84  	circleMask := makeCircle()
    85  	// Blackstones go first
    86  	for _, name := range names {
    87  		if strings.HasPrefix(name, "blackstone") {
    88  			s, m := makeStone(name, circleMask)
    89  			b.stone = append(b.stone, Stone{s, m, nil, nil})
    90  			b.numBlackStones++
    91  		}
    92  	}
    93  	for _, name := range names {
    94  		if strings.HasPrefix(name, "whitestone") {
    95  			s, m := makeStone(name, circleMask)
    96  			b.stone = append(b.stone, Stone{s, m, nil, nil})
    97  			b.numWhiteStones++
    98  		}
    99  	}
   100  	b.Resize(percent) // TODO
   101  	return b
   102  }
   103  
   104  func (b *Board) Resize(percent int) {
   105  	b.Dims.Resize(percent)
   106  	for i := range b.stone {
   107  		stone := &b.stone[i]
   108  		stone.image = resizeRGBA(stone.originalImage, b.stoneDiam)
   109  		stone.mask = resizeAlpha(stone.originalMask, b.stoneDiam)
   110  	}
   111  }
   112  
   113  func resizeRGBA(src *image.RGBA, size int) *image.RGBA {
   114  	dst := image.NewRGBA(image.Rect(0, 0, size, size))
   115  	draw.ApproxBiLinear.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Src, nil)
   116  	return dst
   117  }
   118  
   119  func resizeAlpha(src *image.Alpha, size int) *image.Alpha {
   120  	dst := image.NewAlpha(image.Rect(0, 0, size, size))
   121  	draw.ApproxBiLinear.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Src, nil)
   122  	return dst
   123  }
   124  
   125  func (b *Board) piece(ij IJ) *Piece {
   126  	return b.pieces[(ij.j-1)*b.Dims.dim+ij.i-1]
   127  }
   128  
   129  func jitter() int {
   130  	max := 25 * *scale / 100
   131  	if max&1 == 0 {
   132  		max++
   133  	}
   134  	return rand.Intn(max) - max/2
   135  }
   136  
   137  func (b *Board) putPiece(ij IJ, piece *Piece) {
   138  	b.pieces[(ij.j-1)*b.Dims.dim+ij.i-1] = piece
   139  	if piece != nil {
   140  		piece.ij = ij
   141  		piece.delta = image.Point{jitter(), jitter()}
   142  	}
   143  }
   144  
   145  func (b *Board) selectBlackPiece() *Piece {
   146  	return &Piece{
   147  		stone: &b.stone[rand.Intn(b.numBlackStones)],
   148  		color: black,
   149  	}
   150  }
   151  
   152  func (b *Board) selectWhitePiece() *Piece {
   153  	return &Piece{
   154  		stone: &b.stone[b.numBlackStones+rand.Intn(b.numWhiteStones)],
   155  		color: white,
   156  	}
   157  }
   158  
   159  func makeStone(name string, circleMask *image.Alpha) (*image.RGBA, *image.Alpha) {
   160  	stone := get(name, stoneSize0)
   161  	dst := image.NewRGBA(stone.Bounds())
   162  	// Make the whole area black, for the shadow.
   163  	draw.Draw(dst, dst.Bounds(), image.Black, ZP, draw.Src)
   164  	// Lay in the stone within the circle so it shows up inside the shadow.
   165  	draw.DrawMask(dst, dst.Bounds(), stone, ZP, circleMask, ZP, draw.Over)
   166  	return dst, makeShadowMask(stone)
   167  }
   168  
   169  func get(name string, size int) image.Image {
   170  	f, err := os.Open(filepath.Join("asset", name))
   171  	if err != nil {
   172  		log.Fatal(err)
   173  	}
   174  	i, _, err := image.Decode(f)
   175  	if err != nil {
   176  		log.Fatal(err)
   177  	}
   178  	f.Close()
   179  	if size != 0 {
   180  		r := i.Bounds()
   181  		if r.Dx() != size || r.Dy() != size {
   182  			log.Fatalf("bad stone size %s for %s; must be %d[2]×%d[2]", r, name, size)
   183  		}
   184  	}
   185  	return i
   186  }
   187  
   188  func makeCircle() *image.Alpha {
   189  	mask := image.NewAlpha(image.Rect(0, 0, stoneSize0, stoneSize0))
   190  	// Make alpha work on stone.
   191  	// Shade gives shape, to be applied with black.
   192  	for y := 0; y < stoneSize0; y++ {
   193  		y2 := stoneSize0/2 - y
   194  		y2 *= y2
   195  		for x := 0; x < stoneSize0; x++ {
   196  			x2 := stoneSize0/2 - x
   197  			x2 *= x2
   198  			if x2+y2 <= stoneRad2 {
   199  				mask.SetAlpha(x, y, color.Alpha{255})
   200  			}
   201  		}
   202  	}
   203  	return mask
   204  }
   205  
   206  func makeShadowMask(stone image.Image) *image.Alpha {
   207  	mask := image.NewAlpha(stone.Bounds())
   208  	// Make alpha work on stone.
   209  	// Shade gives shape, to be applied with black.
   210  	const size = 256
   211  	const diam = 225
   212  	for y := 0; y < size; y++ {
   213  		y2 := size/2 - y
   214  		y2 *= y2
   215  		for x := 0; x < size; x++ {
   216  			x2 := size/2 - x
   217  			x2 *= x2
   218  			if x2+y2 > stoneRad2 {
   219  				red, _, _, _ := stone.At(x, y).RGBA()
   220  				mask.SetAlpha(x, y, color.Alpha{255 - uint8(red>>8)})
   221  			} else {
   222  				mask.SetAlpha(x, y, color.Alpha{255})
   223  			}
   224  		}
   225  	}
   226  	return mask
   227  }
   228  
   229  func (b *Board) Draw(m *image.RGBA) {
   230  	r := b.image.Bounds()
   231  	draw.Draw(m, r, b.image, ZP, draw.Src)
   232  	// Vertical lines.
   233  	x := b.xInset + b.squareWidth/2
   234  	y := b.yInset + b.squareHeight/2
   235  	wid := b.lineWidth
   236  	for i := 0; i < b.dim; i++ {
   237  		r := image.Rect(x, y, x+wid, y+(b.dim-1)*b.squareHeight)
   238  		draw.Draw(m, r, image.Black, ZP, draw.Src)
   239  		x += b.squareWidth
   240  	}
   241  	// Horizontal lines.
   242  	x = b.xInset + b.squareWidth/2
   243  	for i := 0; i < b.dim; i++ {
   244  		r := image.Rect(x, y, x+(b.dim-1)*b.squareWidth+wid, y+wid)
   245  		draw.Draw(m, r, image.Black, ZP, draw.Src)
   246  		y += b.squareHeight
   247  	}
   248  	// Points.
   249  	spot := 4
   250  	if b.dim < 13 {
   251  		spot = 3
   252  	}
   253  	points := []IJ{
   254  		{spot, spot},
   255  		{spot, (b.dim + 1) / 2},
   256  		{spot, b.dim + 1 - spot},
   257  		{(b.dim + 1) / 2, spot},
   258  		{(b.dim + 1) / 2, (b.dim + 1) / 2},
   259  		{(b.dim + 1) / 2, b.dim + 1 - spot},
   260  		{b.dim + 1 - spot, spot},
   261  		{b.dim + 1 - spot, (b.dim + 1) / 2},
   262  		{b.dim + 1 - spot, b.dim + 1 - spot},
   263  	}
   264  	for _, ij := range points {
   265  		b.drawPoint(m, ij)
   266  	}
   267  	// Pieces.
   268  	for i := 1; i <= b.dim; i++ {
   269  		for j := 1; j <= b.dim; j++ {
   270  			ij := IJ{i, j}
   271  			if p := b.piece(ij); p != nil {
   272  				b.drawPiece(m, ij, p)
   273  			}
   274  		}
   275  	}
   276  }
   277  
   278  func (b *Board) drawPoint(m *image.RGBA, ij IJ) {
   279  	pt := ij.XYCenter(&b.Dims)
   280  	wid := b.lineWidth
   281  	sz := wid * 3 / 2
   282  	r := image.Rect(pt.x-sz, pt.y-sz, pt.x+wid+sz, pt.y+wid+sz)
   283  	draw.Draw(m, r, image.Black, ZP, draw.Src)
   284  }
   285  
   286  func (b *Board) drawPiece(m *image.RGBA, ij IJ, piece *Piece) {
   287  	xy := ij.XYStone(&b.Dims)
   288  	xy = xy.Add(piece.delta)
   289  	draw.DrawMask(m, xy, piece.stone.image, ZP, piece.stone.mask, ZP, draw.Over)
   290  }
   291  
   292  func (b *Board) click(m *image.RGBA, x, y, button int) {
   293  	ij, ok := XY{x, y}.IJ(&b.Dims)
   294  	if !ok {
   295  		return
   296  	}
   297  	switch button {
   298  	case 1:
   299  		b.putPiece(ij, b.selectBlackPiece())
   300  	case 2:
   301  		b.putPiece(ij, b.selectWhitePiece())
   302  	case 3:
   303  		b.putPiece(ij, nil)
   304  	}
   305  	render(m, b) // TODO: Connect this to paint events.
   306  }