diff --git a/basic_kotlin_project/src/main/kotlin/Main.kt b/basic_kotlin_project/src/main/kotlin/Main.kt index f2a59b6..d8bc15f 100644 --- a/basic_kotlin_project/src/main/kotlin/Main.kt +++ b/basic_kotlin_project/src/main/kotlin/Main.kt @@ -1,7 +1,24 @@ +import model.Animal +import model.BouncyMap +import model.MoveDirection +import model.Vector2d + fun main(args: Array) { println("Hello World!") // Try adding program arguments via Run/Debug configuration. // Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html. - println("Program arguments: ${args.joinToString()}") + val map = BouncyMap(5,5) + val animal1 = Animal(position = Vector2d(3, 3)) + val animal2 = Animal(position = Vector2d(3, 3)) + map.place(animal1) + map.place(animal2) + println(map.objectAt(Vector2d(3, 3))) + map.move(animal1,MoveDirection.FORWARD) + println(map.objectAt(Vector2d(3, 3))) + println(map.objectAt(Vector2d(3, 4))) + val map2 = ArrayList(map.getElements) + println(map2[0].getPosition()) + println(map2[1].getPosition()) +// println("Program arguments: ${args.joinToString()}") } \ No newline at end of file diff --git a/basic_kotlin_project/src/main/kotlin/model/Animal.kt b/basic_kotlin_project/src/main/kotlin/model/Animal.kt new file mode 100644 index 0000000..7edf341 --- /dev/null +++ b/basic_kotlin_project/src/main/kotlin/model/Animal.kt @@ -0,0 +1,21 @@ +package model + +class Animal(private var orientation: MapDirection = MapDirection.NORTH, private var position: Vector2d = Vector2d(2,2)) { + + override fun toString() = orientation.toString() + + fun isAt(position: Vector2d) = this.position == position + + fun getPosition() = this.position + + fun setPosition(position: Vector2d) { + this.position = position + } + + fun move(direction: MoveDirection) = when(direction) { + MoveDirection.RIGHT -> this.orientation = orientation.next() + MoveDirection.LEFT -> this.orientation = orientation.previous() + MoveDirection.FORWARD -> this.position += this.orientation.toUnitVector() + MoveDirection.BACKWARD -> this.position -= this.orientation.toUnitVector() + } +} \ No newline at end of file diff --git a/basic_kotlin_project/src/main/kotlin/model/BouncyMap.kt b/basic_kotlin_project/src/main/kotlin/model/BouncyMap.kt new file mode 100644 index 0000000..8d5979d --- /dev/null +++ b/basic_kotlin_project/src/main/kotlin/model/BouncyMap.kt @@ -0,0 +1,52 @@ +package model + +class BouncyMap(private var width: Int, private var height: Int) : IWorldMap { + private val map = HashMap() + + override val id = this.hashCode() + + override val getElements: ArrayList get() = ArrayList(map.values) + + override fun isOccupied(position: Vector2d) = (objectAt(position) != null) + + fun canMoveTo(position: Vector2d) = (position follows Vector2d(0,0) && position precedes Vector2d(width,height)) + + override fun objectAt(position: Vector2d) = map[position] + + override fun move(animal: Animal, direction: MoveDirection) { + val oldPosition = animal.getPosition() + val oldOrientation = animal.toString() + animal.move(direction) + if(canMoveTo(animal.getPosition())) { + if(animal.getPosition() != oldPosition) { + map.remove(oldPosition) + map[animal.getPosition()] = animal + } else if(oldOrientation != animal.toString()) { + map[animal.getPosition()] = animal + } + } + } + + override fun place(animal: Animal) { + val position = animal.getPosition() + if(map.containsValue(animal)) { + val oldPosition = map.filterValues { it == animal }.keys.first() + map.remove(oldPosition) + map[position] = animal + } else if(canMoveTo(position)) { + if(objectAt(position) == null) { + map[position] = animal + } else if(map.values.size == width * height){ + val randomPosition = map.randomPosition() + map.remove(randomPosition) + map[position] = animal + } else { + val freePosition = map.randomFreePosition(Vector2d(width,height)) + if(freePosition != null) { + map[freePosition] = animal + animal.setPosition(freePosition) + } + } + } + } +} \ No newline at end of file diff --git a/basic_kotlin_project/src/main/kotlin/model/IWorldMap.kt b/basic_kotlin_project/src/main/kotlin/model/IWorldMap.kt new file mode 100644 index 0000000..469a7db --- /dev/null +++ b/basic_kotlin_project/src/main/kotlin/model/IWorldMap.kt @@ -0,0 +1,43 @@ +package model + +/** + * The interface responsible for interacting with the map of the world. + * Assumes that Vector2d and MoveDirection classes are defined. + * + * @author apohllo, idzik + */ +interface IWorldMap { + /** + * Place a animal on the map. + * + * @param animal The animal to place on the map. + * @return True if the animal was placed. The animal cannot be placed if the move is not valid. + */ + fun place(animal: Animal) + + /** + * Moves an animal (if it is present on the map) according to specified direction. + * If the move is not possible, this method has no effect. + */ + fun move(animal: Animal, direction: MoveDirection) + + /** + * Return true if given position on the map is occupied. Should not be + * confused with canMove since there might be empty positions where the animal + * cannot move. + * + * @param position Position to check. + * @return True if the position is occupied. + */ + fun isOccupied(position: Vector2d): Boolean + + /** + * Return an animal at a given position. + * + * @param position The position of the animal. + * @return animal or null if the position is not occupied. + */ + fun objectAt(position: Vector2d): Animal? + val getElements: ArrayList + val id: Int +} \ No newline at end of file diff --git a/basic_kotlin_project/src/main/kotlin/model/MapDirection.kt b/basic_kotlin_project/src/main/kotlin/model/MapDirection.kt new file mode 100644 index 0000000..0beabf5 --- /dev/null +++ b/basic_kotlin_project/src/main/kotlin/model/MapDirection.kt @@ -0,0 +1,26 @@ +package model; + +enum class MapDirection { + NORTH, + SOUTH, + WEST, + EAST; + override fun toString() = when(this) { + NORTH -> ("N") + SOUTH -> ("S") + WEST -> ("W") + EAST -> ("E") + } + fun next() = when(this) { + NORTH -> EAST + SOUTH -> WEST + WEST -> NORTH + EAST -> SOUTH + } + fun previous() = when(this) { + NORTH -> WEST + WEST -> SOUTH + SOUTH -> EAST + EAST -> NORTH + } +} diff --git a/basic_kotlin_project/src/main/kotlin/model/MoveDirection.kt b/basic_kotlin_project/src/main/kotlin/model/MoveDirection.kt new file mode 100644 index 0000000..3c1c517 --- /dev/null +++ b/basic_kotlin_project/src/main/kotlin/model/MoveDirection.kt @@ -0,0 +1,8 @@ +package model; + +enum class MoveDirection { + FORWARD, + BACKWARD, + RIGHT, + LEFT; +} diff --git a/basic_kotlin_project/src/main/kotlin/model/RandomExtensions.kt b/basic_kotlin_project/src/main/kotlin/model/RandomExtensions.kt new file mode 100644 index 0000000..1f21260 --- /dev/null +++ b/basic_kotlin_project/src/main/kotlin/model/RandomExtensions.kt @@ -0,0 +1,18 @@ +package model + +fun Map.randomPosition(): Vector2d? { + return keys.toList().randomOrNull() +} +fun Map.randomFreePosition(mapSize: Vector2d): Vector2d? { + val x = mapSize.x + val y = mapSize.y + val allPositions = mutableSetOf() + for(i in 0..x) { + for(j in 0..y) { + allPositions.add(Vector2d(i,j)) + } + } + val occupiedPositions = keys.toList().toSet() + if(allPositions == occupiedPositions) return null + return (allPositions - occupiedPositions).random() +} \ No newline at end of file diff --git a/basic_kotlin_project/src/main/kotlin/model/Vector2d.kt b/basic_kotlin_project/src/main/kotlin/model/Vector2d.kt new file mode 100644 index 0000000..7e94b55 --- /dev/null +++ b/basic_kotlin_project/src/main/kotlin/model/Vector2d.kt @@ -0,0 +1,23 @@ +package model; + +data class Vector2d(val x: Int, val y: Int) { + infix fun follows(other: Vector2d) = (x >= other.x && y >= other.y) + infix fun precedes(other: Vector2d) = (x <= other.x && y <= other.y) + + fun upperRight(other: Vector2d) = (Vector2d(maxOf(x, other.x), maxOf(y, other.y))) + fun lowerLeft(other: Vector2d) = (Vector2d(minOf(x, other.x), minOf(y, other.y))) + + operator fun plus(other: Vector2d) = (Vector2d(x + other.x, y + other.y)) + operator fun minus(other: Vector2d) = (Vector2d(x - other.x, y - other.y)) + fun opposite() = (Vector2d(y, x)) + + override fun toString() = ("($x,$y)") +} +fun MapDirection.toUnitVector() : Vector2d { + return when (this) { + MapDirection.NORTH -> Vector2d(0, 1) + MapDirection.WEST -> Vector2d(-1, 0) + MapDirection.SOUTH -> Vector2d(0, -1) + MapDirection.EAST -> Vector2d(1, 0) + } +} diff --git a/basic_kotlin_project/src/test/kotlin/model/AnimalTest.kt b/basic_kotlin_project/src/test/kotlin/model/AnimalTest.kt new file mode 100644 index 0000000..87929ce --- /dev/null +++ b/basic_kotlin_project/src/test/kotlin/model/AnimalTest.kt @@ -0,0 +1,84 @@ +package model + +import org.junit.jupiter.api.Assertions.assertEquals +import kotlin.test.Test + +class AnimalTest { + @Test + fun `toString should return the string representation of the animal's orientation`() { + val animal1 = Animal() + val animal2 = Animal(MapDirection.EAST) + val animal3 = Animal(position = Vector2d(2, 2)) + + assertEquals("N", animal1.toString()) + assertEquals("E", animal2.toString()) + assertEquals("N", animal3.toString()) + } + + @Test + fun `isAt should return true when the animal is at the specified position and false when not`() { + val animal1 = Animal(position = Vector2d(4, 4)) + val animal2 = Animal() + + assertEquals(true, animal1.isAt(Vector2d(4, 4))) + assertEquals(true, animal2.isAt(Vector2d(2, 2))) + assertEquals(false, animal1.isAt(Vector2d(3, 3))) + assertEquals(false, animal2.isAt(Vector2d(3, 3))) + } + + @Test + fun `getPosition should return the current position of the animal`() { + val animal1 = Animal(position = Vector2d(7, 7)) + val animal2 = Animal() + + assertEquals(Vector2d(7, 7), animal1.getPosition()) + assertEquals(Vector2d(2, 2), animal2.getPosition()) + } + + @Test + fun `move should update the animal's orientation and position based on the given direction`() { + val animal1 = Animal() + val animal2 = Animal(MapDirection.SOUTH, Vector2d(4, 4)) + + animal1.move(MoveDirection.RIGHT) + assertEquals(MapDirection.EAST.toString(), animal1.toString()) + + animal1.move(MoveDirection.LEFT) + assertEquals(MapDirection.NORTH.toString(), animal1.toString()) + + animal1.move(MoveDirection.FORWARD) + assertEquals(Vector2d(2, 3), animal1.getPosition()) + + animal1.move(MoveDirection.BACKWARD) + assertEquals(Vector2d(2, 2), animal1.getPosition()) + + animal1.move(MoveDirection.LEFT) + assertEquals(MapDirection.WEST.toString(), animal1.toString()) + + animal1.move(MoveDirection.FORWARD) + assertEquals(Vector2d(1, 2), animal1.getPosition()) + + animal1.move(MoveDirection.BACKWARD) + assertEquals(Vector2d(2, 2), animal1.getPosition()) + + + + animal2.move(MoveDirection.RIGHT) + assertEquals(MapDirection.WEST.toString(), animal2.toString()) + + animal2.move(MoveDirection.FORWARD) + assertEquals(Vector2d(3, 4), animal2.getPosition()) + + animal2.move(MoveDirection.BACKWARD) + assertEquals(Vector2d(4, 4), animal2.getPosition()) + + animal2.move(MoveDirection.LEFT) + assertEquals(MapDirection.SOUTH.toString(), animal2.toString()) + + animal2.move(MoveDirection.FORWARD) + assertEquals(Vector2d(4, 3), animal2.getPosition()) + + animal2.move(MoveDirection.BACKWARD) + assertEquals(Vector2d(4, 4), animal2.getPosition()) + } +} \ No newline at end of file diff --git a/basic_kotlin_project/src/test/kotlin/model/BouncyMapTest.kt b/basic_kotlin_project/src/test/kotlin/model/BouncyMapTest.kt new file mode 100644 index 0000000..81761e5 --- /dev/null +++ b/basic_kotlin_project/src/test/kotlin/model/BouncyMapTest.kt @@ -0,0 +1,102 @@ +package model + +import org.junit.jupiter.api.Assertions.* +import kotlin.test.Test + +class BouncyMapTest { + @Test + fun `canMoveTo should return true when position is within map boundaries`() { + val map = BouncyMap(5, 5) + val position1 = Vector2d(3, 3) + val position2 = Vector2d(0, 0) + val position3 = Vector2d(5, 5) + + assertTrue(map.canMoveTo(position1)) + assertTrue(map.canMoveTo(position2)) + assertTrue(map.canMoveTo(position3)) + } + + @Test + fun `canMoveTo should return false when position is outside map boundaries`() { + val map = BouncyMap(5, 5) + val position1 = Vector2d(-1, 3) + val position2 = Vector2d(3, -1) + val position3 = Vector2d(6, 3) + val position4 = Vector2d(3, 6) + + assertFalse(map.canMoveTo(position1)) + assertFalse(map.canMoveTo(position2)) + assertFalse(map.canMoveTo(position3)) + assertFalse(map.canMoveTo(position4)) + } + + @Test + fun `isOccupied should return true when position is occupied by an animal`() { + val map = BouncyMap(5, 5) + val animal = Animal(position = Vector2d(3, 3)) + map.place(animal) + val position = Vector2d(3, 3) + + assertTrue(map.isOccupied(position)) + } + + @Test + fun `isOccupied should return false when position is not occupied by any animal`() { + val map = BouncyMap(5, 5) + val position = Vector2d(3, 3) + + assertFalse(map.isOccupied(position)) + } + + @Test + fun `objectAt should return the animal at the given position`() { + val map = BouncyMap(5, 5) + val animal = Animal(position = Vector2d(3, 3)) + map.place(animal) + val position = Vector2d(3, 3) + + assertEquals(animal, map.objectAt(position)) + } + + @Test + fun `objectAt should return null when there is no animal at the given position`() { + val map = BouncyMap(5, 5) + val position = Vector2d(3, 3) + + assertNull(map.objectAt(position)) + } + + @Test + fun `move should update the animal's position and map correctly`() { + val map = BouncyMap(5, 5) + val animal = Animal(position = Vector2d(3, 3)) + map.place(animal) + + map.move(animal, MoveDirection.RIGHT) + + assertEquals(Vector2d(3, 3), animal.getPosition()) + assertNull(map.objectAt(Vector2d(4, 3))) + assertEquals(animal, map.objectAt(Vector2d(3, 3))) + + map.move(animal, MoveDirection.FORWARD) + assertEquals(Vector2d(4, 3), animal.getPosition()) + assertNull(map.objectAt(Vector2d(3, 3))) + assertEquals(animal, map.objectAt(Vector2d(4, 3))) + } + + @Test + fun `place should add the animal to the map at the given position`() { + val map = BouncyMap(5, 5) + val animal = Animal(position = Vector2d(3, 3)) + + map.place(animal) + + assertEquals(animal, map.objectAt(Vector2d(3, 3))) + + animal.setPosition(Vector2d(4, 3)) + map.place(animal) + + assertEquals(animal, map.objectAt(Vector2d(4, 3))) + assertNotEquals(animal, map.objectAt(Vector2d(3, 3))) + } +} \ No newline at end of file diff --git a/basic_kotlin_project/src/test/kotlin/model/MapDirectionTest.kt b/basic_kotlin_project/src/test/kotlin/model/MapDirectionTest.kt new file mode 100644 index 0000000..9f0f76b --- /dev/null +++ b/basic_kotlin_project/src/test/kotlin/model/MapDirectionTest.kt @@ -0,0 +1,30 @@ +package model + +import org.junit.jupiter.api.Assertions.assertEquals +import kotlin.test.Test + +class MapDirectionTest { + @Test + fun `toString should return correct string representation`() { + assertEquals("N", MapDirection.NORTH.toString()) + assertEquals("S", MapDirection.SOUTH.toString()) + assertEquals("W", MapDirection.WEST.toString()) + assertEquals("E", MapDirection.EAST.toString()) + } + + @Test + fun `next should return the next direction in clockwise order`() { + assertEquals(MapDirection.EAST, MapDirection.NORTH.next()) + assertEquals(MapDirection.WEST, MapDirection.SOUTH.next()) + assertEquals(MapDirection.NORTH, MapDirection.WEST.next()) + assertEquals(MapDirection.SOUTH, MapDirection.EAST.next()) + } + + @Test + fun `previous should return the previous direction in clockwise order`() { + assertEquals(MapDirection.WEST, MapDirection.NORTH.previous()) + assertEquals(MapDirection.SOUTH, MapDirection.WEST.previous()) + assertEquals(MapDirection.EAST, MapDirection.SOUTH.previous()) + assertEquals(MapDirection.NORTH, MapDirection.EAST.previous()) + } +} \ No newline at end of file diff --git a/basic_kotlin_project/src/test/kotlin/model/Vector2dTest.kt b/basic_kotlin_project/src/test/kotlin/model/Vector2dTest.kt new file mode 100644 index 0000000..66a3938 --- /dev/null +++ b/basic_kotlin_project/src/test/kotlin/model/Vector2dTest.kt @@ -0,0 +1,96 @@ +package model + +import org.junit.jupiter.api.Assertions.assertEquals +import kotlin.test.Test + +class Vector2dTest { + @Test + fun `follows should return true when vector follows the other vector and false when not`() { + val vector1 = Vector2d(3, 5) + val vector2 = Vector2d(2, 4) + val vector3 = Vector2d(3, 5) + val vector4 = Vector2d(2, 6) + + assertEquals(true, vector1 follows vector2) + assertEquals(true, vector1 follows vector3) + assertEquals(false, vector2 follows vector1) + assertEquals(false, vector3 follows vector4) + } + + @Test + fun `precedes should return true when vector precedes the other vector and false when not`() { + val vector1 = Vector2d(3, 5) + val vector2 = Vector2d(4, 6) + val vector3 = Vector2d(3, 5) + val vector4 = Vector2d(2, 6) + + assertEquals(true, vector1 precedes vector2) + assertEquals(true, vector1 precedes vector3) + assertEquals(false, vector2 precedes vector1) + assertEquals(false, vector2 precedes vector4) + } + + @Test + fun `upperRight should return the upper right vector`() { + val vector1 = Vector2d(3, 5) + val vector2 = Vector2d(2, 8) + val vector3 = Vector2d(4, 6) + + assertEquals(Vector2d(3, 8), vector1.upperRight(vector2)) + assertEquals(Vector2d(4, 6), vector1.upperRight(vector3)) + } + + @Test + fun `lowerLeft should return the lower left vector`() { + val vector1 = Vector2d(3, 5) + val vector2 = Vector2d(2, 4) + val vector3 = Vector2d(4, 6) + + assertEquals(Vector2d(2, 4), vector1.lowerLeft(vector2)) + assertEquals(Vector2d(3, 5), vector1.lowerLeft(vector3)) + } + + @Test + fun `opposite should return the vector with swapped coordinates`() { + val vector = Vector2d(3, 5) + + assertEquals(Vector2d(5, 3), vector.opposite()) + } + + @Test + fun `toString should return the string representation of the vector`() { + val vector = Vector2d(3, 5) + + assertEquals("(3,5)", vector.toString()) + } + @Test + fun `add should return the sum of two vectors`() { + val vector1 = Vector2d(3, 5) + val vector2 = Vector2d(2, 4) + + assertEquals(Vector2d(5, 9), vector1 + vector2) + } + @Test + fun `subtract should return the difference of two vectors`() { + val vector1 = Vector2d(3, 5) + val vector2 = Vector2d(2, 4) + + assertEquals(Vector2d(1, 1), vector1 - vector2) + } + @Test + fun `equaling should return if two vectors are equal`() { + val vector1 = Vector2d(3, 5) + val vector2 = Vector2d(2, 4) + val vector3 = Vector2d(3, 5) + + assertEquals(false, vector1 == vector2) + assertEquals(true, vector1 == vector3) + } + @Test + fun `toUnitVector should return the correct unit vector`() { + assertEquals(Vector2d(0, 1), MapDirection.NORTH.toUnitVector()) + assertEquals(Vector2d(-1, 0), MapDirection.WEST.toUnitVector()) + assertEquals(Vector2d(0, -1), MapDirection.SOUTH.toUnitVector()) + assertEquals(Vector2d(1, 0), MapDirection.EAST.toUnitVector()) + } +}