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() }