Merge pull request #1 from rjNemo/master

Master
This commit is contained in:
Ruidy 2021-08-30 11:55:08 +02:00 committed by GitHub
commit 068d32e287
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 487 additions and 0 deletions

2
.gitignore vendored
View file

@ -127,3 +127,5 @@ dmypy.json
# Pyre type checker
.pyre/
.idea/

16
.gitlab-ci.yml Normal file
View file

@ -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

11
Makefile Normal file
View file

@ -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

18
Pipfile Normal file
View file

@ -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

256
Pipfile.lock generated Normal file
View file

@ -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"
}
}
}

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# Mars Rover
See [description](./description.pdf) for more details.

BIN
description.pdf Normal file

Binary file not shown.

0
rover/__init__.py Normal file
View file

78
rover/rover.py Normal file
View file

@ -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

18
setup.cfg Normal file
View file

@ -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

0
tests/__init__.py Normal file
View file

8
tests/conftest.py Normal file
View file

@ -0,0 +1,8 @@
import pytest as pytest
from rover.rover import Rover
@pytest.fixture
def rover():
return Rover(0, 0, "EAST")

77
tests/test_rover.py Normal file
View file

@ -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