func (m *Menu) moveRight() { item := m.Items[m.FocusedIndex] switch { case item.Selector != nil: item.Selector.selectedIndex = (item.Selector.selectedIndex + 1) % len(item.Selector.Choices) audio.Play(audio.SoundMove) case item.Slider != nil: if item.Slider.Value++; item.Slider.Value > item.Slider.Max { item.Slider.Value = item.Slider.Min } audio.Play(audio.SoundMove) } }
func (m *Menu) moveUp() { if m.FocusedIndex -= 1; m.FocusedIndex < 0 { m.FocusedIndex = len(m.Items) - 1 m.Selected = false } audio.Play(audio.SoundMove) }
func (m *Menu) moveLeft() { item := m.Items[m.FocusedIndex] switch { case item.Selector != nil: if item.Selector.selectedIndex--; item.Selector.selectedIndex < 0 { item.Selector.selectedIndex = len(item.Selector.Choices) - 1 } audio.Play(audio.SoundMove) case item.Slider != nil: if item.Slider.Value--; item.Slider.Value < item.Slider.Min { item.Slider.Value = item.Slider.Max } audio.Play(audio.SoundMove) } }
func (b *Board) updateMatches() { // Update each match - clearing one block at a time. for i := 0; i < len(b.matches); i++ { m := b.matches[i] finished := true loop: // Animate each block one at a time. Break if it is still animating. for _, mc := range m.cells { block := b.blockAt(mc.x, mc.y) switch { case block.State == BlockCracked: block.State = BlockExploding audio.Play(audio.SoundClear) finished = false break loop case block.State != BlockExploded: finished = false break loop } } // Clear the blocks and remove the chain once all animations are done. if finished { for _, mc := range m.cells { b.blockAt(mc.x, mc.y).State = BlockClearPausing } b.matches = append(b.matches[:i], b.matches[i+1:]...) i-- } } }
// swap swaps the left block with the right block. func (l *Block) swap(r *Block, swapID int) { if blockStateSwappable[l.State] && blockStateSwappable[r.State] { l.State, r.State = r.State, l.State l.Color, r.Color = r.Color, l.Color l.swapID, r.swapID = swapID, swapID l.Dropping, r.Dropping = false, false numBlocks := 0 switch l.State { case BlockStatic: l.setState(BlockSwappingFromRight) numBlocks++ case BlockClearPausing, BlockCleared: l.setState(BlockCleared) } switch r.State { case BlockStatic: r.setState(BlockSwappingFromLeft) numBlocks++ case BlockClearPausing, BlockCleared: r.setState(BlockCleared) } if numBlocks > 0 { audio.Play(audio.SoundSwap) } } }
func (b *Board) addNewMatches() { // Find new matches and append them to the overall list. matches := findGroupedMatches(b) b.matches = append(b.matches, matches...) var dirtyLinks []*chainLink for _, m := range matches { hasDroppedBlock := false for _, c := range m.cells { block := b.blockAt(c.x, c.y) block.State = BlockFlashing hasDroppedBlock = hasDroppedBlock || block.Dropping if block.Dropping { block.Dropping = false audio.Play(audio.SoundThud) } b.numUpdateBlocksCleared++ b.numSpeedBlocksCleared++ } var link *chainLink if hasDroppedBlock { linkLoop: for _, l := range b.chainLinks { for _, lm := range l.matches { for _, lc := range lm.cells { for _, mc := range m.cells { if lc.x == mc.x && lc.y <= mc.y { link = l break linkLoop } } } } } } if link == nil { link = &chainLink{} b.chainLinks = append(b.chainLinks, link) } else { link.level++ } link.nextMatches = append(link.nextMatches, m) dirtyLinks = append(dirtyLinks, link) b.markerAt(m.cells[0].x, m.cells[0].y).show(len(m.cells), link.level) } for _, link := range dirtyLinks { link.matches = link.nextMatches link.nextMatches = nil } }
func (m *Menu) selectItem() { m.Selected = true audio.Play(audio.SoundSelect) }
func (m *Menu) moveDown() { m.FocusedIndex = (m.FocusedIndex + 1) % len(m.Items) m.Selected = false audio.Play(audio.SoundMove) }
func (s *Selector) moveRight() { if s.State == SelectorStatic { s.setState(SelectorMovingRight) audio.Play(audio.SoundMove) } }
func (s *Selector) moveDown() { if s.State == SelectorStatic && s.Y < s.ringCount-1 { s.setState(SelectorMovingDown) audio.Play(audio.SoundMove) } }
func (s *Selector) moveUp() { if s.State == SelectorStatic && s.Y > 0 { s.setState(SelectorMovingUp) audio.Play(audio.SoundMove) } }
func (b *Board) update() { b.numUpdateBlocksCleared = 0 advance := func(nextState BoardState) { if b.step++; b.step >= boardStateSteps[b.State] { b.setState(nextState) } } switch b.State { case BoardEntering: advance(BoardLive) case BoardLive: b.Selector.update() for _, r := range b.Rings { for _, c := range r.Cells { c.Block.update() c.Marker.update() } } // Find droppable blocks before matches to prevent mid-air matches. b.dropBlocks() b.addNewMatches() b.updateMatches() // Reset swap IDs for stationary blocks after new matches have been found. for _, r := range b.Rings { for _, c := range r.Cells { if c.Block.State == BlockStatic && c.Block.swapID != 0 { c.Block.swapID = 0 } } } if b.numSpeedBlocksCleared > requiredBlocksCleared { if b.speed++; b.speed > maxSpeed { b.speed = maxSpeed } b.numSpeedBlocksCleared = 0 } // Don't rise if there are pending matches. if len(b.matches) > 0 { return } // Don't rise if there are blocks with certain states. for _, r := range b.Rings { for _, c := range r.Cells { if !blockStateRiseable[c.Block.State] { return } } } // Reset the chain links since we are rising again. b.chainLinks = nil // Reset dropping flag since we are rising again. for _, r := range b.Rings { for _, c := range r.Cells { if c.Block.Dropping { c.Block.Dropping = false audio.Play(audio.SoundThud) } } } // Determine the rise rate. var riseRate float32 if b.useManualRiseRate { riseRate = manualRiseRate } else { riseRate = minRiseRate + riseRateDelta*float32(b.speed) } if b.Y += riseRate; b.Y > 1 { // Check that topmost ring is empty, so that it can be removed. for _, c := range b.Rings[0].Cells { if c.Block.State != BlockCleared { b.setState(BoardGameOver) return } } b.Y = 0 // Trim off the topmost ring and add a new spare ring. b.Rings = append(b.Rings[1:], b.SpareRings[0]) // Add a new spare ring, since one was taken away. b.SpareRings = append(b.SpareRings[1:], newRing(b.CellCount, b.numBlockColors, false)) // Adjust the selector down in case it was at the removed top ring. if b.Selector.Y--; b.Selector.Y < 0 { b.Selector.Y = 0 } } case BoardGameOver: b.step++ case BoardExiting: b.step++ } }
func (g *Game) KeyCallback(key glfw.Key, action glfw.Action) { if action != glfw.Press && action != glfw.Repeat { // Handle any release triggers. if key == glfw.KeyLeftAlt { g.Board.useManualRiseRate = false } return } if g.StateProgress(0) < 1 { return } switch g.State { case GamePlaying: switch key { case glfw.KeyLeft: g.Board.moveLeft() case glfw.KeyRight: g.Board.moveRight() case glfw.KeyDown: g.Board.moveDown() case glfw.KeyUp: g.Board.moveUp() case glfw.KeySpace: g.Board.swap() case glfw.KeyLeftAlt: g.Board.useManualRiseRate = true case glfw.KeyEscape: g.setState(GamePaused) g.Menu = pausedMenu g.Menu.reset() audio.Play(audio.SoundSelect) } case GameInitial, GamePaused: switch key { case glfw.KeyLeft: g.Menu.moveLeft() case glfw.KeyRight: g.Menu.moveRight() case glfw.KeyDown: g.Menu.moveDown() case glfw.KeyUp: g.Menu.moveUp() case glfw.KeyEnter, glfw.KeySpace: switch g.Menu.focused() { case MenuNewGameItem: g.Menu.selectItem() g.Menu = newGameMenu g.Menu.reset() case MenuExit: g.Menu.selectItem() g.setState(GameExiting) case MenuOK: g.Menu.selectItem() g.setState(GamePlaying) var numBlockColors int switch difficultyItem.Selector.Value() { case MenuEasy: numBlockColors = maxBlockColors - 2 case MenuMedium: numBlockColors = maxBlockColors - 1 default: numBlockColors = maxBlockColors } speed := speedItem.Slider.Value b := newBoard(numBlockColors, speed) h := newHUD(speed) if g.Board == nil { g.Board = b g.HUD = h } else { g.nextBoard = b g.nextHUD = h g.Board.exit() } case MenuContinueGame: g.Menu.selectItem() g.setState(GamePlaying) case MenuQuit: g.Menu.selectItem() g.Menu = mainMenu g.Menu.reset() } case glfw.KeyEscape: switch g.State { case GamePaused: g.setState(GamePlaying) audio.Play(audio.SoundSelect) } } } }