diff --git a/README.md b/README.md index ffd4d01..1fd15fd 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,8 @@ func cgolNextUnitGenerator( for j := -1; j < 2; j += 1 { if !(i == 0 && j == 0) { // Pay attention to "isCrossBorder", if the adjacent unit in the relative coordinate - // is on other side of the field, "isCrossBorder" will be true. - // So if you want to allow your cells to be able to go through border, ignore "isCrossBorder" here. + // is on other side of the map, "isCrossBorder" will be true. + // So if you want to allow your cells to be able to go beyond border, ignore "isCrossBorder" here. adjUnit, isCrossBorder := getAdjacentUnit(coord, &ggol.Coordinate{X: i, Y: j}) if adjUnit.Alive && !isCrossBorder { liveAdjacentCellsCount += 1 @@ -96,7 +96,7 @@ func cgolNextUnitGenerator( } func main() { - // Declare field size. + // Declare size of the map in the game. size := ggol.Size{Height: 3, Width: 3} // Initial status of all units. initialCgolUnit := CgolUnit{Alive: false} @@ -117,9 +117,9 @@ func main() { game.SetUnit(&ggol.Coordinate{X: 1, Y: 1}, &CgolUnit{Alive: true}) game.SetUnit(&ggol.Coordinate{X: 1, Y: 2}, &CgolUnit{Alive: true}) - // This will generate next field, the looking of next field is depending on "cgolNextUnitGenerator" + // This will generate next units, the looking of next generation of units is depending on "cgolNextUnitGenerator" // you just passed in "SetNextUnitGenerator" above. - game.GenerateNextField() + game.GenerateNextUnits() // Let's see if we generate the next status of the Blinker correctly. // If it's correct, all units below should have "Alive" attribute as true. diff --git a/develop_tools_test.go b/develop_tools_test.go index 8f442a7..6443614 100644 --- a/develop_tools_test.go +++ b/develop_tools_test.go @@ -34,8 +34,8 @@ func TestHasLiveCellTestUnitsMapsEqual(t *testing.T) { func testConvertTestUnitsMatricToHasLiveCellTestUnitsMapCaseOne(t *testing.T) { game, _ := NewGame(&Size{2, 2}, &unitForTest{hasLiveCell: true}) game.SetNextUnitGenerator(defauUnitForTestIterator) - generation := game.GetField() - liveUnitsMap := convertUnitForTestMatrixToUnitsHavingLiveCellForTest(generation) + allUnits := game.GetUnits() + liveUnitsMap := convertUnitForTestMatrixToUnitsHavingLiveCellForTest(allUnits) expectedMap := unitsHavingLiveCellForTest{{true, true}, {true, true}} diff --git a/example/conways_game_of_life.go b/example/conways_game_of_life.go index f136595..e99d940 100644 --- a/example/conways_game_of_life.go +++ b/example/conways_game_of_life.go @@ -52,8 +52,8 @@ func conwaysGameOfLifeNextUnitGenerator( } } -func initializeConwaysGameOfLifeField(g ggol.Game[conwaysGameOfLifeUnit]) { - size := g.GetFieldSize() +func initializeConwaysGameOfLifeUnits(g ggol.Game[conwaysGameOfLifeUnit]) { + size := g.GetSize() for i := 0; i < size.Height; i += 1 { for j := 0; j < size.Height; j += 1 { g.SetUnit(&ggol.Coordinate{X: i*5 + 0, Y: j*5 + 0}, &conwaysGameOfLifeUnit{HasLiveCell: true}) @@ -80,7 +80,7 @@ func executeGameOfLife() { size := ggol.Size{Width: 50, Height: 50} game, _ := ggol.NewGame(&size, &initialConwaysGameOfLifeUnit) game.SetNextUnitGenerator(conwaysGameOfLifeNextUnitGenerator) - initializeConwaysGameOfLifeField(game) + initializeConwaysGameOfLifeUnits(game) var conwaysGameOfLifePalette = []color.Color{ color.RGBA{0x00, 0x00, 0x00, 0xff}, @@ -103,7 +103,7 @@ func executeGameOfLife() { } images = append(images, newImage) delays = append(delays, duration) - game.GenerateNextField() + game.GenerateNextUnits() } outputGif("output/conways_game_of_life.gif", images, delays) diff --git a/example/game_of_black_and_white.go b/example/game_of_black_and_white.go index 7f5fdea..960d2e7 100644 --- a/example/game_of_black_and_white.go +++ b/example/game_of_black_and_white.go @@ -31,8 +31,8 @@ func gameOfBlackAndWhiteNextUnitGenerator( } } -func initializeGameOfBlackAndWhiteField(g ggol.Game[gameOfBlackAndWhiteUnit]) { - size := g.GetFieldSize() +func initializeGameOfBlackAndWhiteUnits(g ggol.Game[gameOfBlackAndWhiteUnit]) { + size := g.GetSize() for x := 0; x < size.Width; x++ { for y := 0; y < size.Height; y++ { c := ggol.Coordinate{X: x, Y: y} @@ -56,7 +56,7 @@ func executeGameOfBlackAndWhite() { size := ggol.Size{Width: 50, Height: 50} game, _ := ggol.NewGame(&size, &initialGameOfBlackAndWhiteUnit) game.SetNextUnitGenerator(gameOfBlackAndWhiteNextUnitGenerator) - initializeGameOfBlackAndWhiteField(game) + initializeGameOfBlackAndWhiteUnits(game) var gameOfBlackAndWhitePalette = []color.Color{ color.RGBA{0x00, 0x00, 0x00, 0xff}, @@ -79,7 +79,7 @@ func executeGameOfBlackAndWhite() { } images = append(images, newImage) delays = append(delays, duration) - game.GenerateNextField() + game.GenerateNextUnits() } outputGif("output/game_of_black_and_white.gif", images, delays) diff --git a/example/game_of_king.go b/example/game_of_king.go index b1bad2d..e944a75 100644 --- a/example/game_of_king.go +++ b/example/game_of_king.go @@ -47,8 +47,8 @@ func gameOfKingNextUnitGenerator( return &newUnit } -func initializeGameOfKingField(g ggol.Game[gameOfKingUnit]) { - size := g.GetFieldSize() +func initializeGameOfKingUnits(g ggol.Game[gameOfKingUnit]) { + size := g.GetSize() cellsCount := int((size.Width * size.Height) / 2) for i := 0; i < cellsCount; i += 1 { g.SetUnit(&ggol.Coordinate{X: rand.Intn(size.Width), Y: rand.Intn(size.Height)}, &gameOfKingUnit{Strength: 1, Direction: 0}) @@ -74,7 +74,7 @@ func executeGameOfKing() { size := ggol.Size{Width: 250, Height: 250} game, _ := ggol.NewGame(&size, &initialGameOfKingUnit) game.SetNextUnitGenerator(gameOfKingNextUnitGenerator) - initializeGameOfKingField(game) + initializeGameOfKingUnits(game) var gameOfKingPalette = []color.Color{ color.RGBA{0x00, 0x00, 0x00, 0xff}, @@ -104,7 +104,7 @@ func executeGameOfKing() { } images = append(images, newImage) delays = append(delays, duration) - game.GenerateNextField() + game.GenerateNextUnits() } outputGif("output/game_of_king.gif", images, delays) diff --git a/example/game_of_matrix.go b/example/game_of_matrix.go index fbb3adb..7ad0120 100644 --- a/example/game_of_matrix.go +++ b/example/game_of_matrix.go @@ -11,17 +11,17 @@ import ( type gameOfMatrixUnit struct { WordsLength int CountWords int - // One column can only have a word stream at a time, so we have this count - CountFieldHeight int + // One column (height of game size) can only have a word stream at a time, so we have this count + CountHeight int } var initialGameOfMatrixUnit gameOfMatrixUnit = gameOfMatrixUnit{ - WordsLength: 0, - CountWords: 0, - CountFieldHeight: 50, + WordsLength: 0, + CountWords: 0, + CountHeight: 50, } -// A field can only have 20 word of streams in total +// A game can only have 20 word of streams in total var totalWordStreamsCount = 0 func gameOfMatrixNextUnitGenerator( @@ -31,11 +31,11 @@ func gameOfMatrixNextUnitGenerator( ) (nextUnit *gameOfMatrixUnit) { newUnit := *unit if coord.Y == 0 { - if unit.CountWords == 0 && unit.CountFieldHeight >= 50 && totalWordStreamsCount < 50 { + if unit.CountWords == 0 && unit.CountHeight >= 50 && totalWordStreamsCount < 50 { if rand.Intn(50) == 1 { newUnit.WordsLength = 30 + rand.Intn(40) newUnit.CountWords = 1 - newUnit.CountFieldHeight = 0 + newUnit.CountHeight = 0 totalWordStreamsCount += 1 } } else if unit.CountWords < unit.WordsLength { @@ -45,7 +45,7 @@ func gameOfMatrixNextUnitGenerator( newUnit.CountWords = 0 totalWordStreamsCount -= 1 } - newUnit.CountFieldHeight += 1 + newUnit.CountHeight += 1 return &newUnit } else { prevUnit, _ := getAdjacentUnit(coord, &ggol.Coordinate{X: 0, Y: -1}) @@ -54,7 +54,7 @@ func gameOfMatrixNextUnitGenerator( } } -func initializeGameOfMatrixField(g ggol.Game[gameOfMatrixUnit]) { +func initializeGameOfMatrixUnits(g ggol.Game[gameOfMatrixUnit]) { // Do nothing } @@ -80,11 +80,11 @@ func executeGameOfMatrix() { size := ggol.Size{Width: 50, Height: 50} game, _ := ggol.NewGame(&size, &initialGameOfMatrixUnit) game.SetNextUnitGenerator(gameOfMatrixNextUnitGenerator) - initializeGameOfMatrixField(game) + initializeGameOfMatrixUnits(game) previousSteps := 100 for i := 0; i < previousSteps; i += 1 { - game.GenerateNextField() + game.GenerateNextUnits() } var gameOfMatrixPalette = []color.Color{ @@ -116,7 +116,7 @@ func executeGameOfMatrix() { } images = append(images, newImage) delays = append(delays, duration) - game.GenerateNextField() + game.GenerateNextUnits() } outputGif("output/game_of_matrix.gif", images, delays) diff --git a/example/game_of_wave.go b/example/game_of_wave.go index cfbc649..707d2cf 100644 --- a/example/game_of_wave.go +++ b/example/game_of_wave.go @@ -32,9 +32,9 @@ func gameOfWaveNextUnitGenerator( } } -func initializeGameOfWaveField(g ggol.Game[gameOfWaveUnit]) { +func initializeGameOfWaveUnits(g ggol.Game[gameOfWaveUnit]) { var margin int = 0 - size := g.GetFieldSize() + size := g.GetSize() for x := 0; x < size.Width; x++ { for y := 0; y < size.Height; y++ { if y%10 == 0 { @@ -65,7 +65,7 @@ func executeGameOfWave() { size := ggol.Size{Width: 50, Height: 50} game, _ := ggol.NewGame(&size, &initialGameOfWaveUnit) game.SetNextUnitGenerator(gameOfWaveNextUnitGenerator) - initializeGameOfWaveField(game) + initializeGameOfWaveUnits(game) var gameOfWavePalette = []color.Color{ color.RGBA{0x00, 0x00, 0x00, 0xff}, @@ -88,7 +88,7 @@ func executeGameOfWave() { } images = append(images, newImage) delays = append(delays, duration) - game.GenerateNextField() + game.GenerateNextUnits() } outputGif("output/game_of_wave.gif", images, delays) diff --git a/ggol.go b/ggol.go index 4d63947..5a565e0 100644 --- a/ggol.go +++ b/ggol.go @@ -6,31 +6,35 @@ import ( // "T" in the Game interface represents the type of unit, it's defined by you. type Game[T any] interface { - // Reset entire field with initial unit. - ResetField() - // Generate next field, the way you generate next field will be depending on the NextUnitGenerator function + // ResetUnits all units with initial unit. + ResetUnits() + // Generate next units, the way you generate next units will be depending on the NextUnitGenerator function // you passed in SetNextUnitGenerator. - GenerateNextField() + GenerateNextUnits() // Set NextUnitGenerator, which tells the game how you want to generate next unit of the given unit. SetNextUnitGenerator(nextUnitGenerator NextUnitGenerator[T]) // Set the status of the unit at the given coordinate. SetUnit(coord *Coordinate, unit *T) (err error) - // Get the size of the field. - GetFieldSize() (size *Size) + // Get the size of the game. + GetSize() (size *Size) // Get the status of the unit at the given coordinate. GetUnit(coord *Coordinate) (unit *T, err error) - // Get the field, it's a matrix that contains all units in the game. - GetField() (field *Units[T]) - // Iterate through entire field - IterateField(callback UnitsIteratorCallback[T]) + // Get all units in the area. + GetUnitsInArea(area *Area) (units *Units[T], err error) + // Get all units in the game. + GetUnits() (units *Units[T]) + // Iterate through units in the given area. + IterateUnitsInArea(area *Area, callback UnitsIteratorCallback[T]) (err error) + // Iterate through all units in the game + IterateUnits(callback UnitsIteratorCallback[T]) } type gameInfo[T any] struct { - size *Size - initialUnit *T - field *Units[T] - unitIterator NextUnitGenerator[T] - locker sync.RWMutex + size *Size + initialUnit *T + units *Units[T] + nextUnitGenerator NextUnitGenerator[T] + locker sync.RWMutex } func defaultNextUnitGenerator[T any](coord *Coordinate, unit *T, getAdjacentUnit AdjacentUnitGetter[T]) (nextUnit *T) { @@ -43,13 +47,13 @@ func NewGame[T any]( initialUnit *T, ) (Game[T], error) { if size.Width < 0 || size.Height < 0 { - return nil, &ErrSizeIsNotValid{size} + return nil, &ErrSizeIsInvalid{size} } newG := gameInfo[T]{ size, initialUnit, - createField(size, initialUnit), + createInitialUnits(size, initialUnit), defaultNextUnitGenerator[T], sync.RWMutex{}, } @@ -57,22 +61,26 @@ func NewGame[T any]( return &newG, nil } -func createField[T any](size *Size, initialUnit *T) *Units[T] { - field := make(Units[T], size.Width) +func createInitialUnits[T any](size *Size, initialUnit *T) *Units[T] { + units := make(Units[T], size.Width) for x := 0; x < size.Width; x++ { - newRowOfField := make([]*T, size.Height) - field[x] = &newRowOfField + newRowOfUnits := make([]*T, size.Height) + units[x] = &newRowOfUnits for y := 0; y < size.Height; y++ { - (*field[x])[y] = initialUnit + (*units[x])[y] = initialUnit } } - return &field + return &units } -func (g *gameInfo[T]) isCoordinateOutsideField(c *Coordinate) bool { +func (g *gameInfo[T]) isCoordinateInvalid(c *Coordinate) bool { return c.X < 0 || c.X >= g.size.Width || c.Y < 0 || c.Y >= g.size.Height } +func (g *gameInfo[T]) isAreaInvalid(area *Area) bool { + return area.From.X > area.To.X || area.From.Y > area.To.Y +} + func (g *gameInfo[T]) getAdjacentUnit( originCoord *Coordinate, relativeCoord *Coordinate, @@ -81,7 +89,7 @@ func (g *gameInfo[T]) getAdjacentUnit( targetY := originCoord.Y + relativeCoord.Y var isCrossBorder bool = false - if (g.isCoordinateOutsideField(&Coordinate{X: targetX, Y: targetY})) { + if (g.isCoordinateInvalid(&Coordinate{X: targetX, Y: targetY})) { isCrossBorder = true for targetX < 0 { targetX += g.size.Width @@ -93,42 +101,42 @@ func (g *gameInfo[T]) getAdjacentUnit( targetY = targetY % g.size.Height } - return (*(*g.field)[targetX])[targetY], isCrossBorder + return (*(*g.units)[targetX])[targetY], isCrossBorder } -// ResetField game. -func (g *gameInfo[T]) ResetField() { +// ResetUnits game. +func (g *gameInfo[T]) ResetUnits() { g.locker.Lock() defer g.locker.Unlock() - g.field = createField(g.size, g.initialUnit) + g.units = createInitialUnits(g.size, g.initialUnit) } -// Generate next field. -func (g *gameInfo[T]) GenerateNextField() { +// Generate next units. +func (g *gameInfo[T]) GenerateNextUnits() { g.locker.Lock() defer g.locker.Unlock() - nextField := make([][]*T, g.size.Width) + nextUnits := make([][]*T, g.size.Width) for x := 0; x < g.size.Width; x++ { - nextField[x] = make([]*T, g.size.Height) + nextUnits[x] = make([]*T, g.size.Height) for y := 0; y < g.size.Height; y++ { coord := Coordinate{X: x, Y: y} - nextUnit := g.unitIterator(&coord, (*(*g.field)[x])[y], g.getAdjacentUnit) - nextField[x][y] = nextUnit + nextUnit := g.nextUnitGenerator(&coord, (*(*g.units)[x])[y], g.getAdjacentUnit) + nextUnits[x][y] = nextUnit } } for x := 0; x < g.size.Width; x++ { for y := 0; y < g.size.Height; y++ { - (*(*g.field)[x])[y] = nextField[x][y] + (*(*g.units)[x])[y] = nextUnits[x][y] } } } func (g *gameInfo[T]) SetNextUnitGenerator(iterator NextUnitGenerator[T]) { - g.unitIterator = iterator + g.nextUnitGenerator = iterator } // Update the unit at the given coordinate. @@ -136,16 +144,16 @@ func (g *gameInfo[T]) SetUnit(c *Coordinate, unit *T) error { g.locker.Lock() defer g.locker.Unlock() - if g.isCoordinateOutsideField(c) { - return &ErrCoordinateIsOutsideField{c} + if g.isCoordinateInvalid(c) { + return &ErrCoordinateIsInvalid{c} } - (*(*g.field)[c.X])[c.Y] = unit + (*(*g.units)[c.X])[c.Y] = unit return nil } -// Get the field size. -func (g *gameInfo[T]) GetFieldSize() *Size { +// Get the game size. +func (g *gameInfo[T]) GetSize() *Size { g.locker.RLock() defer g.locker.RUnlock() @@ -157,26 +165,76 @@ func (g *gameInfo[T]) GetUnit(c *Coordinate) (*T, error) { g.locker.RLock() defer g.locker.RUnlock() - if g.isCoordinateOutsideField(c) { - return nil, &ErrCoordinateIsOutsideField{c} + if g.isCoordinateInvalid(c) { + return nil, &ErrCoordinateIsInvalid{c} } - return (*(*g.field)[c.X])[c.Y], nil + return (*(*g.units)[c.X])[c.Y], nil } -// Get the entire genetation, which is a matrix that contains all units. -func (g *gameInfo[T]) GetField() *Units[T] { +// Get all units in the game +func (g *gameInfo[T]) GetUnits() *Units[T] { g.locker.RLock() defer g.locker.RUnlock() + return g.units +} + +// Get all units in the given area. +func (g *gameInfo[T]) GetUnitsInArea(area *Area) (*Units[T], error) { + g.locker.RLock() + defer g.locker.RUnlock() + + if g.isCoordinateInvalid(&area.From) { + return nil, &ErrCoordinateIsInvalid{&area.From} + } + + if g.isCoordinateInvalid(&area.To) { + return nil, &ErrCoordinateIsInvalid{&area.To} + } + + if g.isAreaInvalid(area) { + return nil, &ErrAreaIsInvalid{area} + } + + unitsInArea := make(Units[T], area.To.X-area.From.X+1) + for x := area.From.X; x <= area.To.X; x++ { + newRow := make([]*T, area.To.Y-area.From.Y+1) + unitsInArea[x] = &newRow + for y := area.From.Y; y <= area.To.Y; y++ { + (*unitsInArea[x])[y] = (*(*g.units)[x])[y] + } + } - return g.field + return &unitsInArea, nil } -// We will iterate field and call the callback func that you passes in. -func (g *gameInfo[T]) IterateField(callback UnitsIteratorCallback[T]) { +// We will iterate all units in the game and call the callbacks with coordiante and unit. +func (g *gameInfo[T]) IterateUnits(callback UnitsIteratorCallback[T]) { for x := 0; x < g.size.Width; x++ { for y := 0; y < g.size.Height; y++ { - callback(&Coordinate{X: x, Y: y}, (*(*g.field)[x])[y]) + callback(&Coordinate{X: x, Y: y}, (*(*g.units)[x])[y]) } } } + +// We will iterate all units in the given area and call the callbacks with coordiante and unit. +func (g *gameInfo[T]) IterateUnitsInArea(area *Area, callback UnitsIteratorCallback[T]) error { + if g.isCoordinateInvalid(&area.From) { + return &ErrCoordinateIsInvalid{&area.From} + } + + if g.isCoordinateInvalid(&area.To) { + return &ErrCoordinateIsInvalid{&area.To} + } + + if g.isAreaInvalid(area) { + return &ErrAreaIsInvalid{area} + } + + for x := area.From.X; x <= area.To.X; x++ { + for y := area.From.Y; y <= area.To.Y; y++ { + callback(&Coordinate{X: x, Y: y}, (*(*g.units)[x])[y]) + } + } + return nil +} diff --git a/ggol_structs.go b/ggol_structs.go index d6e659e..c874e68 100644 --- a/ggol_structs.go +++ b/ggol_structs.go @@ -3,30 +3,45 @@ package ggol import "fmt" // This error will be thrown when you try to create a new game with invalid size. -type ErrSizeIsNotValid struct { +type ErrSizeIsInvalid struct { Size *Size } -func (e *ErrSizeIsNotValid) Error() string { +func (e *ErrSizeIsInvalid) Error() string { return fmt.Sprintf("The size (%v x %v) is not valid.", e.Size.Width, e.Size.Height) } // This error will be thrown when you're trying to set or get an unit with invalid coordinate. -type ErrCoordinateIsOutsideField struct { +type ErrCoordinateIsInvalid struct { Coordinate *Coordinate } -func (e *ErrCoordinateIsOutsideField) Error() string { - return fmt.Sprintf("Coordinate (%v, %v) is outside game border.", e.Coordinate.X, e.Coordinate.Y) +func (e *ErrCoordinateIsInvalid) Error() string { + return fmt.Sprintf("Coordinate (%v, %v) is outside the border.", e.Coordinate.X, e.Coordinate.Y) } -// Coordniate tells you the position of an unit in the field. +// This error will be thrown when the X or Y of "from coordinate" is less than X or Y of "to coordinate" in the area. +type ErrAreaIsInvalid struct { + Area *Area +} + +func (e *ErrAreaIsInvalid) Error() string { + return fmt.Sprintf("Area with from coordinate (%v, %v) and end coordiante (%v, %v) is not valid.", e.Area.From.X, e.Area.From.Y, e.Area.To.X, e.Area.To.Y) +} + +// Coordniate tells you the position of an unit in the game. type Coordinate struct { X int Y int } -// The size of the field of the game. +// Area indicates an area within two coordinates. +type Area struct { + From Coordinate + To Coordinate +} + +// The size of the game. type Size struct { Width int Height int @@ -35,11 +50,11 @@ type Size struct { type Units[T any] []*[]*T // This function will be passed into NextUnitGenerator, this is how you can adajcent units in NextUnitGenerator. -// Also, 2nd argument "isCrossBorder" tells you if the adjacent unit is on ohter side of the field. +// Also, 2nd argument "isCrossBorder" tells you if the adjacent unit is on ohter side of the map. type AdjacentUnitGetter[T any] func(originCoord *Coordinate, relativeCoord *Coordinate) (unit *T, isCrossBorder bool) // NextUnitGenerator tells the game how you're gonna generate next status of the given unit. type NextUnitGenerator[T any] func(coord *Coordinate, unit *T, getAdjacentUnit AdjacentUnitGetter[T]) (nextUnit *T) -// UnitsIteratorCallback will be called when iterating through field. +// UnitsIteratorCallback will be called when iterating through units. type UnitsIteratorCallback[T any] func(coord *Coordinate, unit *T) diff --git a/ggol_test.go b/ggol_test.go index 5249b58..c028a3b 100644 --- a/ggol_test.go +++ b/ggol_test.go @@ -11,7 +11,8 @@ func shouldInitializeGameWithCorrectSize(t *testing.T) { size := Size{Width: width, Height: height} g, _ := NewGame(&size, &initialUnitForTest) g.SetNextUnitGenerator(defauUnitForTestIterator) - unitLiveMap := *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetField()) + + unitLiveMap := *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetUnits()) if len(unitLiveMap) == width && len(unitLiveMap[0]) == height { t.Log("Passed") @@ -47,7 +48,7 @@ func shouldThrowErrorWhenCoordinateExceedsBoarder(t *testing.T) { err := g.SetUnit(&c, &unitForTest{hasLiveCell: true}) if err == nil { - t.Fatalf("Should get error when coordinate is outside the field.") + t.Fatalf("Should get error when coordinate is outside the game map.") } t.Log("Passed") } @@ -87,9 +88,9 @@ func testBlockPattern(t *testing.T) { g.SetUnit(&Coordinate{X: 0, Y: 1}, &unitForTest{hasLiveCell: true}) g.SetUnit(&Coordinate{X: 1, Y: 0}, &unitForTest{hasLiveCell: true}) g.SetUnit(&Coordinate{X: 1, Y: 1}, &unitForTest{hasLiveCell: true}) - g.GenerateNextField() + g.GenerateNextUnits() - nexthasLiveCellUnitsMap := *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetField()) + nexthasLiveCellUnitsMap := *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetUnits()) expectedNexthasLiveCellUnitsMap := unitsHavingLiveCellForTest{ {true, true, false}, {true, true, false}, @@ -128,14 +129,14 @@ func testBlinkerPattern(t *testing.T) { {false, false, false}, } - g.GenerateNextField() - unitLiveMap = *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetField()) + g.GenerateNextUnits() + unitLiveMap = *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetUnits()) if !areTwoUnitsHavingLiveCellForTestEqual(unitLiveMap, expectedNexthasLiveCellUnitsMapOne) { t.Fatalf("Should generate next unitLiveMap of a blinker, but got %v.", unitLiveMap) } - g.GenerateNextField() - unitLiveMap = *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetField()) + g.GenerateNextUnits() + unitLiveMap = *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetUnits()) if !areTwoUnitsHavingLiveCellForTestEqual(unitLiveMap, expectedNexthasLiveCellUnitsMapTwo) { t.Fatalf("Should generate 2nd next unitLiveMap of a blinker, but got %v.", unitLiveMap) } @@ -186,26 +187,26 @@ func testGliderPattern(t *testing.T) { {false, false, false, true, false}, } - g.GenerateNextField() - unitLiveMap = *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetField()) + g.GenerateNextUnits() + unitLiveMap = *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetUnits()) if !areTwoUnitsHavingLiveCellForTestEqual(unitLiveMap, expectedhasLiveCellUnitsMapOne) { t.Fatalf("Should generate next unitLiveMap of a glider, but got %v.", unitLiveMap) } - g.GenerateNextField() - unitLiveMap = *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetField()) + g.GenerateNextUnits() + unitLiveMap = *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetUnits()) if !areTwoUnitsHavingLiveCellForTestEqual(unitLiveMap, expectedhasLiveCellUnitsMapTwo) { t.Fatalf("Should generate 2nd next unitLiveMap of a glider, but got %v.", unitLiveMap) } - g.GenerateNextField() - unitLiveMap = *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetField()) + g.GenerateNextUnits() + unitLiveMap = *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetUnits()) if !areTwoUnitsHavingLiveCellForTestEqual(unitLiveMap, expectedhasLiveCellUnitsMapThree) { t.Fatalf("Should generate 3rd next next unitLiveMap of a glider, but got %v.", unitLiveMap) } - g.GenerateNextField() - unitLiveMap = *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetField()) + g.GenerateNextUnits() + unitLiveMap = *convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetUnits()) if !areTwoUnitsHavingLiveCellForTestEqual(unitLiveMap, expectedhasLiveCellUnitsMapFour) { t.Fatalf("Should generate 4th next next unitLiveMap of a glider, but got %v.", unitLiveMap) } @@ -236,10 +237,10 @@ func testGliderPatternWithConcurrency(t *testing.T) { for i := 0; i < step; i++ { // Let the glider fly to digonal unit in four steps. go func() { - g.GenerateNextField() - g.GenerateNextField() - g.GenerateNextField() - g.GenerateNextField() + g.GenerateNextUnits() + g.GenerateNextUnits() + g.GenerateNextUnits() + g.GenerateNextUnits() wg.Done() }() } @@ -258,29 +259,29 @@ func testGliderPatternWithConcurrency(t *testing.T) { t.Log("Passed") } -func TestGenerateNextField(t *testing.T) { +func TestGenerateNextUnits(t *testing.T) { testBlockPattern(t) testBlinkerPattern(t) testGliderPattern(t) testGliderPatternWithConcurrency(t) } -func testGetFieldSizeCaseOne(t *testing.T) { +func testGetSizeCaseOne(t *testing.T) { width := 3 height := 6 size := Size{Width: width, Height: height} g, _ := NewGame(&size, &initialUnitForTest) g.SetNextUnitGenerator(defauUnitForTestIterator) - if g.GetFieldSize().Width == 3 && g.GetFieldSize().Height == 6 { + if g.GetSize().Width == 3 && g.GetSize().Height == 6 { t.Log("Passed") } else { t.Fatalf("Size is not correct.") } } -func TestGetFieldSize(t *testing.T) { - testGetFieldSizeCaseOne(t) +func TestGetSize(t *testing.T) { + testGetSizeCaseOne(t) } func testGetUnitCaseOne(t *testing.T) { @@ -321,7 +322,7 @@ func TestGetUnit(t *testing.T) { testGetUnitCaseTwo(t) } -func testResetFieldCaseOne(t *testing.T) { +func testResetUnitsCaseOne(t *testing.T) { width := 3 height := 3 size := Size{Width: width, Height: height} @@ -333,8 +334,9 @@ func testResetFieldCaseOne(t *testing.T) { g.SetUnit(&Coordinate{X: 1, Y: 1}, &unitForTest{hasLiveCell: true}) g.SetUnit(&Coordinate{X: 1, Y: 2}, &unitForTest{hasLiveCell: true}) - g.ResetField() - unitLiveMap := convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetField()) + g.ResetUnits() + + unitLiveMap := convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetUnits()) expectedBinaryBoard := unitsHavingLiveCellForTest{ {false, false, false}, @@ -349,8 +351,8 @@ func testResetFieldCaseOne(t *testing.T) { } } -func TestResetField(t *testing.T) { - testResetFieldCaseOne(t) +func TestResetUnits(t *testing.T) { + testResetUnitsCaseOne(t) } func testSetNextUnitGeneratorCaseOne(t *testing.T) { @@ -371,8 +373,9 @@ func testSetNextUnitGeneratorCaseOne(t *testing.T) { } g, _ := NewGame(&size, &initialUnitForTest) g.SetNextUnitGenerator(customNextUnitGenerator) - g.GenerateNextField() - unitLiveMap := convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetField()) + g.GenerateNextUnits() + + unitLiveMap := convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetUnits()) expectedBinaryBoard := unitsHavingLiveCellForTest{ {true, true, true}, @@ -391,29 +394,92 @@ func TestSetNextUnitGenerator(t *testing.T) { testSetNextUnitGeneratorCaseOne(t) } -func testGetFieldCaseOne(t *testing.T) { +func testGetUnitsCaseOne(t *testing.T) { width := 2 height := 2 size := Size{Width: width, Height: height} g, _ := NewGame(&size, &initialUnitForTest) g.SetNextUnitGenerator(defauUnitForTestIterator) - generation := g.GetField() - aliveUnitsMap := convertUnitForTestMatrixToUnitsHavingLiveCellForTest(generation) + + aliveUnitsMap := convertUnitForTestMatrixToUnitsHavingLiveCellForTest(g.GetUnits()) expectedUnitsMap := [][]bool{{false, false}, {false, false}} if areTwoUnitsHavingLiveCellForTestEqual(*aliveUnitsMap, expectedUnitsMap) { t.Log("Passed") } else { - t.Fatalf("Did not get correct generation.") + t.Fatalf("Did not get all units correctly.") + } +} + +func TestGetUnits(t *testing.T) { + testGetUnitsCaseOne(t) +} + +func testGetUnitsInAreaCaseOne(t *testing.T) { + width := 3 + height := 3 + size := Size{Width: width, Height: height} + g, _ := NewGame(&size, &initialUnitForTest) + g.SetNextUnitGenerator(defauUnitForTestIterator) + area := Area{ + From: Coordinate{X: 0, Y: 0}, + To: Coordinate{X: 1, Y: 1}, + } + + unitsInArea, _ := g.GetUnitsInArea(&area) + aliveUnitsMap := convertUnitForTestMatrixToUnitsHavingLiveCellForTest(unitsInArea) + + expectedUnitsMap := [][]bool{{false, false}, {false, false}} + + if areTwoUnitsHavingLiveCellForTestEqual(*aliveUnitsMap, expectedUnitsMap) { + t.Log("Passed") + } else { + t.Fatalf("Did not get all units in the given area correctly.") + } +} + +func TestGetUnitsInArea(t *testing.T) { + testGetUnitsInAreaCaseOne(t) +} + +func testIterateUnitsCaseOne(t *testing.T) { + width := 3 + height := 3 + size := Size{Width: width, Height: height} + g, _ := NewGame(&size, &initialUnitForTest) + g.SetUnit(&Coordinate{X: 1, Y: 0}, &unitForTest{hasLiveCell: true}) + g.SetUnit(&Coordinate{X: 1, Y: 1}, &unitForTest{hasLiveCell: true}) + g.SetUnit(&Coordinate{X: 1, Y: 2}, &unitForTest{hasLiveCell: true}) + sumsOfXCoord := 0 + sumsOfYCoord := 0 + liveCellsCount := 0 + + g.IterateUnits(func(c *Coordinate, unit *unitForTest) { + sumsOfXCoord += c.X + sumsOfYCoord += c.Y + if unit.hasLiveCell { + liveCellsCount += 1 + } + }) + + if sumsOfXCoord == 9 && sumsOfYCoord == 9 && liveCellsCount == 3 { + t.Log("Passed") + } else { + t.Fatalf( + "Did not iterate through units correctly, sums of X: %v, sums of Y: %v, count of live cells: %v.", + sumsOfXCoord, + sumsOfYCoord, + liveCellsCount, + ) } } -func TestGetField(t *testing.T) { - testGetFieldCaseOne(t) +func TestIterateUnits(t *testing.T) { + testIterateUnitsCaseOne(t) } -func testIterateFieldCaseOne(t *testing.T) { +func testIterateUnitsInAreaCaseOne(t *testing.T) { width := 3 height := 3 size := Size{Width: width, Height: height} @@ -423,23 +489,32 @@ func testIterateFieldCaseOne(t *testing.T) { g.SetUnit(&Coordinate{X: 1, Y: 2}, &unitForTest{hasLiveCell: true}) sumsOfXCoord := 0 sumsOfYCoord := 0 - aliveCellCount := 0 + liveCellsCount := 0 + area := Area{ + From: Coordinate{X: 0, Y: 0}, + To: Coordinate{X: 1, Y: 1}, + } - g.IterateField(func(c *Coordinate, unit *unitForTest) { + g.IterateUnitsInArea(&area, func(c *Coordinate, unit *unitForTest) { sumsOfXCoord += c.X sumsOfYCoord += c.Y if unit.hasLiveCell { - aliveCellCount += 1 + liveCellsCount += 1 } }) - if sumsOfXCoord == 9 && sumsOfYCoord == 9 && aliveCellCount == 3 { + if sumsOfXCoord == 2 && sumsOfYCoord == 2 && liveCellsCount == 2 { t.Log("Passed") } else { - t.Fatalf("Did not iterate through field correctly.") + t.Fatalf( + "Did not iterate through units in the given area correctly, sums of X: %v, sums of Y: %v, count of live cells: %v.", + sumsOfXCoord, + sumsOfYCoord, + liveCellsCount, + ) } } -func TestIterateField(t *testing.T) { - testIterateFieldCaseOne(t) +func TestIterateUnitsInArea(t *testing.T) { + testIterateUnitsInAreaCaseOne(t) }