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 }