diff --git a/.gitignore b/.gitignore index b6e4761..1d4e738 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,5 @@ dmypy.json # Pyre type checker .pyre/ + +.idea/ \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..909a418 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,16 @@ +test: + script: + - apt-get update -qy + - apt-get install -y python-dev python-pip + - pip install -r requirements.txt + - pytest -v tests + +staging: + type: deploy + script: + - apt-get update -qy + - apt-get install -y python-dev + - pip install -r requirements.txt + - dpl --provider=heroku --app=$HEROKU_STAGING_APP + only: + - master diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..095794f --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +run: + pipenv run python -m rover.main +test: tests +tests: + pipenv run pytest -v --cov=. --cov-report=html +lint: + pipenv run black -l 99 . + pipenv run flake8 . + pipenv run mypy . + +.PHONY: run test tests lint \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..d8a8205 --- /dev/null +++ b/Pipfile @@ -0,0 +1,18 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] + +[dev-packages] +pytest = "*" +pytest-cov = "*" +black = "*" +mypy = "*" + +[requires] +python_version = "3.9" + +[pipenv] +allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..2a21339 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,256 @@ +{ + "_meta": { + "hash": { + "sha256": "2f01db9a7ea9d9fbf733806a56ecb7cd41a930958d13ff14a9e8d161f3c08c2d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": {}, + "develop": { + "appdirs": { + "hashes": [ + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" + ], + "version": "==1.4.4" + }, + "attrs": { + "hashes": [ + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" + }, + "black": { + "hashes": [ + "sha256:1c7aa6ada8ee864db745b22790a32f94b2795c253a75d6d9b5e439ff10d23116", + "sha256:c8373c6491de9362e39271630b65b964607bc5c79c83783547d76c839b3aa219" + ], + "index": "pypi", + "version": "==21.7b0" + }, + "click": { + "hashes": [ + "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", + "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" + ], + "markers": "python_version >= '3.6'", + "version": "==8.0.1" + }, + "coverage": { + "hashes": [ + "sha256:16db4173575901db8f3e6cc05e50fe19c7849b0256f6dc2e0979485184053417", + "sha256:18183948d5480e2ae30ad67edddf748149c778592b7e4ee649c058d5de2dcbb1", + "sha256:22888d3ce1b6fa1125f0be1602d8c634e00e7ec3a87bdb594ad87bde0b00b2b6", + "sha256:23c1611471cbfa2ac0e283862a76a333c13e5e7c4d499feb9919a5f52884610e", + "sha256:2dcc6d62b69a82759e5dddd788e09dd329124e493e62d92cfd01c0b918d7e511", + "sha256:40e30139113b141c238620b700aa5bd5c1b3a7b29ae47398936ff1c9166109d9", + "sha256:4528368196a90f11b70fb5668c13d92e88ba795eb4d37aab5855fd0479db417b", + "sha256:4cbdc51fc8c00ec6e53b30221d5757034aecf9839761bf97eaec0db7f0ff4955", + "sha256:6a585ba4087cc1fb5bfe34d1ecaaee183b854427992be2b42f1722ba8289fa82", + "sha256:79c136327e90ee46a2b3094263df94da5212890d6145678741eb805d79714971", + "sha256:7beec4df7542cf681356ef243fee3bf948775fc0d125bdcad3508e834229e07d", + "sha256:8394626a07e0a1b3695a16a4548d32e7259e00817d4bab1ef8172a1bd82a724e", + "sha256:84a1000f622d1df8824cd1ac629aa8392679c5c4de3f0de9e6889373f99ff3a0", + "sha256:91cd79f0f2996a4de737de89fdcbcd379a5bfd7b15129378ad1e5fc234e58d33", + "sha256:951e8d7bc98bceb61fc4fb426966fae854160301c0f8cd0945c62f2504f68615", + "sha256:95d2293d6a60da8952c675050231c02c9f4f1c1b9cf916315173e921d137d683", + "sha256:9981294b131023e63061ba88f4498fe27b9b15d908079d1866ee66a63d6e793f", + "sha256:a8826f6ecf079cb648534790ba59218a64e12a59bf2cd9ff00199abb39864a79", + "sha256:c1630e847ae0a2a366f18ddc3e017b69f80d729e95830579c61b5f9e9b94b91e", + "sha256:c6f46d5bbec8fe1ff25215356e819528a90d84b2801703514746b665742f1cd2", + "sha256:c8099c7033fb1ca73ac2246c3e52f45dd6a9c3826c59b3b5ad94e5be4e08d99b", + "sha256:ceb872b89c6461d4365be5f8fbf14f867be6b5217760980de7e014e54648f8ef", + "sha256:d6fbe69d52628b3e8a144265fd134f5da07cf287a00cf529730ae10380d315b2", + "sha256:da7de6e4162c69cc03cc56b7d051ae11147ac30872ff57df4ba4cac6d70ce5d9", + "sha256:ddb2287f66500ac57b24cce60341074b148977b74cd20eca755f95262928086f", + "sha256:e6a4260f0abf90c023b4f838905f645695b31666b76837152e2befad3d1ef5d6", + "sha256:e97b387f2744762b9984639b59abd7abb46ea6ae2ea24cb7c07893612328559b", + "sha256:ea784c96ca3b94912176d7adc9c4bb7d1988f36a0223a9ac128f4c834775202c", + "sha256:f0b250a03891255feb3ae69ac29d05cf9a62f5869bb8bac0e7f4968e7274efac", + "sha256:fdaa96733c9cf85491ad406fd78aa16025a1ea468951545b3da7ee133c150c7a" + ], + "markers": "python_version >= '3.6'", + "version": "==6.0b1" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "mypy": { + "hashes": [ + "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9", + "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a", + "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9", + "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e", + "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2", + "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212", + "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b", + "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885", + "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150", + "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703", + "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072", + "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457", + "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e", + "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0", + "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb", + "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97", + "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8", + "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811", + "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6", + "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de", + "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504", + "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921", + "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d" + ], + "index": "pypi", + "version": "==0.910" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "packaging": { + "hashes": [ + "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7", + "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14" + ], + "markers": "python_version >= '3.6'", + "version": "==21.0" + }, + "pathspec": { + "hashes": [ + "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", + "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" + ], + "version": "==0.9.0" + }, + "pluggy": { + "hashes": [ + "sha256:265a94bf44ca13662f12fcd1b074c14d4b269a712f051b6f644ef7e705d6735f", + "sha256:467f0219e89bb5061a8429c6fc5cf055fa3983a0e68e84a1d205046306b37d9e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.0.0.dev0" + }, + "py": { + "hashes": [ + "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", + "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.10.0" + }, + "pyparsing": { + "hashes": [ + "sha256:10f1886e70da7b76ca1b4cf9bbd60f708ef037892496d5cb7c77ab2982412b2d", + "sha256:e96a96967098a5221a78bf94d72930cd1cfaf0ab88dae2ea6bfc2b8b8ccb1930" + ], + "markers": "python_version >= '3.5'", + "version": "==3.0.0b3" + }, + "pytest": { + "hashes": [ + "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b", + "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890" + ], + "index": "pypi", + "version": "==6.2.4" + }, + "pytest-cov": { + "hashes": [ + "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a", + "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7" + ], + "index": "pypi", + "version": "==2.12.1" + }, + "regex": { + "hashes": [ + "sha256:0696eb934dee723e3292056a2c046ddb1e4dd3887685783a9f4af638e85dee76", + "sha256:105122fa63da98d8456d5026bc6ac5a1399fd82fa6bad22c6ea641b1572c9142", + "sha256:116c277774f84266044e889501fe79cfd293a8b4336b7a5e89b9f20f1e5a9f21", + "sha256:12eaf0bbe568bd62e6cade7937e0bf01a2a4cef49a82f4fd204401e78409e158", + "sha256:1401cfa4320691cbd91191ec678735c727dee674d0997b0902a5a38ad482faf5", + "sha256:19acdb8831a4e3b03b23369db43178d8fee1f17b99c83af6cd907886f76bd9d4", + "sha256:208851a2f8dd31e468f0b5aa6c94433975bd67a107a4e7da3bdda947c9f85e25", + "sha256:24d68499a27b2d93831fde4a9b84ea5b19e0ab141425fbc9ab1e5b4dad179df7", + "sha256:2778c6cb379d804e429cc8e627392909e60db5152b42c695c37ae5757aae50ae", + "sha256:2a0a5e323cf86760784ce2b91d8ab5ea09d0865d6ef4da0151e03d15d097b24e", + "sha256:2d9cbe0c755ab8b6f583169c0783f7278fc6b195e423b09c5a8da6f858025e96", + "sha256:2de1429e4eeab799c168a4f6e6eecdf30fcaa389bba4039cc8a065d6b7aad647", + "sha256:32753eda8d413ce4f208cfe01dd61171a78068a6f5d5f38ccd751e00585cdf1d", + "sha256:3ee8ad16a35c45a5bab098e39020ecb6fec3b0e700a9d88983d35cbabcee79c8", + "sha256:4f03fc0a25122cdcbf39136510d4ea7627f732206892db522adf510bc03b8c67", + "sha256:4f3e36086d6631ceaf468503f96a3be0d247caef0660c9452fb1b0c055783851", + "sha256:503c1ba0920a46a1844363725215ef44d59fcac2bd2c03ae3c59aa9d08d29bd6", + "sha256:507861cf3d97a86fbe26ea6cc04660ae028b9e4080b8290e28b99547b4e15d89", + "sha256:56ae6e3cf0506ec0c40b466e31f41ee7a7149a2b505ae0ee50edd9043b423d27", + "sha256:6530b7b9505123cdea40a2301225183ca65f389bc6129f0c225b9b41680268d8", + "sha256:6729914dd73483cd1c8aaace3ac082436fc98b0072743ac136eaea0b3811d42f", + "sha256:7406dd2e44c7cfb4680c0a45a03264381802c67890cf506c147288f04c67177d", + "sha256:7684016b73938ca12d160d2907d141f06b7597bd17d854e32bb7588be01afa1d", + "sha256:7db58ad61f3f6ea393aaf124d774ee0c58806320bc85c06dc9480f5c7219c250", + "sha256:83946ca9278b304728b637bc8d8200ab1663a79de85e47724594917aeed0e892", + "sha256:84057cfae5676f456b03970eb78b7e182fddc80c2daafd83465a3d6ca9ff8dbf", + "sha256:862b6164e9a38b5c495be2c2854e75fd8af12c5be4c61dc9b42d255980d7e907", + "sha256:8ddb4f9ce6bb388ecc97b4b3eb37e786f05d7d5815e8822e0d87a3dbd7100649", + "sha256:92eb03f47427fea452ff6956d11f5d5a3f22a048c90a0f34fa223e6badab6c85", + "sha256:a5f3bc727fea58f21d99c22e6d4fca652dc11dbc2a1e7cfc4838cd53b2e3691f", + "sha256:a6180dbf5945b27e9420e1b58c3cacfc79ad5278bdad3ea35109f5680fbe16d1", + "sha256:b158f673ae6a6523f13704f70aa7e4ce875f91e379bece4362c89db18db189d5", + "sha256:cd45b4542134de63e7b9dd653e0a2d7d47ffed9615e3637c27ca5f6b78ea68bb", + "sha256:d2404336fd16788ea757d4218a2580de60adb052d9888031e765320be8884309", + "sha256:db888d4fb33a2fd54b57ac55d5015e51fa849f0d8592bd799b4e47f83bd04e00", + "sha256:dde0ac721c7c5bfa5f9fc285e811274dec3c392f2c1225f7d07ca98a8187ca84", + "sha256:de0d06ccbc06af5bf93bddec10f4f80275c5d74ea6d28b456931f3955f58bc8c", + "sha256:e02dad60e3e8442eefd28095e99b2ac98f2b8667167493ac6a2f3aadb5d84a17", + "sha256:e960fe211496333b2f7e36badf4c22a919d740386681f79139ee346b403d1ca1", + "sha256:e9700c52749cb3e90c98efd72b730c97b7e4962992fca5fbcaf1363be8e3b849", + "sha256:ee318974a1fdacba1701bc9e552e9015788d6345416364af6fa987424ff8df53" + ], + "version": "==2021.8.27" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.10.2" + }, + "tomli": { + "hashes": [ + "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f", + "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442" + ], + "markers": "python_version >= '3.6'", + "version": "==1.2.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" + ], + "version": "==3.10.0.0" + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..9bc34c8 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Mars Rover + +See [description](./description.pdf) for more details. \ No newline at end of file diff --git a/description.pdf b/description.pdf new file mode 100644 index 0000000..a9257c9 Binary files /dev/null and b/description.pdf differ diff --git a/rover/__init__.py b/rover/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rover/rover.py b/rover/rover.py new file mode 100644 index 0000000..f8367a1 --- /dev/null +++ b/rover/rover.py @@ -0,0 +1,78 @@ +from dataclasses import dataclass, field +from typing import Tuple + + +@dataclass(frozen=True) +class Coordinates: + x: int + y: int + left: str + right: str + + +@dataclass +class Rover: + x: int + y: int + direction: str + obstacles: list[list[int]] = field(default_factory=list) + + valid_commands = ("F", "B", "R", "L") + + direction_map = { + "NORTH": Coordinates(0, 1, "WEST", "EAST"), + "EAST": Coordinates(1, 0, "NORTH", "SOUTH"), + "SOUTH": Coordinates(0, -1, "EAST", "WEST"), + "WEST": Coordinates(-1, 0, "SOUTH", "NORTH"), + } + + def is_valid_command(self, command: str) -> bool: + for ch in command: + if ch not in self.valid_commands: + return False + + return True + + def is_obstacle(self, x: int, y: int) -> bool: + return [x, y] in self.obstacles + + def move(self, command: str): + if not self.is_valid_command(command): + raise ValueError("invalid command. The rover does not move") + + for ch in command: + x, y, direction = self._compute_new_coordinates(ch) + if obstacle := self.is_obstacle(x, y): + return self._new_coordinates_output(obstacle) + + self._apply_new_coordinates(x, y, direction) + + return self._new_coordinates_output() + + def _new_coordinates_output(self, obstacle: bool = False): + if obstacle: + return self.x, self.y, self.direction, "STOPPED" + return self.x, self.y, self.direction + + def _compute_new_coordinates(self, command: str) -> Tuple[int, int, str]: + x = self.x + y = self.y + direction = self.direction + + if command == "B": + x -= self.direction_map[self.direction].x + y -= self.direction_map[self.direction].y + if command == "F": + x += self.direction_map[self.direction].x + y += self.direction_map[self.direction].y + if command == "R": + direction = self.direction_map[self.direction].right + if command == "L": + direction = self.direction_map[self.direction].left + + return x, y, direction + + def _apply_new_coordinates(self, x: int, y: int, direction: str) -> None: + self.x = x + self.y = y + self.direction = direction diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..832bd84 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,18 @@ +[flake8] +exclude = .git, __pycache__, __init__.py, app/migrations/* +max-complexity = 13 +max-line-length = 120 + +[mypy] +ignore_missing_imports = True +warn_unused_configs = True +no_implicit_optional = True +warn_unused_ignores = True +warn_unreachable = True +warn_redundant_casts = True +#disallow_untyped_defs=True + +plugins = pydantic.mypy +[pydantic-mypy] +init_forbid_extra = True +init_typed = True diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..1129c75 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,8 @@ +import pytest as pytest + +from rover.rover import Rover + + +@pytest.fixture +def rover(): + return Rover(0, 0, "EAST") diff --git a/tests/test_rover.py b/tests/test_rover.py new file mode 100644 index 0000000..9400442 --- /dev/null +++ b/tests/test_rover.py @@ -0,0 +1,77 @@ +import pytest + +from rover.rover import Rover + + +def test_rover_initialisation(): + assert Rover(0, 0, "EAST") + + +def test_rover_can_move(rover): + command = "F" + assert rover.move(command) is not None + + +@pytest.mark.parametrize( + ("init", "command", "expected"), + [ + ((0, 0, "EAST"), "F", (1, 0, "EAST")), + ((0, 0, "EAST"), "B", (-1, 0, "EAST")), + ((0, 0, "EAST"), "R", (0, 0, "SOUTH")), + ((0, 0, "EAST"), "L", (0, 0, "NORTH")), + # change starting point + ((0, 0, "WEST"), "F", (-1, 0, "WEST")), + ((0, 0, "SOUTH"), "R", (0, 0, "WEST")), + ((0, 0, "SOUTH"), "F", (0, -1, "SOUTH")), + ], +) +def test_rover_receive_commands(init, command, expected): + rover = Rover(*init) + assert rover.move(command) == expected + + +def test_rover_rejects_invalid_commands(rover): + command = " W " + with pytest.raises(ValueError): + rover.move(command) + + +@pytest.mark.parametrize( + ("init", "command", "expected"), + [ + ((0, 0, "NORTH"), "FB", (0, 0, "NORTH")), + ((0, 0, "NORTH"), "FF", (0, 2, "NORTH")), + ((4, 2, "EAST"), "FLFFFRFLB", (6, 4, "NORTH")), + ], +) +def test_rover_can_receive_multiple_commands(init, command, expected): + rover = Rover(*init) + assert rover.move(command) == expected + + +@pytest.mark.parametrize( + ("init", "obstacles", "command", "expected"), + [ + ( + (0, 0, "NORTH"), + [[0, 1]], + "F", + (0, 0, "NORTH", "STOPPED"), + ), + ( + (0, 0, "NORTH"), + [[1, 1]], + "FRF", + (0, 1, "EAST", "STOPPED"), + ), + ( + (4, 2, "EAST"), + [[1, 4], [5, 5], [7, 4]], + "FLFFFRFLB", + (5, 4, "NORTH", "STOPPED"), + ), + ], +) +def test_rover_avoids_obstacles(init, obstacles, command, expected): + rover = Rover(*init, obstacles=obstacles) + assert rover.move(command) == expected