pixel-examples/community/amidakuji/ladder.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()
}