func (b *Board) Print(w io.Writer) error {
	var top, middle, bottom bytes.Buffer
	top.WriteRune('┏')
	middle.WriteRune('┠')
	bottom.WriteRune('┗')
	for j := 1; j < b.width; j++ {
		top.Write([]byte("━┯"))
		middle.Write([]byte("─┼"))
		bottom.Write([]byte("━┷"))
	}
	top.Write([]byte("━┓\n"))
	middle.Write([]byte("─┨\n"))
	bottom.Write([]byte("━┛\n"))
	out := bytes.NewBuffer(top.Bytes())
	b.lock.RLock()
	for i := 0; i < b.height; i++ {
		out.WriteRune('┃')
		for j := 0; j < b.width; j++ {
			if b.fields[i][j].queen {
				out.WriteRune('♛')
			} else if b.fields[i][j].attacks > 0 {
				out.WriteRune('•')
			} else {
				out.WriteRune(' ')
			}
			if j < (b.width - 1) {
				out.WriteRune('│')
			}
		}
		out.Write([]byte("┃\n"))
		if i < (b.height - 1) {
			out.Write(middle.Bytes())
		}
	}
	b.lock.RUnlock()
	out.Write(bottom.Bytes())
	_, err := w.Write(out.Bytes())
	return err
}
Print writes a human readable representation of the current board state to
the given writer
package queensboard
import (
	"bytes"
	"fmt"
	"io"
	"sync"
)
var (
	ErrorOperationNotPermitted = fmt.Errorf("operation not permitted")
	ErrorOutOfBounds = fmt.Errorf("out of bounds coordinates")
	ErrorInvalidBoardDimensions = fmt.Errorf("invalid board dimensions")
)
type Coordinates struct {
	X, Y int
}
type field struct {
	queen   bool  
	attacks int   
}
type Board struct {
	lock   sync.RWMutex
	fields [][]field
	queens int
	width  int
	height int
}
func New() *Board {
	b, _ := NewCustom(8, 8)
	return b
}
func NewCustom(width, height int) (*Board, error) {
	if width < 1 || height < 1 {
		return nil, ErrorInvalidBoardDimensions
	}
	b := &Board{
		width:  width,
		height: height,
	}
	b.fields = make([][]field, b.height, b.height)
	for i := range b.fields {
		b.fields[i] = make([]field, b.width, b.width)
	}
	return b, nil
}
func (b *Board) withinBounds(x, y int) bool {
	if x >= 0 && y >= 0 && x < b.width && y < b.height {
		return true
	}
	return false
}
func (b *Board) PlaceQueen(c Coordinates) error {
	if !b.withinBounds(c.X, c.Y) {
		return ErrorOutOfBounds
	}
	b.lock.Lock()
	defer b.lock.Unlock()
	if b.fields[c.Y][c.X].queen || b.fields[c.Y][c.X].attacks > 0 {
		return ErrorOperationNotPermitted
	}
	for j := 0; b.withinBounds(j, c.Y); j++ {
		b.fields[c.Y][j].attacks++
	}
	for i := 0; b.withinBounds(c.X, i); i++ {
		b.fields[i][c.X].attacks++
	}
	for j, i := c.X-1, c.Y-1; b.withinBounds(j, i); {
		b.fields[i][j].attacks++
		j--
		i--
	}
	for j, i := c.X+1, c.Y-1; b.withinBounds(j, i); {
		b.fields[i][j].attacks++
		j++
		i--
	}
	for j, i := c.X-1, c.Y+1; b.withinBounds(j, i); {
		b.fields[i][j].attacks++
		j--
		i++
	}
	for j, i := c.X+1, c.Y+1; b.withinBounds(j, i); {
		b.fields[i][j].attacks++
		j++
		i++
	}
	b.fields[c.Y][c.X].attacks = 0
	b.fields[c.Y][c.X].queen = true
	b.queens++
	return nil
}
func (b *Board) RemoveQueen(c Coordinates) error {
	if !b.withinBounds(c.X, c.Y) {
		return ErrorOutOfBounds
	}
	b.lock.Lock()
	defer b.lock.Unlock()
	if !b.fields[c.Y][c.X].queen {
		return ErrorOperationNotPermitted
	}
	for j := 0; b.withinBounds(j, c.Y); j++ {
		b.fields[c.Y][j].attacks--
	}
	for i := 0; b.withinBounds(c.X, i); i++ {
		b.fields[i][c.X].attacks--
	}
	for j, i := c.X-1, c.Y-1; b.withinBounds(j, i); {
		b.fields[i][j].attacks--
		j--
		i--
	}
	for j, i := c.X+1, c.Y-1; b.withinBounds(j, i); {
		b.fields[i][j].attacks--
		j++
		i--
	}
	for j, i := c.X-1, c.Y+1; b.withinBounds(j, i); {
		b.fields[i][j].attacks--
		j--
		i++
	}
	for j, i := c.X+1, c.Y+1; b.withinBounds(j, i); {
		b.fields[i][j].attacks--
		j++
		i++
	}
	b.fields[c.Y][c.X].attacks = 0
	b.fields[c.Y][c.X].queen = false
	b.queens--
	return nil
}
func (b *Board) Queens() int {
	b.lock.RLock()
	defer b.lock.RUnlock()
	return b.queens
}
func (b *Board) AvailableFields() []Coordinates {
	var cs []Coordinates
	b.lock.RLock()
	defer b.lock.RUnlock()
	for i := range b.fields {
		for j := range b.fields[i] {
			if b.fields[i][j].attacks > 0 {
				continue
			}
			if b.fields[i][j].queen {
				continue
			}
			cs = append(cs, Coordinates{X: j, Y: i})
		}
	}
	return cs
}