diff --git a/Readme.md b/Readme.md index c29c77c..3ed7c98 100644 --- a/Readme.md +++ b/Readme.md @@ -7,13 +7,20 @@ sshOurboros -- multiplayer game where snake eats itself and grows *To play go here `ssh web2u.org -p6996`* *The Rules:* + 1. The goal is to claim the most space 2. Secondary goal is to kill as many other snakes as you can + *The how:* + 1. To claim space you need to either eat your own tail or reach tiles you've already claimed, tiles that are enclosed when you do so become yours! 2. To kill other snakes you hit their tails +3. To speed up press arrow of the same direction you going toward mulitple times +4. To slow down either press "space" or the opposite direction to gradually slow down + *To watchout:* + 1. Other players can kill you 2. Other players can take your tiles. diff --git a/internal/game/GameManager.go b/internal/game/GameManager.go index 40c04b8..825204c 100644 --- a/internal/game/GameManager.go +++ b/internal/game/GameManager.go @@ -135,80 +135,92 @@ func (gm *GameManager) processGameTick() { return true } - nextTile := player.GetNextTile() - if IsWall(nextTile.Y, nextTile.X) { - player.isDead = true - gm.PlayerManager.SunsetPlayersChannel <- player - return true - } + if player.Speed < 0 { + if player.ticksSkippedCount < player.Speed*-1 { + // we are skipping this tick because we are slow + player.ticksSkippedCount += 1 + return true + } - player.isSafe = false + player.ticksSkippedCount = 0 + } - if nextTile.OwnerColor != nil && nextTile.OwnerColor != player.Color { - nextTileOwnerAny, _ := gm.Players.Load(*nextTile.OwnerColor) - if nextTileOwnerAny == nil { + nextTiles := player.GetNextTiles() + for _, nextTile := range nextTiles { + if IsWall(nextTile.Y, nextTile.X) { + player.isDead = true + gm.PlayerManager.SunsetPlayersChannel <- player return true } - nextTileOwner := nextTileOwnerAny.(*Player) - if nextTileOwner.isDead || nextTileOwner.isSafe { - return true + player.isSafe = false + + if nextTile.OwnerColor != nil && nextTile.OwnerColor != player.Color { + nextTileOwnerAny, _ := gm.Players.Load(*nextTile.OwnerColor) + if nextTileOwnerAny == nil { + return true + } + + nextTileOwner := nextTileOwnerAny.(*Player) + if nextTileOwner.isDead || nextTileOwner.isSafe { + return true + } + + if nextTileOwner.Location == nextTile { + nextTileOwner.isDead = true + player.isDead = true + player.Kills += 1 + nextTileOwner.Kills += 1 + + gm.PlayerManager.SunsetPlayersChannel <- nextTileOwner + gm.PlayerManager.SunsetPlayersChannel <- player + return true + } + + if nextTile.IsTail { + nextTileOwner.isDead = true + gm.PlayerManager.SunsetPlayersChannel <- nextTileOwner + + player.Kills += 1 + nextTile.OwnerColor = player.Color + nextTile.IsTail = true + player.Tail.tailLock.Lock() + player.Tail.tailTiles = append(player.Tail.tailTiles, nextTile) + player.Tail.tailLock.Unlock() + + player.Location = nextTile + + return true + } + } - if nextTileOwner.Location == nextTile { - nextTileOwner.isDead = true - player.isDead = true - player.Kills += 1 - nextTileOwner.Kills += 1 + if nextTile.OwnerColor == player.Color && len(player.Tail.tailTiles) > 0 { + select { + case gm.SpaceFillerService.SpaceFillerChan <- player: + // Successfully sent direction + default: + gm.SpaceFillerService = getNewSpaceFiller(gm.GameMap) + log.Printf("space fill channel is full") + } - gm.PlayerManager.SunsetPlayersChannel <- nextTileOwner - gm.PlayerManager.SunsetPlayersChannel <- player + player.Location = nextTile + player.isSafe = true return true } - if nextTile.IsTail { - nextTileOwner.isDead = true - gm.PlayerManager.SunsetPlayersChannel <- nextTileOwner - - player.Kills += 1 + if nextTile.OwnerColor != player.Color { nextTile.OwnerColor = player.Color nextTile.IsTail = true + nextTile.Direction = player.CurrentDirection player.Tail.tailLock.Lock() player.Tail.tailTiles = append(player.Tail.tailTiles, nextTile) player.Tail.tailLock.Unlock() - - player.Location = nextTile - - return true - } - - } - - if nextTile.OwnerColor == player.Color && len(player.Tail.tailTiles) > 0 { - select { - case gm.SpaceFillerService.SpaceFillerChan <- player: - // Successfully sent direction - default: - gm.SpaceFillerService = getNewSpaceFiller(gm.GameMap) - log.Printf("space fill channel is full") } player.Location = nextTile - player.isSafe = true - return true - } - - if nextTile.OwnerColor != player.Color { - nextTile.OwnerColor = player.Color - nextTile.IsTail = true - nextTile.Direction = player.CurrentDirection - player.Tail.tailLock.Lock() - player.Tail.tailTiles = append(player.Tail.tailTiles, nextTile) - player.Tail.tailLock.Unlock() } - player.Location = nextTile - if player.BotStrategy != nil { gm.BotStrategyWg.Add(1) go func() { diff --git a/internal/game/Player.go b/internal/game/Player.go index ba82a3f..0f1d1d3 100644 --- a/internal/game/Player.go +++ b/internal/game/Player.go @@ -19,19 +19,21 @@ type AllTiles struct { } type Player struct { - Name string - SshSession ssh.Session - Color *int - ClaimedEstate int - Location *Tile - CurrentDirection Direction - UpdateChannel chan tea.Msg - BotStrategy Strategy - Kills int - isDead bool - isSafe bool - Tail Tail - AllTiles AllTiles + Name string + SshSession ssh.Session + Color *int + ClaimedEstate int + Location *Tile + CurrentDirection Direction + UpdateChannel chan tea.Msg + BotStrategy Strategy + Kills int + isDead bool + isSafe bool + Speed int + ticksSkippedCount int //this is used if speed is below 0 + Tail Tail + AllTiles AllTiles } func CreateNewPlayer(sshSession ssh.Session, name string, color int, spawnPoint *Tile) *Player { @@ -54,33 +56,44 @@ func CreateNewPlayer(sshSession ssh.Session, name string, color int, spawnPoint tailTiles: []*Tile{ spawnPoint, }}, - UpdateChannel: make(chan tea.Msg, 256), - Kills: 0, - isDead: false, - isSafe: false, + UpdateChannel: make(chan tea.Msg, 256), + Kills: 0, + isDead: false, + isSafe: false, + Speed: 0, + ticksSkippedCount: 0, } } -func (p *Player) GetNextTile() *Tile { - nextX := p.Location.X + p.CurrentDirection.Dx - nextY := p.Location.Y + p.CurrentDirection.Dy +func (p *Player) GetNextTiles() []*Tile { + tilesToGet := max(1, p.Speed) + currLocationX := p.Location.X + currLocationY := p.Location.Y + result := []*Tile{} - if nextX < 0 { - nextX = MapColCount - 1 - } else if nextX >= MapColCount { - nextX = 0 - } - if nextY < 0 { - nextY = MapRowCount - 1 - } else if nextY >= MapRowCount { - nextY = 0 - } + for range tilesToGet { + nextX := currLocationX + p.CurrentDirection.Dx + nextY := currLocationY + p.CurrentDirection.Dy + + if nextX < 0 { + nextX = MapColCount - 1 + } else if nextX >= MapColCount { + nextX = 0 + } + if nextY < 0 { + nextY = MapRowCount - 1 + } else if nextY >= MapRowCount { + nextY = 0 + } - if nextY < MapRowCount && nextX < MapColCount { - return getInitGameMap()[nextY][nextX] + if nextY < MapRowCount && nextX < MapColCount { + result = append(result, getInitGameMap()[nextY][nextX]) + currLocationX = nextX + currLocationY = nextY + } } - return nil + return result } func (p *Player) resetTailData() { @@ -112,3 +125,7 @@ func (p *Player) GetConsolidateTiles() float64 { p.AllTiles.AllPlayerTiles = updatedTiles return claimedLand } + +func (p *Player) ResetSpeed() { + p.Speed = 0 +} diff --git a/internal/ui/GameView.go b/internal/ui/GameView.go index 7f9533f..780b01f 100644 --- a/internal/ui/GameView.go +++ b/internal/ui/GameView.go @@ -113,15 +113,23 @@ func (m GameViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { engineCommand = game.Direction{Dx: -1, Dy: 0, PlayerColor: *currentPlayer.Color} case "d", "right": engineCommand = game.Direction{Dx: 1, Dy: 0, PlayerColor: *currentPlayer.Color} + case " ": + currentPlayer.ResetSpeed() default: return m, nil } - if (engineCommand.Dx == -currentPlayer.CurrentDirection.Dx && engineCommand.Dy == -currentPlayer.CurrentDirection.Dy) || (engineCommand == currentPlayer.CurrentDirection) { - return m, nil + oldSpeed := currentPlayer.Speed + if engineCommand.Dx == -currentPlayer.CurrentDirection.Dx && engineCommand.Dy == -currentPlayer.CurrentDirection.Dy { + currentPlayer.Speed -= 1 + } + + if engineCommand == currentPlayer.CurrentDirection { + currentPlayer.Speed += 1 } - if (engineCommand == game.Direction{}) { + //speed was altered no need to bather with direction switch + if oldSpeed != currentPlayer.Speed { return m, nil } @@ -379,9 +387,11 @@ func (m GameViewModel) renderStatusPanel(currentPlayer *game.Player, width int, // (This section remains unchanged from your original code) statusContent.WriteString(lipgloss.NewStyle().Bold(true).Render("--- Player Stats ---\n")) claimedLand := currentPlayer.GetConsolidateTiles() + statusContent.WriteString(fmt.Sprintf("Direction: %c\n", headRunes[game.Direction{Dx: currentPlayer.CurrentDirection.Dx, Dy: currentPlayer.CurrentDirection.Dy}])) + statusContent.WriteString(fmt.Sprintf("Speed: %d \n", currentPlayer.Speed)) + statusContent.WriteString(fmt.Sprintf("Kills: %d\n", currentPlayer.Kills)) statusContent.WriteString(fmt.Sprintf("Claimed: %.2f %% of land\n", claimedLand*100/float64(game.MapColCount*game.MapColCount))) - statusContent.WriteString(fmt.Sprintf("Direction: %c\n", headRunes[game.Direction{Dx: currentPlayer.CurrentDirection.Dx, Dy: currentPlayer.CurrentDirection.Dy}])) statusContent.WriteString("\n") botCount := 0