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