335 lines
9.4 KiB
Go
335 lines
9.4 KiB
Go
package main
|
|
|
|
import (
|
|
"math/rand"
|
|
"sync"
|
|
|
|
gg "github.com/faiface/pixel-examples/community/amidakuji/glossary"
|
|
|
|
"github.com/faiface/pixel"
|
|
"github.com/faiface/pixel/imdraw"
|
|
"golang.org/x/image/colornames"
|
|
)
|
|
|
|
// Ladder is an imdraw that does not animate at all,
|
|
// hence does not need to be modified every frame.
|
|
// Something kinda static and bone-like.
|
|
type Ladder struct {
|
|
imd *imdraw.IMDraw // shared variable
|
|
mutex sync.Mutex // synchronize
|
|
//
|
|
bound pixel.Rect
|
|
grid [][]pixel.Vec
|
|
bridges [][]bool
|
|
nParticipants int
|
|
nLevel int
|
|
paddingTop float64
|
|
paddingRight float64
|
|
paddingBottom float64
|
|
paddingLeft float64
|
|
colors []pixel.RGBA
|
|
}
|
|
|
|
// NewLadder is a constructor.
|
|
func NewLadder(_nParticipants, _nLevel int,
|
|
_width, _height,
|
|
_paddingTop, _paddingRight,
|
|
_paddingBottom, _paddingLeft float64) *Ladder {
|
|
|
|
// get random colors
|
|
colors := []pixel.RGBA{}
|
|
for i := 0; i < _nParticipants; i++ {
|
|
colors = append(colors, gg.RandomNiceColor())
|
|
}
|
|
|
|
// new grid
|
|
newGrid := func(nRow, nCol int) [][]pixel.Vec {
|
|
arr := make([][]pixel.Vec, nRow)
|
|
for i := range arr {
|
|
arr[i] = make([]pixel.Vec, nCol)
|
|
}
|
|
// log.Println(arr) //
|
|
return arr
|
|
}
|
|
|
|
// new bridges
|
|
newBridges := func(nParticipants, nLevel int) [][]bool {
|
|
nRow := nParticipants - 1
|
|
nCol := nLevel
|
|
arr := make([][]bool, nRow)
|
|
for i := range arr {
|
|
arr[i] = make([]bool, nCol)
|
|
}
|
|
return arr
|
|
}
|
|
|
|
// init ladder
|
|
l := Ladder{
|
|
imd: imdraw.New(nil),
|
|
bound: pixel.R(0, 0, _width, _height),
|
|
grid: newGrid(_nParticipants, _nLevel),
|
|
bridges: newBridges(_nParticipants, _nLevel),
|
|
nParticipants: _nParticipants,
|
|
nLevel: _nLevel,
|
|
paddingTop: _paddingTop,
|
|
paddingBottom: _paddingBottom,
|
|
paddingRight: _paddingRight,
|
|
paddingLeft: _paddingLeft,
|
|
colors: colors,
|
|
}
|
|
|
|
// init grid
|
|
updateGrid := func(l *Ladder) {
|
|
for participant := range l.grid { // row
|
|
for level := range l.grid[participant] { // col
|
|
y := l.Height() - (float64(participant) * l.DistParticipant()) // reverse
|
|
x := float64(level) * l.DistLevel()
|
|
y -= l.paddingTop
|
|
x += l.paddingLeft
|
|
l.grid[participant][level] = pixel.V(x, y)
|
|
}
|
|
}
|
|
} // Indices would not be aligned with the screen coordinates. (Reverse Y - Rows)
|
|
updateGrid(&l)
|
|
|
|
// log.Println(l.grid) //
|
|
return &l
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Important methods
|
|
|
|
// Draw guarantees the thread safety, though it's not a necessary condition.
|
|
// It is quite dangerous to access this struct's member (imdraw) directly from outside these methods.
|
|
func (l *Ladder) Draw(t pixel.Target) {
|
|
l.mutex.Lock()
|
|
defer l.mutex.Unlock()
|
|
|
|
if l.imd == nil { // isInvisible set to true.
|
|
return // An empty image is drawn.
|
|
}
|
|
|
|
l.imd.Draw(t)
|
|
}
|
|
|
|
// Update draws a ladder on an imdraw.
|
|
func (l *Ladder) Update() {
|
|
ptsMovedAbout := func(sub pixel.Vec, pts ...pixel.Vec) (ptsMoved []pixel.Vec) {
|
|
ptsMoved = make([]pixel.Vec, len(pts))
|
|
copy(ptsMoved, pts)
|
|
for i, vec := range ptsMoved {
|
|
ptsMoved[i] = vec.Sub(sub)
|
|
}
|
|
// log.Println(ptsMoved) // debug
|
|
// log.Println(pts) // debug
|
|
return ptsMoved
|
|
}
|
|
|
|
ptsStart := l.PtsAtLevelOfPicks()
|
|
ptsEnd := l.PtsAtLevelOfPrizes()
|
|
|
|
circleRadius := 20.0
|
|
circlePts := ptsMovedAbout(pixel.V(circleRadius+10, 0), ptsStart...)
|
|
|
|
// lock shared imdraw access
|
|
l.mutex.Lock()
|
|
defer l.mutex.Unlock()
|
|
|
|
// imdraw (a state machine)
|
|
if l.imd == nil { // lazy creation
|
|
l.imd = imdraw.New(nil)
|
|
}
|
|
imd := l.imd
|
|
imd.Clear()
|
|
|
|
// draw lanes
|
|
imd.Color = colornames.White
|
|
imd.EndShape = imdraw.RoundEndShape
|
|
for i := range ptsStart {
|
|
imd.Push(ptsStart[i], ptsEnd[i])
|
|
imd.Line(13)
|
|
}
|
|
|
|
// draw bridges
|
|
imd.Color = colornames.White
|
|
imd.EndShape = imdraw.RoundEndShape
|
|
for nrow, row := range l.bridges {
|
|
for ncol, e := range row {
|
|
if e {
|
|
imd.Push(l.grid[nrow][ncol], l.grid[nrow+1][ncol])
|
|
imd.Line(13)
|
|
}
|
|
}
|
|
}
|
|
|
|
// draw start points
|
|
imd.EndShape = imdraw.RoundEndShape
|
|
for i := range ptsStart {
|
|
imd.Color = l.colors[i]
|
|
imd.Push(circlePts[i])
|
|
}
|
|
imd.Circle(circleRadius, 0)
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Read only methods
|
|
|
|
// Height returns the height of a ladder.
|
|
// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
|
|
func (l Ladder) Height() float64 {
|
|
return l.bound.H()
|
|
}
|
|
|
|
// Width returns the width of a ladder.
|
|
// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
|
|
func (l Ladder) Width() float64 {
|
|
return l.bound.W()
|
|
}
|
|
|
|
// DistLevel returns the distance between each level.
|
|
// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
|
|
func (l Ladder) DistLevel() float64 {
|
|
return (l.Width() - (l.paddingLeft + l.paddingRight)) / float64(l.nLevel-1)
|
|
}
|
|
|
|
// DistParticipant returns the distance between each lane.
|
|
// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
|
|
func (l Ladder) DistParticipant() float64 {
|
|
return (l.Height() - (l.paddingTop + l.paddingBottom)) / float64(l.nParticipants-1)
|
|
}
|
|
|
|
// PtsAtLevelOfPicks returns all starting points of a ladder.
|
|
// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
|
|
func (l Ladder) PtsAtLevelOfPicks() (ret []pixel.Vec) {
|
|
const levelOfDraw int = 0 // where it starts
|
|
ret = make([]pixel.Vec, l.nParticipants, l.nParticipants)
|
|
|
|
for participant := range l.grid { // row
|
|
ret[participant] = l.grid[participant][levelOfDraw]
|
|
}
|
|
|
|
// log.Println(len(ret), ret) //
|
|
return ret //[:l.nParticipants] //
|
|
}
|
|
|
|
// PtAtLevelOfPicks returns a starting point of a ladder.
|
|
// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
|
|
func (l Ladder) PtAtLevelOfPicks(participant int) pixel.Vec {
|
|
const levelOfDraw int = 0 // where it starts
|
|
return l.grid[participant][levelOfDraw]
|
|
}
|
|
|
|
// PtsAtLevelOfPrizes returns all end points of a ladder.
|
|
// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
|
|
func (l Ladder) PtsAtLevelOfPrizes() (ret []pixel.Vec) {
|
|
levelOfPrize := l.nLevel - 1 // where it ends
|
|
ret = make([]pixel.Vec, l.nParticipants, l.nParticipants)
|
|
|
|
for participant := range l.grid { // row
|
|
ret[participant] = l.grid[participant][levelOfPrize]
|
|
}
|
|
|
|
// log.Println(len(ret), ret) //
|
|
return ret //[:l.nParticipants] //
|
|
}
|
|
|
|
// PtAtLevelOfPrizes returns an end point of a ladder.
|
|
// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
|
|
func (l Ladder) PtAtLevelOfPrizes(participant int) pixel.Vec {
|
|
levelOfPrize := l.nLevel - 1 // where it ends
|
|
return l.grid[participant][levelOfPrize]
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// Methods that write to itself
|
|
|
|
// ClearBridges of a ladder.
|
|
// Only values are changed, not the pointers.
|
|
func (l *Ladder) ClearBridges() {
|
|
for _, row := range l.bridges {
|
|
for i := range row {
|
|
row[i] = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// GenerateRandomBridges of an approximate amount.
|
|
// Only values are changed, not the pointers.
|
|
func (l *Ladder) GenerateRandomBridges(amountApprox int) {
|
|
pickOneBridgeInRandom := func(l *Ladder) {
|
|
nRow := len(l.bridges)
|
|
nCol := l.nLevel
|
|
row := rand.Intn(int(nRow)) // participant
|
|
col := rand.Intn(int(nCol)) // level
|
|
// check right
|
|
isOkRight := func(rowRight, col int) bool {
|
|
includeLowerBound := func(rowRight int) bool {
|
|
return rowRight >= 0
|
|
}
|
|
if !includeLowerBound(rowRight) { // out of bound
|
|
return true
|
|
}
|
|
if !l.bridges[rowRight][col] {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
// check left
|
|
isOkLeft := func(rowLeft, col int) bool {
|
|
excludeUpperBound := func(rowLeft int) bool {
|
|
return rowLeft < len(l.bridges)
|
|
}
|
|
if !excludeUpperBound(rowLeft) { // out of bound
|
|
return true
|
|
}
|
|
if !l.bridges[rowLeft][col] {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
rowRight := row - 1
|
|
rowLeft := row + 1
|
|
if isOkRight(rowRight, col) && isOkLeft(rowLeft, col) {
|
|
l.bridges[row][col] = true
|
|
}
|
|
} // func
|
|
|
|
// repeat
|
|
for i := 0; i < amountApprox; i++ {
|
|
pickOneBridgeInRandom(l)
|
|
}
|
|
} // method
|
|
|
|
// RegenerateRandomBridges clears out all bridges and then GenerateRandomBridges() an approximate amount.
|
|
// Only values are changed, not the pointers.
|
|
func (l *Ladder) RegenerateRandomBridges(amountApprox int) {
|
|
l.ClearBridges()
|
|
l.GenerateRandomBridges(amountApprox)
|
|
}
|
|
|
|
// RegenerateRandomColors sets all colors of a ladder random for each.
|
|
// Only values are changed, not the pointers.
|
|
func (l *Ladder) RegenerateRandomColors() {
|
|
for i := range l.colors {
|
|
l.colors[i] = gg.RandomNiceColor()
|
|
}
|
|
}
|
|
|
|
// Reset all bridges and colors.
|
|
// Only values are changed, not the pointers.
|
|
func (l *Ladder) Reset() {
|
|
aboutTwo := l.nParticipants * (l.nLevel - 1) * 2
|
|
aboutOne := l.nParticipants * (l.nLevel - 1)
|
|
aboutHalf := (l.nParticipants * (l.nLevel - 1)) / 2
|
|
//
|
|
var pick [4]int
|
|
pick[0] = aboutTwo
|
|
pick[1] = aboutOne
|
|
pick[2] = aboutHalf
|
|
i := rand.Intn(3)
|
|
// log.Println(i) //
|
|
//
|
|
l.RegenerateRandomBridges(pick[i])
|
|
l.RegenerateRandomColors()
|
|
}
|