/tester/test_engine.go
Go | 916 lines | 790 code | 124 blank | 2 comment | 102 complexity | e431e17f6dd0bd1f5b948a0bf0ad0290 MD5 | raw file
- package main
- import (
- "fmt"
- "github.com/lanior/mysterious-bomberman/actor"
- "github.com/lanior/mysterious-bomberman/engine"
- . "github.com/lanior/mysterious-bomberman/server"
- . "github.com/lanior/mysterious-bomberman/tester/testlib"
- "regexp"
- "strings"
- "time"
- "unicode"
- )
- type gameHelper struct {
- Id int
- Settings *GameSettings
- Players []*actor.Actor
- Field []string
- State *WSGameState
- tick int
- playersMap []int
- }
- var emptyMap = []string{
- "............",
- "............",
- "....0>......",
- "......1.....",
- "............",
- "............",
- "............",
- }
- var playerPosRe = regexp.MustCompile(`\d`)
- func getInitialMapLine(line string) string {
- result := make([]byte, len(line)/2)
- for i := 0; i < len(line); i += 2 {
- if line[i] >= 'A' && line[i] <= 'Z' {
- result[i/2] = '%'
- } else if line[i] >= '0' && line[i] <= '9' {
- result[i/2] = byte(line[i+1])
- } else {
- result[i/2] = byte(line[i])
- }
- }
- return string(result)
- }
- var startedGameId int
- func newStartedGame(api *actor.Actor, seed int, field []string, settings *GameSettings) *gameHelper {
- playerCount := 0
- for _, row := range field {
- playerCount += len(playerPosRe.FindAllString(row, -1))
- }
- game := &gameHelper{
- Field: field,
- Settings: settings,
- Players: make([]*actor.Actor, playerCount),
- }
- startedGameId++
- playerNames := make([]string, playerCount)
- players := make([]*actor.Actor, playerCount)
- for i := 0; i < playerCount; i++ {
- playerNames[i] = fmt.Sprintf("gamer%d%d", i, startedGameId)
- players[i] = createUserAndLogin(api, playerNames[i], "asd")
- players[i].WSAuth()
- }
- host := players[0]
- mapId := host.Ok(&UploadMap{Name: "test map", Field: field, Settings: *settings}).(*UploadMapResponse).MapId
- game.Id = host.Ok(&CreateGame{
- Name: "test_game",
- Password: "",
- MapId: mapId,
- Settings: *settings,
- MaxPlayerCount: playerCount,
- }).(*CreateGameResponse).GameId
- host.Ok(&GameSetDebugMode{RandomSeed: seed})
- host.Ok(&StartGame{})
- for i := 1; i < playerCount; i++ {
- players[i].Ok(&JoinGame{GameId: game.Id})
- }
- for i := 1; i < playerCount; i++ {
- players[i].Ok(&StartGame{})
- }
- gameStartedEvents := make([]WSGameStarted, playerCount)
- playersByIndex := make([]int, playerCount)
- for i := 0; i < playerCount; i++ {
- players[i].ExpectEvent(&gameStartedEvents[i])
- playersByIndex[gameStartedEvents[i].CurrentPlayerIndex] = i
- }
- assert(len(gameStartedEvents[0].Players) == playerCount, "invalid player count")
- for _, playerName := range playerNames {
- found := false
- for _, name := range gameStartedEvents[0].Players {
- if name == playerName {
- found = true
- break
- }
- }
- assert(found, "player `%s` assumed to be in game", playerName)
- }
- game.State = host.Ok(&GameSetTick{Tick: 0}).(*GameSetTickResponse).GameState
- assert(game.State.Tick == 0, "expected tick 0")
- assert(len(game.State.Field) == len(field), "field height mismatch")
- for i, line := range game.State.Field {
- expected := getInitialMapLine(field[i])
- assert(line == expected, "field [line %d]: expected `%s`, got `%s`", i+1, expected, line)
- }
- game.playersMap = make([]int, playerCount)
- for i, player := range game.State.Players {
- index := -1
- if player.X%cellSize == 0 && player.Y%cellSize == 0 {
- x := player.X / cellSize
- y := player.Y / cellSize
- if x >= 0 && x < len(field[0]) && y >= 0 && y < len(field) {
- index = int(field[y][x*2] - '0')
- }
- }
- assert(index >= 0, "invalid player %d position", i)
- game.playersMap[index] = i
- game.Players[index] = players[playersByIndex[i]]
- }
- return game
- }
- func (game *gameHelper) Action(player int, dir, action string) {
- game.Player(player).Ok(
- &DebugGameAction{
- GameAction: WSGameAction{
- Direction: dir,
- Action: action,
- },
- },
- )
- }
- func (game *gameHelper) Tick(tickCount int) *WSGameState {
- game.tick += tickCount
- game.State = game.Player(0).Ok(&GameSetTick{Tick: game.tick}).(*GameSetTickResponse).GameState
- assert(game.State.Tick == game.tick, "game_set_tick: expected tick %d, got %d", game.tick, game.State.Tick)
- return game.State
- }
- func (game *gameHelper) GetMappedPlayerId(player int) int {
- return game.playersMap[player]
- }
- func (game *gameHelper) Player(player int) *actor.Actor {
- return game.Players[player]
- }
- func (game *gameHelper) PlayerState(player int) *GamePlayerState {
- return &game.State.Players[game.GetMappedPlayerId(player)]
- }
- func (game *gameHelper) PlayerStates() []*GamePlayerState {
- states := make([]*GamePlayerState, len(game.State.Players))
- for i := 0; i < len(states); i++ {
- states[i] = game.PlayerState(i)
- }
- return states
- }
- func (game *gameHelper) PlayerPos(player int) engine.Vector2 {
- return engine.Vector2{
- X: game.State.Players[game.GetMappedPlayerId(player)].X,
- Y: game.State.Players[game.GetMappedPlayerId(player)].Y,
- }
- }
- func (game *gameHelper) AssertPlayerPos(player int, expected engine.Vector2, format string, args ...interface{}) {
- assertPos(expected, game.PlayerPos(player), format, args...)
- }
- func assertPos(expected, got engine.Vector2, format string, args ...interface{}) {
- msg := fmt.Sprintf(format, args...)
- assert(expected == got, fmt.Sprintf("%s: expected pos (%d, %d), got (%d, %d)", msg, expected.X, expected.Y, got.X, got.Y))
- }
- func assertEqual(field string, expected, got int) {
- assert(expected == got, "expected %s = %d, got %d", field, expected, got)
- }
- func assertBombState(expected, got BombState) {
- assertPos(engine.Vector2{expected.X, expected.Y}, engine.Vector2{got.X, got.Y}, "bomb")
- assertEqual("created_at", expected.CreatedAt, got.CreatedAt)
- assertEqual("destroy_at", expected.DestroyAt, got.DestroyAt)
- assertEqual("flame_length", expected.FlameLength, got.FlameLength)
- // assertEqual("direction", expected.MovementDirection, got.MovementDirection)
- // assertEqual("speed", expected.Speed, got.Speed)
- }
- func assertExplosionState(expected, got ExplosionState) {
- assertPos(engine.Vector2{expected.X, expected.Y}, engine.Vector2{got.X, got.Y}, "explosion")
- assertEqual("created_at", expected.CreatedAt, got.CreatedAt)
- assertEqual("destroy_at", expected.DestroyAt, got.DestroyAt)
- assertEqual("up_length", expected.UpLength, got.UpLength)
- assertEqual("down_length", expected.DownLength, got.DownLength)
- assertEqual("left_length", expected.LeftLength, got.LeftLength)
- assertEqual("right_length", expected.RightLength, got.RightLength)
- }
- func testAllDirectionsWithPerpendiculars(fn func(dir, perpendicular engine.Vector2)) {
- forAllDirections(func(dir engine.Vector2) {
- fn(dir, dir.Perpendicular())
- fn(dir, dir.Perpendicular().ScaleBy(-1))
- })
- }
- func forAllDirections(fn func(dir engine.Vector2)) {
- fn(engine.Left)
- fn(engine.Right)
- fn(engine.Up)
- fn(engine.Down)
- }
- func cloneSettings(gs GameSettings) GameSettings {
- newSettings := gs
- newSettings.Items = map[string]ItemDescription{}
- for key, item := range gs.Items {
- newSettings.Items[key] = item
- }
- return newSettings
- }
- type obstacle struct {
- Code string
- Destructible bool
- }
- func generateExplosionTestMap(size, radius int, obs obstacle, direction engine.Vector2) (testMap []string) {
- obsCell := obs.Code
- if obs.Destructible {
- obsCell += "."
- } else {
- obsCell += obs.Code
- }
- lines := make([][]string, size)
- for j := range lines {
- lines[j] = make([]string, size)
- for i := range lines[j] {
- lines[j][i] = ".."
- }
- }
- lines[size/2][size/2] = "0."
- lines[0][0] = "1."
- center := engine.Vector2{X: size / 2, Y: size / 2}
- placeObstacle := func(dir engine.Vector2) {
- o := center.Add(dir.ScaleBy(radius))
- lines[o.Y][o.X] = obsCell
- }
- if direction == engine.None {
- forAllDirections(placeObstacle)
- } else {
- placeObstacle(direction)
- }
- testMap = make([]string, size)
- for i := range testMap {
- testMap[i] = strings.Join(lines[i], "")
- }
- return
- }
- func placeBomb(game *gameHelper, player int, blow bool) {
- ticksPerCell := cellSize / game.Settings.PlayerBaseSpeed
- startTick := game.tick
- game.Action(player, "none", "place_bomb")
- game.Tick(1)
- game.Action(player, "right", "none")
- game.Tick(ticksPerCell)
- game.Action(player, "down", "none")
- game.Tick(ticksPerCell)
- game.Action(player, "none", "none")
- if blow {
- game.Tick(game.Settings.BombDelay + startTick - 2*ticksPerCell)
- }
- }
- func EngineSpec(c Context, api *actor.Actor) {
- api.Ok(Reset{})
- c.Test("Movement", func(c Context) {
- c.Test("simple movement", func(c Context) {
- game := newStartedGame(api, 42, emptyMap, &gameSettings)
- pos := game.PlayerPos(0)
- testDirection := func(dir engine.Vector2) {
- dirName := TranslateDirection(dir)
- game.Action(0, dirName, "none")
- for i := 0; i < 10; i++ {
- pos = pos.Add(dir.ScaleBy(gameSettings.PlayerBaseSpeed))
- game.Tick(1)
- game.AssertPlayerPos(0, pos, "move %s", dirName)
- }
- game.Action(0, "none", "none")
- for i := 0; i < 3; i++ {
- game.Tick(1)
- game.AssertPlayerPos(0, pos, "move %s, stop", dirName)
- }
- }
- forAllDirections(testDirection)
- })
- c.Test("player cannot cross map borders", func(c Context) {
- var x, y, newX, newY int
- game := newStartedGame(api, 42, emptyMap, &gameSettings)
- maxDim := len(emptyMap[0]) / 2
- if len(emptyMap) > maxDim {
- maxDim = len(emptyMap)
- }
- ticks := maxDim*cellSize/gameSettings.PlayerBaseSpeed + 1
- game.Action(0, "left", "none")
- game.Tick(ticks)
- x, newX = 0, game.PlayerPos(0).X
- assert(newX == x, "move left: player 0: expected X = %d, got %d", x, newX)
- game.Action(0, "right", "none")
- game.Tick(ticks)
- x, newX = (len(emptyMap[0])/2-1)*cellSize, game.PlayerPos(0).X
- assert(newX == x, "move right: player 0: expected X = %d, got %d", x, newX)
- game.Action(0, "up", "none")
- game.Tick(ticks)
- y, newY = 0, game.PlayerPos(0).Y
- assert(newY == y, "move up: player 0: expected Y = %d, got %d", y, newY)
- game.Action(0, "down", "none")
- game.Tick(ticks)
- y, newY = (len(emptyMap)-1)*cellSize, game.PlayerPos(0).Y
- assert(newY == y, "move down: player 0: expected Y = %d, got %d", y, newY)
- })
- c.Test("players can cross each other", func(c Context) {
- game := newStartedGame(api, 42, []string{
- "........",
- "..0.1...",
- "........",
- }, &gameSettings)
- ticks := cellSize / gameSettings.PlayerBaseSpeed
- pos0 := game.PlayerPos(0).Add(engine.Right.ScaleBy(game.Settings.PlayerBaseSpeed * ticks))
- pos1 := game.PlayerPos(1).Add(engine.Left.ScaleBy(game.Settings.PlayerBaseSpeed * ticks))
- game.Action(0, "right", "none")
- game.Action(1, "left", "none")
- game.Tick(ticks)
- game.AssertPlayerPos(0, pos0, "cross player")
- game.AssertPlayerPos(1, pos1, "cross player")
- })
- c.Test("players cannot cross obstacles", func(c Context) {
- game := newStartedGame(api, 42, []string{
- "######%.%.%.",
- "##0.##%.1.%.",
- "######%.%.%.",
- }, &gameSettings)
- testDirection := func(dir engine.Vector2) {
- dirName := TranslateDirection(dir)
- for i := range game.Players {
- pos := game.PlayerPos(i)
- game.Action(i, dirName, "none")
- game.Tick(1)
- game.AssertPlayerPos(i, pos, "move %s, obstacle", dirName)
- }
- }
- forAllDirections(testDirection)
- })
- slideSettings := cloneSettings(gameSettings)
- slideSettings.MaxSlideLength = 3000
- slideSettings.PlayerBaseSpeed = 1000
- slideSettings.BombDelay = 10000
- moveToObstacle := func(game *gameHelper, dir, slideDir engine.Vector2, offset int) (pos engine.Vector2) {
- dirName := TranslateDirection(dir)
- slideDirName := TranslateDirection(slideDir)
- speed := game.Settings.PlayerBaseSpeed
- pos = game.PlayerPos(0)
- game.Action(0, slideDirName, "none")
- game.Tick(offset)
- pos = pos.Add(slideDir.ScaleBy(offset * speed))
- game.Action(0, dirName, "none")
- game.Tick(10)
- pos = pos.Add(dir.ScaleBy(10 * speed))
- game.AssertPlayerPos(0, pos, "player hits the wall while moving %s", dirName)
- return
- }
- testNoSlide := func(game *gameHelper, offset int) {
- testDirectionForProjection := func(dir, slideDir engine.Vector2) {
- dirName := TranslateDirection(dir)
- c.Test(fmt.Sprintf("move %s, no slide", dirName), func(c Context) {
- pos := moveToObstacle(game, dir, slideDir, offset)
- game.Tick(1)
- game.AssertPlayerPos(0, pos, "player doesn't slide while moving %s", dirName)
- })
- }
- testAllDirectionsWithPerpendiculars(testDirectionForProjection)
- }
- testSlide := func(game *gameHelper) {
- testDirectionForProjection := func(dir, slideDir engine.Vector2) {
- dirName := TranslateDirection(dir)
- slideDirName := TranslateDirection(slideDir)
- speed := game.Settings.PlayerBaseSpeed
- c.Test(fmt.Sprintf("move %s, slide %s, move %s", dirName, slideDirName, dirName), func(c Context) {
- pos := moveToObstacle(game, dir, slideDir, 8)
- game.Tick(1)
- pos = pos.Add(slideDir.ScaleBy(speed))
- game.AssertPlayerPos(0, pos, "player slides %s while moving %s", slideDirName, dirName)
- game.Tick(2)
- pos = pos.Add(slideDir.ScaleBy(speed)).Add(dir.ScaleBy(speed))
- game.AssertPlayerPos(0, pos, "player walks %s after sliding %s", dirName, slideDirName)
- })
- }
- testAllDirectionsWithPerpendiculars(testDirectionForProjection)
- }
- slideMap := []string{
- "....##....",
- "..........",
- "##..0...##",
- "..........",
- "....##....",
- "....1.....",
- }
- createMapWithBombs := func() (game *gameHelper) {
- bombMap := []string{
- "..........",
- "..........",
- "....0.....",
- "..........",
- "......1...",
- "..........",
- }
- game = newStartedGame(api, 42, bombMap, &slideSettings)
- ticksPerCell := cellSize / gameSettings.PlayerBaseSpeed
- forAllDirections(func(dir engine.Vector2) {
- dirName := TranslateDirection(dir)
- game.Action(1, dirName, "none")
- game.Tick(ticksPerCell)
- game.Action(1, "none", "place_bomb")
- game.Tick(1)
- game.Action(1, dirName, "none")
- game.Tick(2 * ticksPerCell)
- game.Action(1, TranslateDirection(dir.Perpendicular()), "none")
- game.Tick(ticksPerCell)
- })
- game.Action(1, "down", "none")
- game.Tick(ticksPerCell)
- return
- }
- c.Test("players slide", func(c Context) {
- testSlide(newStartedGame(api, 42, slideMap, &slideSettings))
- })
- c.Test("players slide with bombs", func(c Context) {
- testSlide(createMapWithBombs())
- })
- c.Test("players do not slide if projection > max_slide_length", func(c Context) {
- game := newStartedGame(api, 42, slideMap, &slideSettings)
- testNoSlide(game, 5)
- })
- c.Test("players do not slide if they cannot move", func(c Context) {
- noSlideMap := []string{
- "..######..",
- "##......##",
- "##..0...##",
- "##......##",
- "..######..",
- "....1.....",
- }
- game := newStartedGame(api, 42, noSlideMap, &slideSettings)
- testNoSlide(game, 8)
- })
- })
- c.Test("Bombs", func(c Context) {
- c.Test("player can place bomb", func(c Context) {
- game := newStartedGame(api, 42, emptyMap, &gameSettings)
- game.Action(0, "none", "place_bomb")
- game.Tick(1)
- pos := game.PlayerPos(0)
- assert(len(game.State.Bombs) == 1, "bomb at pos (%d, %d) expected, no bombs found", pos.X, pos.Y)
- bomb := game.State.Bombs[0]
- expectedState := BombState{
- X: pos.X,
- Y: pos.Y,
- MovementDirection: "none",
- Speed: game.Settings.BombSpeed,
- FlameLength: game.PlayerState(0).FlameLength,
- CreatedAt: 1,
- DestroyAt: 1 + game.Settings.BombDelay,
- }
- assertBombState(expectedState, bomb)
- })
- bombySettings := cloneSettings(gameSettings)
- bombySettings.PlayerBaseSpeed = 1000
- bombySettings.Items["bomb"] = ItemDescription{
- BombBonus: 1,
- MaxCount: 9,
- StartCount: 4,
- Weight: 18,
- }
- game := newStartedGame(api, 42, emptyMap, &bombySettings)
- c.Test("player can place one bomb at a cell", func(c Context) {
- bombs := game.PlayerState(0).RemainingBombs
- assert(bombs > 1, "more than one remaining bombs required for the test")
- game.Action(0, "right", "place_bomb")
- game.Tick(3)
- assert(len(game.State.Bombs) == 1, "one bomb expected, %d bombs found", len(game.State.Bombs))
- assertEqual("remaining_bombs", game.PlayerState(0).RemainingBombs, bombs-1)
- })
- c.Test("player places bomb when cancelling place_bomb at the same tick", func(c Context) {
- game.Action(0, "left", "place_bomb")
- game.Action(0, "left", "none")
- game.Tick(1)
- assert(len(game.State.Bombs) == 1, "one bomb expected, %d bombs found", len(game.State.Bombs))
- })
- ticksPerHalfCell := cellSize / game.Settings.PlayerBaseSpeed / 2
- game.Action(0, "right", "place_bomb")
- c.Test("player places bombs while walking", func(c Context) {
- game.Tick(3*ticksPerHalfCell + 1)
- assert(len(game.State.Bombs) == 3, "three bombs expected, %d bombs found", len(game.State.Bombs))
- })
- c.Test("player stops placing bombs", func(c Context) {
- game.Tick(2 * ticksPerHalfCell)
- game.Action(0, "right", "none")
- game.Tick(2 * ticksPerHalfCell)
- assert(len(game.State.Bombs) == 2, "two bombs expected, %d bombs found", len(game.State.Bombs))
- })
- })
- c.Test("Explosions", func(c Context) {
- obstacles := make([]obstacle, len(engine.ItemTypes))
- for i := range engine.ItemTypes {
- obstacles[i] = obstacle{string(engine.ItemTypes[i].Code), true}
- }
- wall := obstacle{"#", false}
- floor := obstacle{".", false}
- destructableWall := obstacle{"%", true}
- obstacles = append(obstacles, wall, floor, destructableWall)
- explosionSettings := cloneSettings(gameSettings)
- explosionSettings.PlayerBaseSpeed = 1000
- makeExplosionWithSettings := func(
- radius, blastAddition int, obs obstacle, dir engine.Vector2,
- ) (game *gameHelper, pos engine.Vector2) {
- explosionSettings.Items["flame"] = ItemDescription{
- FlameBonus: 1,
- MaxCount: 12,
- StartCount: radius + blastAddition,
- Weight: 18,
- }
- explosionMap := generateExplosionTestMap(2*radius+3, radius, obs, dir)
- game = newStartedGame(api, 42, explosionMap, &explosionSettings)
- pos = game.PlayerPos(0)
- placeBomb(game, 0, true)
- return
- }
- testExplosionState := func(game *gameHelper, radius, blastAddition int, blocksExp bool, dir, pos engine.Vector2) {
- assertEqual("explosion count", 1, len(game.State.Explosions))
- blastTime := game.Settings.BombDelay + 1
- length := radius + blastAddition
- if dir == engine.None && blocksExp && length > radius-1 {
- length = radius - 1
- }
- expectedState := ExplosionState{
- X: pos.X,
- Y: pos.Y,
- CreatedAt: blastTime,
- DestroyAt: blastTime + game.Settings.BlastWaveDuration,
- UpLength: length,
- DownLength: length,
- LeftLength: length,
- RightLength: length,
- }
- if dir != engine.None && blocksExp && length > radius-1 {
- if dir == engine.Up {
- expectedState.UpLength = radius - 1
- } else if dir == engine.Down {
- expectedState.DownLength = radius - 1
- } else if dir == engine.Left {
- expectedState.LeftLength = radius - 1
- } else if dir == engine.Right {
- expectedState.RightLength = radius - 1
- }
- }
- assertExplosionState(expectedState, game.State.Explosions[0])
- }
- c.Test("explosion with different obstacles (circular)", func(c Context) {
- for _, obstacle := range obstacles {
- c.Test(fmt.Sprintf("obstacle %s", obstacle.Code), func(c Context) {
- game, pos := makeExplosionWithSettings(2, 1, obstacle, engine.None)
- testExplosionState(game, 2, 1, obstacle.Code != ".", engine.None, pos)
- })
- }
- })
- c.Test("explosion with different blast radiuses (circular)", func(c Context) {
- for radius := 2; radius < 5; radius++ {
- for blastAddition := -1; blastAddition < 2; blastAddition++ {
- game, pos := makeExplosionWithSettings(radius, blastAddition, destructableWall, engine.None)
- testExplosionState(game, radius, blastAddition, true, engine.None, pos)
- }
- }
- })
- c.Test("explosion with asymmetrical obstacles", func(c Context) {
- forAllDirections(func(dir engine.Vector2) {
- game, pos := makeExplosionWithSettings(2, 1, destructableWall, dir)
- testExplosionState(game, 2, 1, true, dir, pos)
- })
- })
- c.Test("explosion ends in time", func(c Context) {
- game, _ := makeExplosionWithSettings(2, 1, destructableWall, engine.None)
- game.Tick(game.Settings.BlastWaveDuration - 1)
- assertEqual("explosion count", 1, len(game.State.Explosions))
- game.Tick(1)
- assertEqual("explosion count", 0, len(game.State.Explosions))
- })
- c.Test("explosion is limited by map borders", func(c Context) {
- explosionSettings.Items["flame"] = ItemDescription{FlameBonus: 1, MaxCount: 12, StartCount: 3, Weight: 18}
- game := newStartedGame(api, 42, []string{
- "......",
- "..0...",
- "....1.",
- }, &explosionSettings)
- pos := game.PlayerPos(0)
- placeBomb(game, 0, true)
- testExplosionState(game, 2, 0, true, engine.None, pos)
- })
- c.Test("explosion interaction with obstacles", func(c Context) {
- testSymbol := func(obs obstacle, beforeDecay, afterDecay rune) {
- game, pos := makeExplosionWithSettings(2, 1, obs, engine.Left)
- assertCellOnObstaclePos := func(msg string, expected rune) {
- assert(rune(game.State.Field[pos.Y/cellSize][1]) == expected,
- "%s: %c expected, %c found",
- msg,
- expected,
- rune(game.State.Field[pos.Y/cellSize][1]),
- )
- }
- assertCellOnObstaclePos("obstacle on explosion start", beforeDecay)
- game.Tick(game.Settings.CellDecayDuration - 1)
- assertCellOnObstaclePos("obstacle at the end of decay period", beforeDecay)
- game.Tick(1)
- assertCellOnObstaclePos("obstacle after decay period", afterDecay)
- }
- testSymbol(destructableWall, '$', '.')
- testSymbol(wall, '#', '#')
- testSymbol(floor, '.', '.')
- testSymbol(obstacle{"i", true}, '.', '.')
- })
- c.Test("explosion interaction with players", func(c Context) {
- explosionSettings.Items["flame"] = ItemDescription{FlameBonus: 1, MaxCount: 12, StartCount: 2, Weight: 18}
- game := newStartedGame(api, 42, []string{
- "....1.....",
- "..........",
- "2...0...3.",
- "..........",
- "....4.....",
- }, &explosionSettings)
- pos := game.PlayerPos(0)
- placeBomb(game, 0, true)
- c.Test("players limit flame length", func(c Context) {
- testExplosionState(game, 2, 0, true, engine.None, pos)
- })
- c.Test("explosion kills players", func(c Context) {
- for i, player := range game.PlayerStates() {
- if i != 0 {
- assert(player.Dead, "player %d wasn't killed by explosion", i)
- }
- }
- })
- })
- c.Test("explosion interaction with bombs", func(c Context) {
- ticksPerCell := cellSize / explosionSettings.PlayerBaseSpeed
- blastTime := explosionSettings.BombDelay + 1
- createGameWithExplosion := func(chainReactionDelay int) (game *gameHelper) {
- explosionSettings.Items["flame"] = ItemDescription{FlameBonus: 1, MaxCount: 12, StartCount: 2, Weight: 18}
- explosionSettings.BombChainReactionDelay = chainReactionDelay
- game = newStartedGame(api, 42, []string{
- "0...1...",
- "........",
- "........",
- }, &explosionSettings)
- placeBomb(game, 0, false)
- placeBomb(game, 1, false)
- game.Tick(game.Settings.BombDelay - 4*ticksPerCell)
- return
- }
- c.Test("bomb limits flame length", func(c Context) {
- game := createGameWithExplosion(3)
- assertEqual("explosion count", 1, len(game.State.Explosions))
- assertExplosionState(ExplosionState{
- CreatedAt: blastTime,
- DestroyAt: blastTime + game.Settings.BlastWaveDuration,
- DownLength: 2,
- RightLength: 1,
- }, game.State.Explosions[0])
- })
- c.Test("next bomb explodes sooner", func(c Context) {
- game := createGameWithExplosion(3)
- assertEqual("bomb count", 1, len(game.State.Bombs))
- assertEqual(
- "bomb destroy time",
- game.tick+game.Settings.BombChainReactionDelay-1,
- game.State.Bombs[0].DestroyAt,
- )
- })
- c.Test("next bombs explodes as soon as possible", func(c Context) {
- game := createGameWithExplosion(30)
- assertEqual("bomb count", 1, len(game.State.Bombs))
- assertEqual(
- "bomb destroy time",
- 2+2*ticksPerCell+game.Settings.BombDelay,
- game.State.Bombs[0].DestroyAt,
- )
- })
- })
- })
- c.Test("Non-random items drop", func(c Context) {
- itemSettings := cloneSettings(gameSettings)
- itemSettings.PlayerBaseSpeed = 1000
- ticksPerCell := cellSize / itemSettings.PlayerBaseSpeed
- itemMap := []string{
- "B.F.G.S.K.T.I.C.A.N.U.1.",
- "0.......................",
- "........................",
- }
- game := newStartedGame(api, 45, itemMap, &itemSettings)
- for i := 0; i < 11; i++ {
- x := game.PlayerPos(0).X / cellSize
- y := game.PlayerPos(0).Y/cellSize - 1
- placeBomb(game, 0, true)
- game.Tick(game.Settings.CellDecayDuration)
- expected := unicode.ToLower(rune(itemMap[y][x*2]))
- got := rune(game.State.Field[y][x])
- assert(expected == got, "bonus at pos (%d, %d): %c expected, %c found", x, y, expected, got)
- game.Action(0, "up", "none")
- game.Tick(ticksPerCell)
- }
- })
- c.Test("Random items drop", func(c Context) {
- itemSettings := cloneSettings(gameSettings)
- itemSettings.PlayerBaseSpeed = 1000
- ticksPerCell := cellSize / itemSettings.PlayerBaseSpeed
- testItemDrop := func(settings GameSettings, dropProbability, seed int) {
- settings.ItemDropProbability = dropProbability
- game := newStartedGame(api, seed, []string{
- "%%%%%%%%%%%%%%%%%%%%..",
- "0...................1.",
- "......................",
- }, &settings)
- mt := engine.NewMersenneTwister(uint32(seed))
- totalWeight := 0
- for _, item := range game.Settings.Items {
- totalWeight += item.Weight
- }
- for i := 0; i < 10; i++ {
- pos := game.PlayerPos(0)
- x := pos.X / cellSize
- y := pos.Y/cellSize - 1
- placeBomb(game, 0, true)
- game.Tick(game.Settings.CellDecayDuration)
- if mt.Next()%100 < uint32(game.Settings.ItemDropProbability) && totalWeight > 0 {
- drop := mt.Next() % uint32(totalWeight)
- sum := 0
- for _, item := range engine.ItemTypes {
- sum += game.Settings.Items[item.Name].Weight
- if uint32(sum) > drop {
- got := rune(game.State.Field[y][x])
- assert(got == item.Code, "bonus at pos (%d, %d): %c expected, %c found", x, y, item.Code, got)
- break
- }
- }
- } else {
- assert(rune(game.State.Field[y][x]) == '.', "unexpected bonus at pos (%d, %d)", x, y)
- }
- game.Action(0, "up", "none")
- game.Tick(ticksPerCell)
- }
- }
- c.Test("with mostly default settings", func(c Context) {
- testItemDrop(itemSettings, 55, 45)
- })
- c.Test("with different seed", func(c Context) {
- for _, seed := range []int{154, 1, 13} {
- testItemDrop(itemSettings, 55, seed)
- }
- })
- c.Test("with different drop probability", func(c Context) {
- for _, drop := range []int{100, 0, 20, 9} {
- testItemDrop(itemSettings, drop, 45)
- }
- })
- c.Test("with zero weights", func(c Context) {
- for key, item := range itemSettings.Items {
- item.Weight = 0
- itemSettings.Items[key] = item
- }
- testItemDrop(itemSettings, 55, 45)
- })
- })
- c.Test("Game ticks come from one and only game", func(c Context) {
- user := createUserAndLogin(api, "first", "first")
- user2 := createUserAndLogin(api, "second", "second")
- user.WSAuth()
- user2.WSAuth()
- gameId := createGame(user, "")
- user2.Ok(JoinGame{GameId: gameId, Password: ""})
- user.Ok(StartGame{})
- user2.Ok(StartGame{})
- time.Sleep(100 * time.Millisecond)
- user.Ok(LeaveGame{})
- user2.Ok(LeaveGame{})
- user2.ExpectEvent(&WSPlayerLeftGame{})
- gameId2 := createGame(user2, "")
- user.Ok(JoinGame{GameId: gameId2, Password: ""})
- user.Ok(StartGame{})
- user2.Ok(StartGame{})
- user2.ExpectEvent(&WSGameStarted{})
- var gameState WSGameState
- var ticks []string
- tick := 0
- ok := true
- for i := 0; i < 30; i++ {
- user2.ExpectEvent(&gameState)
- ticks = append(ticks, fmt.Sprintf("%d", gameState.Tick))
- ok = ok && tick == gameState.Tick
- tick++
- }
- assert(ok, "expected 0,1,2,...n+1, got %s", strings.Join(ticks, ","))
- })
- }