// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build example // // This build tag means that "go install golang.org/x/exp/shiny/..." doesn't // install this example program. Use "go run main.go board.go xy.go" to run it // or "go install -tags=example" to install it. package main import ( "image" "image/color" _ "image/jpeg" _ "image/png" "log" "math/rand" "os" "path/filepath" "strings" "golang.org/x/image/draw" ) const maxBoard = 21 // Maximum board size we can handle. var ZP = image.ZP // For brevity. type stoneColor uint8 const ( blank stoneColor = iota // Unused black white ) // Piece represents a stone on the board. A nil Piece is "blank". // The delta records pixel offset from the central dot. type Piece struct { stone *Stone ij IJ delta image.Point color stoneColor } type Board struct { Dims pieces []*Piece // The board. Dimensions are 1-indexed. 1, 1 is the lower left corner. image *image.RGBA stone []Stone // All the black stones, followed by all the white stones. numBlackStones int numWhiteStones int } type Stone struct { originalImage *image.RGBA originalMask *image.Alpha image *image.RGBA mask *image.Alpha } func NewBoard(dim, percent int) *Board { switch dim { case 9, 13, 19, 21: default: return nil } boardTexture := get("goboard.jpg", 0) b := new(Board) b.Dims.Init(dim, 100) b.pieces = make([]*Piece, maxBoard*maxBoard) b.image = image.NewRGBA(boardTexture.Bounds()) draw.Draw(b.image, b.image.Bounds(), boardTexture, ZP, draw.Src) dir, err := os.Open("asset") if err != nil { log.Fatal(err) } defer dir.Close() names, err := dir.Readdirnames(0) if err != nil { log.Fatal(err) } circleMask := makeCircle() // Blackstones go first for _, name := range names { if strings.HasPrefix(name, "blackstone") { s, m := makeStone(name, circleMask) b.stone = append(b.stone, Stone{s, m, nil, nil}) b.numBlackStones++ } } for _, name := range names { if strings.HasPrefix(name, "whitestone") { s, m := makeStone(name, circleMask) b.stone = append(b.stone, Stone{s, m, nil, nil}) b.numWhiteStones++ } } b.Resize(percent) // TODO return b } func (b *Board) Resize(percent int) { b.Dims.Resize(percent) for i := range b.stone { stone := &b.stone[i] stone.image = resizeRGBA(stone.originalImage, b.stoneDiam) stone.mask = resizeAlpha(stone.originalMask, b.stoneDiam) } } func resizeRGBA(src *image.RGBA, size int) *image.RGBA { dst := image.NewRGBA(image.Rect(0, 0, size, size)) draw.ApproxBiLinear.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Src, nil) return dst } func resizeAlpha(src *image.Alpha, size int) *image.Alpha { dst := image.NewAlpha(image.Rect(0, 0, size, size)) draw.ApproxBiLinear.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Src, nil) return dst } func (b *Board) piece(ij IJ) *Piece { return b.pieces[(ij.j-1)*b.Dims.dim+ij.i-1] } func jitter() int { max := 25 * *scale / 100 if max&1 == 0 { max++ } return rand.Intn(max) - max/2 } func (b *Board) putPiece(ij IJ, piece *Piece) { b.pieces[(ij.j-1)*b.Dims.dim+ij.i-1] = piece if piece != nil { piece.ij = ij piece.delta = image.Point{jitter(), jitter()} } } func (b *Board) selectBlackPiece() *Piece { return &Piece{ stone: &b.stone[rand.Intn(b.numBlackStones)], color: black, } } func (b *Board) selectWhitePiece() *Piece { return &Piece{ stone: &b.stone[b.numBlackStones+rand.Intn(b.numWhiteStones)], color: white, } } func makeStone(name string, circleMask *image.Alpha) (*image.RGBA, *image.Alpha) { stone := get(name, stoneSize0) dst := image.NewRGBA(stone.Bounds()) // Make the whole area black, for the shadow. draw.Draw(dst, dst.Bounds(), image.Black, ZP, draw.Src) // Lay in the stone within the circle so it shows up inside the shadow. draw.DrawMask(dst, dst.Bounds(), stone, ZP, circleMask, ZP, draw.Over) return dst, makeShadowMask(stone) } func get(name string, size int) image.Image { f, err := os.Open(filepath.Join("asset", name)) if err != nil { log.Fatal(err) } i, _, err := image.Decode(f) if err != nil { log.Fatal(err) } f.Close() if size != 0 { r := i.Bounds() if r.Dx() != size || r.Dy() != size { log.Fatalf("bad stone size %s for %s; must be %d[2]×%d[2]", r, name, size) } } return i } func makeCircle() *image.Alpha { mask := image.NewAlpha(image.Rect(0, 0, stoneSize0, stoneSize0)) // Make alpha work on stone. // Shade gives shape, to be applied with black. for y := 0; y < stoneSize0; y++ { y2 := stoneSize0/2 - y y2 *= y2 for x := 0; x < stoneSize0; x++ { x2 := stoneSize0/2 - x x2 *= x2 if x2+y2 <= stoneRad2 { mask.SetAlpha(x, y, color.Alpha{255}) } } } return mask } func makeShadowMask(stone image.Image) *image.Alpha { mask := image.NewAlpha(stone.Bounds()) // Make alpha work on stone. // Shade gives shape, to be applied with black. const size = 256 const diam = 225 for y := 0; y < size; y++ { y2 := size/2 - y y2 *= y2 for x := 0; x < size; x++ { x2 := size/2 - x x2 *= x2 if x2+y2 > stoneRad2 { red, _, _, _ := stone.At(x, y).RGBA() mask.SetAlpha(x, y, color.Alpha{255 - uint8(red>>8)}) } else { mask.SetAlpha(x, y, color.Alpha{255}) } } } return mask } func (b *Board) Draw(m *image.RGBA) { r := b.image.Bounds() draw.Draw(m, r, b.image, ZP, draw.Src) // Vertical lines. x := b.xInset + b.squareWidth/2 y := b.yInset + b.squareHeight/2 wid := b.lineWidth for i := 0; i < b.dim; i++ { r := image.Rect(x, y, x+wid, y+(b.dim-1)*b.squareHeight) draw.Draw(m, r, image.Black, ZP, draw.Src) x += b.squareWidth } // Horizontal lines. x = b.xInset + b.squareWidth/2 for i := 0; i < b.dim; i++ { r := image.Rect(x, y, x+(b.dim-1)*b.squareWidth+wid, y+wid) draw.Draw(m, r, image.Black, ZP, draw.Src) y += b.squareHeight } // Points. spot := 4 if b.dim < 13 { spot = 3 } points := []IJ{ {spot, spot}, {spot, (b.dim + 1) / 2}, {spot, b.dim + 1 - spot}, {(b.dim + 1) / 2, spot}, {(b.dim + 1) / 2, (b.dim + 1) / 2}, {(b.dim + 1) / 2, b.dim + 1 - spot}, {b.dim + 1 - spot, spot}, {b.dim + 1 - spot, (b.dim + 1) / 2}, {b.dim + 1 - spot, b.dim + 1 - spot}, } for _, ij := range points { b.drawPoint(m, ij) } // Pieces. for i := 1; i <= b.dim; i++ { for j := 1; j <= b.dim; j++ { ij := IJ{i, j} if p := b.piece(ij); p != nil { b.drawPiece(m, ij, p) } } } } func (b *Board) drawPoint(m *image.RGBA, ij IJ) { pt := ij.XYCenter(&b.Dims) wid := b.lineWidth sz := wid * 3 / 2 r := image.Rect(pt.x-sz, pt.y-sz, pt.x+wid+sz, pt.y+wid+sz) draw.Draw(m, r, image.Black, ZP, draw.Src) } func (b *Board) drawPiece(m *image.RGBA, ij IJ, piece *Piece) { xy := ij.XYStone(&b.Dims) xy = xy.Add(piece.delta) draw.DrawMask(m, xy, piece.stone.image, ZP, piece.stone.mask, ZP, draw.Over) } func (b *Board) click(m *image.RGBA, x, y, button int) bool { ij, ok := XY{x, y}.IJ(&b.Dims) if !ok { return false } switch button { default: return false case 1: b.putPiece(ij, b.selectBlackPiece()) case 2: b.putPiece(ij, b.selectWhitePiece()) case 3: b.putPiece(ij, nil) } render(m, b) // TODO: Connect this to paint events. return true }