Compare commits
45 Commits
9f431fcb11
...
arghing
Author | SHA1 | Date | |
---|---|---|---|
3de96b813f
|
|||
a73acedc6d
|
|||
92e3d6fe5b
|
|||
395006cdb8
|
|||
622d47071a
|
|||
1452d544bb
|
|||
56c6a8cf09
|
|||
cc29386cee
|
|||
95453bf197
|
|||
a6526af6ff
|
|||
3ea30a997a
|
|||
03edacc8ec
|
|||
d1ffbe25a3
|
|||
dc3a40b19d
|
|||
f2e0de1b66
|
|||
58842504c4
|
|||
1080737931
|
|||
de6e907c60
|
|||
9989801e62
|
|||
8c280c303e
|
|||
b2e61cd0d2
|
|||
c15bafe55d
|
|||
af7d5c6e14
|
|||
512eddc1ab
|
|||
2bc9788441
|
|||
c52d9b9563
|
|||
fbb04df86d
|
|||
e30cbe2aca
|
|||
116ad347db
|
|||
1493deac96
|
|||
a7e45b8add
|
|||
f1cc614836
|
|||
a2661f4369
|
|||
b020618d57
|
|||
d2405f75d8
|
|||
1d827b517b
|
|||
47d658f04f
|
|||
6c45a1390b
|
|||
b25e99a632
|
|||
abd957d42f
|
|||
d326a055f7
|
|||
6d6e600720
|
|||
d2563484f9
|
|||
eac3c2d8a6
|
|||
052fb87667
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
*.hex
|
||||
*.log
|
||||
*.out
|
||||
*env
|
||||
.dep
|
||||
**/target/
|
||||
|
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (C) 2021 Dan Buch
|
||||
Copyright (C) 2022 Dan Buch
|
||||
|
||||
MIT License
|
||||
|
||||
|
@@ -1,13 +0,0 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
|
||||
[dev-packages]
|
||||
mypy = ""
|
||||
pytest = ""
|
||||
|
||||
[requires]
|
||||
python_version = "3.9"
|
156
aoc2020/py/Pipfile.lock
generated
156
aoc2020/py/Pipfile.lock
generated
@@ -1,156 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "ed02d1728cc686824535903ae2f5f3956ba104434c4c6e532237df55bcd69a12"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.9"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {},
|
||||
"develop": {
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
|
||||
"sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==20.3.0"
|
||||
},
|
||||
"iniconfig": {
|
||||
"hashes": [
|
||||
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
|
||||
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
|
||||
],
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"mypy": {
|
||||
"hashes": [
|
||||
"sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324",
|
||||
"sha256:0d34d6b122597d48a36d6c59e35341f410d4abfa771d96d04ae2c468dd201abc",
|
||||
"sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802",
|
||||
"sha256:2842d4fbd1b12ab422346376aad03ff5d0805b706102e475e962370f874a5122",
|
||||
"sha256:2b21ba45ad9ef2e2eb88ce4aeadd0112d0f5026418324176fd494a6824b74975",
|
||||
"sha256:72060bf64f290fb629bd4a67c707a66fd88ca26e413a91384b18db3876e57ed7",
|
||||
"sha256:af4e9ff1834e565f1baa74ccf7ae2564ae38c8df2a85b057af1dbbc958eb6666",
|
||||
"sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669",
|
||||
"sha256:c614194e01c85bb2e551c421397e49afb2872c88b5830e3554f0519f9fb1c178",
|
||||
"sha256:cf4e7bf7f1214826cf7333627cb2547c0db7e3078723227820d0a2490f117a01",
|
||||
"sha256:da56dedcd7cd502ccd3c5dddc656cb36113dd793ad466e894574125945653cea",
|
||||
"sha256:e86bdace26c5fe9cf8cb735e7cedfe7850ad92b327ac5d797c656717d2ca66de",
|
||||
"sha256:e97e9c13d67fbe524be17e4d8025d51a7dca38f90de2e462243ab8ed8a9178d1",
|
||||
"sha256:eea260feb1830a627fb526d22fbb426b750d9f5a47b624e8d5e7e004359b219c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==0.790"
|
||||
},
|
||||
"mypy-extensions": {
|
||||
"hashes": [
|
||||
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
|
||||
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
|
||||
],
|
||||
"version": "==0.4.3"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858",
|
||||
"sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==20.8"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
|
||||
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.13.1"
|
||||
},
|
||||
"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:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.7"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8",
|
||||
"sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==6.2.1"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.2"
|
||||
},
|
||||
"typed-ast": {
|
||||
"hashes": [
|
||||
"sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1",
|
||||
"sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d",
|
||||
"sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6",
|
||||
"sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd",
|
||||
"sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37",
|
||||
"sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151",
|
||||
"sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07",
|
||||
"sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440",
|
||||
"sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70",
|
||||
"sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496",
|
||||
"sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea",
|
||||
"sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400",
|
||||
"sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc",
|
||||
"sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606",
|
||||
"sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc",
|
||||
"sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581",
|
||||
"sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412",
|
||||
"sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a",
|
||||
"sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2",
|
||||
"sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787",
|
||||
"sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f",
|
||||
"sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937",
|
||||
"sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64",
|
||||
"sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487",
|
||||
"sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b",
|
||||
"sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41",
|
||||
"sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a",
|
||||
"sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3",
|
||||
"sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166",
|
||||
"sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"
|
||||
],
|
||||
"version": "==1.4.2"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
|
||||
"sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
|
||||
"sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
|
||||
],
|
||||
"version": "==3.7.4.3"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
import sys
|
||||
import typing
|
||||
|
||||
|
||||
def main() -> int:
|
||||
inputs = [int(s) for s in sys.stdin.read().split()]
|
||||
for i, j in _find_2020_pairs(inputs):
|
||||
print(f"pair: {i} * {j} == {i * j}")
|
||||
|
||||
for i, j, k in _find_2020_triplets(inputs):
|
||||
print(f"triplet: {i} * {j} * {k} == {i * j * k}")
|
||||
return 0
|
||||
|
||||
|
||||
def _find_2020_pairs(
|
||||
inputs: typing.List[int],
|
||||
) -> typing.Generator[typing.Tuple[int, ...], None, None]:
|
||||
found = []
|
||||
|
||||
for i, in0 in enumerate(inputs):
|
||||
for j, in1 in enumerate(inputs):
|
||||
if i == j:
|
||||
continue
|
||||
if in0 + in1 == 2020:
|
||||
to_yield = tuple(sorted([in0, in1]))
|
||||
if to_yield not in found:
|
||||
yield to_yield
|
||||
found.append(to_yield)
|
||||
|
||||
|
||||
def _find_2020_triplets(
|
||||
inputs: typing.List[int],
|
||||
) -> typing.Generator[typing.Tuple[int, ...], None, None]:
|
||||
found = []
|
||||
|
||||
for i, in0 in enumerate(inputs):
|
||||
for j, in1 in enumerate(inputs):
|
||||
for k, in2 in enumerate(inputs):
|
||||
if i == j or i == k:
|
||||
continue
|
||||
if in0 + in1 + in2 == 2020:
|
||||
to_yield = tuple(sorted([in0, in1, in2]))
|
||||
if to_yield not in found:
|
||||
yield to_yield
|
||||
found.append(to_yield)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
@@ -1,53 +0,0 @@
|
||||
import sys
|
||||
|
||||
|
||||
def main() -> int:
|
||||
n_valid = 0
|
||||
total = 0
|
||||
|
||||
for pol_pas in [PolicyPassword.fromstring(s) for s in sys.stdin.readlines(False)]:
|
||||
if pol_pas.is_valid():
|
||||
n_valid += 1
|
||||
total += 1
|
||||
|
||||
print(f"{n_valid}/{total} valid")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
class Policy:
|
||||
def __init__(self, char: str, pos1: int, pos2: int):
|
||||
self.char = char
|
||||
self.pos1 = pos1
|
||||
self.pos2 = pos2
|
||||
|
||||
@classmethod
|
||||
def fromstring(cls, input_string) -> "Policy":
|
||||
parts = [s.strip() for s in input_string.split(" ")][:2]
|
||||
pos = [int(s) for s in parts[0].split("-")][:2]
|
||||
return cls(parts[1], pos[0], pos[1])
|
||||
|
||||
def is_valid(self, password: str) -> bool:
|
||||
matches = 0
|
||||
for pos in (self.pos1 - 1, self.pos2 - 1):
|
||||
if password[pos] == self.char:
|
||||
matches += 1
|
||||
return matches == 1
|
||||
|
||||
|
||||
class PolicyPassword:
|
||||
def __init__(self, policy: "Policy", password: str):
|
||||
self.policy = policy
|
||||
self.password = password
|
||||
|
||||
@classmethod
|
||||
def fromstring(cls, input_string: str) -> "PolicyPassword":
|
||||
parts = [s.strip() for s in input_string.split(":")][:2]
|
||||
return cls(Policy.fromstring(parts[0]), parts[1])
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
return self.policy.is_valid(self.password)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
@@ -1,52 +0,0 @@
|
||||
import functools
|
||||
import sys
|
||||
import typing
|
||||
|
||||
|
||||
class Loc(typing.NamedTuple):
|
||||
x: int
|
||||
y: int
|
||||
|
||||
|
||||
def main() -> int:
|
||||
forest_frame = [list(line.strip()) for line in sys.stdin.readlines()]
|
||||
frame_width = len(forest_frame[0])
|
||||
frame_height = len(forest_frame)
|
||||
|
||||
all_trees_encountered = []
|
||||
|
||||
for slope in [
|
||||
Loc(x=1, y=1),
|
||||
Loc(x=3, y=1),
|
||||
Loc(x=5, y=1),
|
||||
Loc(x=7, y=1),
|
||||
Loc(x=1, y=2),
|
||||
]:
|
||||
loc = Loc(x=0, y=0)
|
||||
|
||||
trees_encountered = 0
|
||||
|
||||
while loc.y <= (frame_height - 1):
|
||||
at_loc = forest_frame[loc.y][loc.x]
|
||||
if at_loc == "#":
|
||||
trees_encountered += 1
|
||||
|
||||
next_x = (loc.x + slope.x) % frame_width
|
||||
next_y = loc.y + slope.y
|
||||
next_loc = Loc(x=next_x, y=next_y)
|
||||
loc = next_loc
|
||||
|
||||
print(
|
||||
f"(slope right={slope.x} down={slope.y}) trees encountered: {trees_encountered}"
|
||||
)
|
||||
all_trees_encountered.append(trees_encountered)
|
||||
|
||||
trees_encountered_product = functools.reduce(
|
||||
lambda x, y: x * y, all_trees_encountered
|
||||
)
|
||||
print(f"trees encountered product: {trees_encountered_product}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
@@ -1,115 +0,0 @@
|
||||
import sys
|
||||
import typing
|
||||
|
||||
|
||||
def main() -> int:
|
||||
checked = []
|
||||
for passport in _read_passports(sys.stdin):
|
||||
if passport is None:
|
||||
checked.append(False)
|
||||
continue
|
||||
checked.append(passport.is_valid())
|
||||
|
||||
print(f"total={len(checked)} valid={checked.count(True)}")
|
||||
return 0
|
||||
|
||||
|
||||
NoneString = typing.Optional[str]
|
||||
|
||||
VALID_EYE_COLORS = ("amb", "blu", "brn", "gry", "grn", "hzl", "oth")
|
||||
|
||||
|
||||
class Passport:
|
||||
byr: NoneString = None
|
||||
cid: NoneString = None
|
||||
ecl: NoneString = None
|
||||
eyr: NoneString = None
|
||||
hcl: NoneString = None
|
||||
hgt: NoneString = None
|
||||
iyr: NoneString = None
|
||||
pid: NoneString = None
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
return (
|
||||
self._is_year_in_range(self.byr, range(1920, 2003))
|
||||
and self._is_year_in_range(self.iyr, range(2010, 2021))
|
||||
and self._is_year_in_range(self.eyr, range(2020, 2031))
|
||||
and self._has_valid_height()
|
||||
and self._has_valid_hair_color()
|
||||
and self.ecl in VALID_EYE_COLORS
|
||||
and self._has_valid_passport_id()
|
||||
)
|
||||
|
||||
def _has_valid_height(self) -> bool:
|
||||
if self.hgt is None:
|
||||
return False
|
||||
|
||||
height_value = 0
|
||||
|
||||
height_value_string = self.hgt.replace("cm", "").replace("in", "")
|
||||
if not height_value_string.isdigit():
|
||||
return False
|
||||
|
||||
height = int(height_value_string)
|
||||
|
||||
if self.hgt.endswith("cm") and height in range(150, 194):
|
||||
return True
|
||||
elif self.hgt.endswith("in") and height in range(59, 77):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _has_valid_hair_color(self) -> bool:
|
||||
if self.hcl is None:
|
||||
return False
|
||||
|
||||
if not self.hcl.startswith("#"):
|
||||
return False
|
||||
|
||||
hair_value = self.hcl.replace("#", "")
|
||||
if len(hair_value) != 6:
|
||||
return False
|
||||
|
||||
try:
|
||||
_ = int(hair_value, 16)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def _has_valid_passport_id(self) -> bool:
|
||||
return (
|
||||
self.pid is not None
|
||||
and len(self.pid) == 9
|
||||
and all([s.isdigit() for s in list(self.pid)])
|
||||
)
|
||||
|
||||
def _is_year_in_range(self, value: NoneString, yr_range: range) -> bool:
|
||||
if value is None:
|
||||
return False
|
||||
if len(value) != 4:
|
||||
return False
|
||||
if not value.isdigit():
|
||||
return False
|
||||
return int(value) in yr_range
|
||||
|
||||
|
||||
def _read_passports(
|
||||
instream: typing.TextIO,
|
||||
) -> typing.Generator[typing.Optional[Passport], None, None]:
|
||||
cur = Passport()
|
||||
|
||||
for i, line in enumerate(instream):
|
||||
line = line.strip()
|
||||
if line == "":
|
||||
yield cur
|
||||
cur = Passport()
|
||||
|
||||
for pair in line.split():
|
||||
attr, value = pair.split(":", 1)
|
||||
setattr(cur, attr, value)
|
||||
|
||||
yield cur
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
@@ -1,77 +0,0 @@
|
||||
import sys
|
||||
import typing
|
||||
|
||||
|
||||
def main() -> int:
|
||||
highest_seat = 0
|
||||
|
||||
taken_seats = set()
|
||||
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
if line == "":
|
||||
continue
|
||||
|
||||
seat = _locate_seat(line)
|
||||
|
||||
if seat.number > highest_seat:
|
||||
highest_seat = seat.number
|
||||
|
||||
taken_seats.add(seat.number)
|
||||
|
||||
print(f"bp={line} row={seat.row} column={seat.col} seat={seat.number}")
|
||||
|
||||
seat_number: typing.Optional[int] = None
|
||||
n_found = 0
|
||||
|
||||
for candidate_seat in range(0, (127 * 8) + 1):
|
||||
if candidate_seat in taken_seats:
|
||||
continue
|
||||
|
||||
if (candidate_seat - 1) in taken_seats and (candidate_seat + 1) in taken_seats:
|
||||
seat_number = candidate_seat
|
||||
n_found += 1
|
||||
|
||||
print(f"highest_seat={highest_seat} seat_number={seat_number} n_found={n_found}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
class Seat:
|
||||
row: int
|
||||
column: int
|
||||
|
||||
def __init__(self, row: int = 0, col: int = 0):
|
||||
self.row = row
|
||||
self.col = col
|
||||
|
||||
@property
|
||||
def number(self) -> int:
|
||||
return (self.row * 8) + self.col
|
||||
|
||||
|
||||
def _locate_seat(bp: str) -> Seat:
|
||||
rows = list(range(0, 128))
|
||||
cols = list(range(0, 8))
|
||||
|
||||
row_part = list(bp[:7])
|
||||
col_part = list(bp[7:])
|
||||
|
||||
return Seat(
|
||||
row=_bisect(rows, [{"F": 0, "B": 1}[s] for s in row_part]),
|
||||
col=_bisect(cols, [{"L": 0, "R": 1}[s] for s in col_part]),
|
||||
)
|
||||
|
||||
|
||||
def _bisect(initial_selection: typing.List[int], bisections: typing.List[int]) -> int:
|
||||
selection = initial_selection[:]
|
||||
|
||||
for bisection in bisections:
|
||||
halfway = int(len(selection) / 2)
|
||||
selection = [selection[:halfway], selection[halfway:]][bisection]
|
||||
|
||||
return selection[0]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
@@ -1,3 +0,0 @@
|
||||
BFFFBBFRRR
|
||||
FFFBBBFRRR
|
||||
BBFFBBFRLL
|
@@ -1,3 +0,0 @@
|
||||
bp=BFFFBBFRRR row=70 column=7 seat=567
|
||||
bp=FFFBBBFRRR row=14 column=7 seat=119
|
||||
bp=BBFFBBFRLL row=102 column=4 seat=820
|
@@ -1,21 +0,0 @@
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from solution import main
|
||||
|
||||
|
||||
HERE = Path(__file__).absolute().parent
|
||||
|
||||
|
||||
def test_solution(capsys):
|
||||
with (HERE / "test-input").open() as infile:
|
||||
sys.stdin = infile
|
||||
main()
|
||||
|
||||
expected_output = (HERE / "test-output").read_text().splitlines()
|
||||
assert expected_output == [
|
||||
l for l in capsys.readouterr().out.splitlines() if l.startswith("counts_sum=")
|
||||
]
|
@@ -1,42 +0,0 @@
|
||||
import sys
|
||||
import typing
|
||||
|
||||
|
||||
def main() -> int:
|
||||
counts_sum = sum([c for c in _iter_group_counts(sys.stdin)])
|
||||
print(f"counts_sum={counts_sum}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _iter_group_counts(instream: typing.TextIO) -> typing.Generator[int, None, None]:
|
||||
for i, group in enumerate(_iter_groups(instream)):
|
||||
answers = set(list(group[0]))
|
||||
print(f"i={i} initial={answers}")
|
||||
|
||||
for answers_text in group[1:]:
|
||||
to_add = set(list(answers_text))
|
||||
answers = answers.intersection(set(list(answers_text)))
|
||||
print(f"i={i} added={to_add} result={answers}")
|
||||
|
||||
print(f"i={i} final={answers} n={len(answers)}")
|
||||
yield len(answers)
|
||||
|
||||
|
||||
def _iter_groups(instream):
|
||||
cur_group = []
|
||||
|
||||
for line in instream:
|
||||
line = line.strip()
|
||||
if line == "":
|
||||
yield cur_group
|
||||
cur_group = []
|
||||
continue
|
||||
|
||||
cur_group.append(line)
|
||||
|
||||
yield cur_group
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
@@ -1,15 +0,0 @@
|
||||
abc
|
||||
|
||||
a
|
||||
b
|
||||
c
|
||||
|
||||
ab
|
||||
ac
|
||||
|
||||
a
|
||||
a
|
||||
a
|
||||
a
|
||||
|
||||
b
|
@@ -1 +0,0 @@
|
||||
counts_sum=6
|
@@ -1,21 +0,0 @@
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from solution import main
|
||||
|
||||
|
||||
HERE = Path(__file__).absolute().parent
|
||||
|
||||
|
||||
def test_solution(capsys):
|
||||
with (HERE / "test-input").open() as infile:
|
||||
sys.stdin = infile
|
||||
main()
|
||||
|
||||
expected_output = (HERE / "test-output").read_text().splitlines()
|
||||
assert expected_output == [
|
||||
l for l in capsys.readouterr().out.splitlines() if l.startswith("counts_sum")
|
||||
]
|
4
argh/README.md
Normal file
4
argh/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# argh command line parser
|
||||
|
||||
> NOTE: much of this is lifted from
|
||||
> https://blog.gopheracademy.com/advent-2014/parsers-lexers/
|
34
argh/argh.go
Normal file
34
argh/argh.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package argh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
tracingEnabled = os.Getenv("ARGH_TRACING") == "enabled"
|
||||
traceLogger *log.Logger
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !tracingEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
traceLogger = log.New(os.Stderr, "ARGH TRACING: ", 0)
|
||||
}
|
||||
|
||||
func tracef(format string, v ...any) {
|
||||
if !tracingEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
if _, file, line, ok := runtime.Caller(1); ok {
|
||||
format = fmt.Sprintf("%v:%v ", filepath.Base(file), line) + format
|
||||
}
|
||||
|
||||
traceLogger.Printf(format, v...)
|
||||
}
|
46
argh/cmd/argh-example/main.go
Normal file
46
argh/cmd/argh-example/main.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"git.meatballhat.com/x/box-o-sand/argh"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
func main() {
|
||||
asJSON := os.Getenv("ARGH_OUTPUT_JSON") == "enabled"
|
||||
|
||||
log.SetFlags(0)
|
||||
|
||||
pt, err := argh.ParseArgs(os.Args, argh.NewParserConfig(
|
||||
&argh.CommandConfig{
|
||||
NValue: argh.OneOrMoreValue,
|
||||
ValueNames: []string{"topping"},
|
||||
Flags: &argh.Flags{
|
||||
Automatic: true,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ast := argh.NewQuerier(pt.Nodes).AST()
|
||||
|
||||
if asJSON {
|
||||
b, err := json.MarshalIndent(ast, "", " ")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(b))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
spew.Dump(ast)
|
||||
}
|
12
argh/go.mod
Normal file
12
argh/go.mod
Normal file
@@ -0,0 +1,12 @@
|
||||
module git.meatballhat.com/x/box-o-sand/argh
|
||||
|
||||
go 1.18
|
||||
|
||||
require github.com/pkg/errors v0.9.1
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.7.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
12
argh/go.sum
Normal file
12
argh/go.sum
Normal file
@@ -0,0 +1,12 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
46
argh/node.go
Normal file
46
argh/node.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package argh
|
||||
|
||||
type Node interface{}
|
||||
|
||||
type TypedNode struct {
|
||||
Type string
|
||||
Node Node
|
||||
}
|
||||
|
||||
type PassthroughArgs struct {
|
||||
Nodes []Node
|
||||
}
|
||||
|
||||
type CompoundShortFlag struct {
|
||||
Nodes []Node
|
||||
}
|
||||
|
||||
type Ident struct {
|
||||
Literal string
|
||||
}
|
||||
|
||||
type BadArg struct {
|
||||
Literal string
|
||||
From Pos
|
||||
To Pos
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
Name string
|
||||
Values map[string]string
|
||||
Nodes []Node
|
||||
}
|
||||
|
||||
type Flag struct {
|
||||
Name string
|
||||
Values map[string]string
|
||||
Nodes []Node
|
||||
}
|
||||
|
||||
type StdinFlag struct{}
|
||||
|
||||
type StopFlag struct{}
|
||||
|
||||
type ArgDelimiter struct{}
|
||||
|
||||
type Assign struct{}
|
26
argh/nvalue_string.go
Normal file
26
argh/nvalue_string.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Code generated by "stringer -type NValue"; DO NOT EDIT.
|
||||
|
||||
package argh
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[OneOrMoreValue - -2]
|
||||
_ = x[ZeroOrMoreValue - -1]
|
||||
_ = x[ZeroValue-0]
|
||||
}
|
||||
|
||||
const _NValue_name = "OneOrMoreValueZeroOrMoreValueZeroValue"
|
||||
|
||||
var _NValue_index = [...]uint8{0, 14, 29, 38}
|
||||
|
||||
func (i NValue) String() string {
|
||||
i -= -2
|
||||
if i < 0 || i >= NValue(len(_NValue_index)-1) {
|
||||
return "NValue(" + strconv.FormatInt(int64(i+-2), 10) + ")"
|
||||
}
|
||||
return _NValue_name[_NValue_index[i]:_NValue_index[i+1]]
|
||||
}
|
346
argh/parser.go
Normal file
346
argh/parser.go
Normal file
@@ -0,0 +1,346 @@
|
||||
package argh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
s *Scanner
|
||||
|
||||
cfg *ParserConfig
|
||||
|
||||
errors ParserErrorList
|
||||
|
||||
tok Token
|
||||
lit string
|
||||
pos Pos
|
||||
|
||||
buffered bool
|
||||
}
|
||||
|
||||
type ParseTree struct {
|
||||
Nodes []Node `json:"nodes"`
|
||||
}
|
||||
|
||||
func ParseArgs(args []string, pCfg *ParserConfig) (*ParseTree, error) {
|
||||
p := &parser{}
|
||||
p.init(
|
||||
strings.NewReader(strings.Join(args, string(nul))),
|
||||
pCfg,
|
||||
)
|
||||
|
||||
tracef("ParseArgs(...) parser=%+#v", p)
|
||||
|
||||
return p.parseArgs()
|
||||
}
|
||||
|
||||
func (p *parser) addError(msg string) {
|
||||
p.errors.Add(Position{Column: int(p.pos)}, msg)
|
||||
}
|
||||
|
||||
func (p *parser) init(r io.Reader, pCfg *ParserConfig) {
|
||||
p.errors = ParserErrorList{}
|
||||
|
||||
if pCfg == nil {
|
||||
pCfg = POSIXyParserConfig
|
||||
}
|
||||
|
||||
p.cfg = pCfg
|
||||
|
||||
p.s = NewScanner(r, pCfg.ScannerConfig)
|
||||
|
||||
p.next()
|
||||
}
|
||||
|
||||
func (p *parser) parseArgs() (*ParseTree, error) {
|
||||
if p.errors.Len() != 0 {
|
||||
tracef("parseArgs() bailing due to initial error")
|
||||
return nil, p.errors.Err()
|
||||
}
|
||||
|
||||
tracef("parseArgs() parsing %q as program command; cfg=%+#v", p.lit, p.cfg.Prog)
|
||||
prog := p.parseCommand(&p.cfg.Prog)
|
||||
|
||||
tracef("parseArgs() top level node is %T", prog)
|
||||
|
||||
nodes := []Node{prog}
|
||||
if v := p.parsePassthrough(); v != nil {
|
||||
tracef("parseArgs() appending passthrough argument %v", v)
|
||||
nodes = append(nodes, v)
|
||||
}
|
||||
|
||||
tracef("parseArgs() returning ParseTree")
|
||||
|
||||
return &ParseTree{Nodes: nodes}, p.errors.Err()
|
||||
}
|
||||
|
||||
func (p *parser) next() {
|
||||
tracef("next() before scan: %v %q %v", p.tok, p.lit, p.pos)
|
||||
|
||||
p.tok, p.lit, p.pos = p.s.Scan()
|
||||
|
||||
tracef("next() after scan: %v %q %v", p.tok, p.lit, p.pos)
|
||||
}
|
||||
|
||||
func (p *parser) parseCommand(cCfg *CommandConfig) Node {
|
||||
tracef("parseCommand(%+#v)", cCfg)
|
||||
|
||||
node := &Command{
|
||||
Name: p.lit,
|
||||
}
|
||||
values := map[string]string{}
|
||||
nodes := []Node{}
|
||||
|
||||
identIndex := 0
|
||||
|
||||
for i := 0; p.tok != EOL; i++ {
|
||||
if !p.buffered {
|
||||
tracef("parseCommand(...) buffered=false; scanning next")
|
||||
p.next()
|
||||
}
|
||||
|
||||
p.buffered = false
|
||||
|
||||
tracef("parseCommand(...) for=%d values=%+#v", i, values)
|
||||
tracef("parseCommand(...) for=%d nodes=%+#v", i, nodes)
|
||||
tracef("parseCommand(...) for=%d tok=%s lit=%q pos=%v", i, p.tok, p.lit, p.pos)
|
||||
|
||||
tracef("parseCommand(...) cCfg=%+#v", cCfg)
|
||||
|
||||
if subCfg, ok := cCfg.GetCommandConfig(p.lit); ok {
|
||||
subCommand := p.lit
|
||||
|
||||
nodes = append(nodes, p.parseCommand(&subCfg))
|
||||
|
||||
tracef("parseCommand(...) breaking after sub-command=%v", subCommand)
|
||||
break
|
||||
}
|
||||
|
||||
switch p.tok {
|
||||
case ARG_DELIMITER:
|
||||
tracef("parseCommand(...) handling %s", p.tok)
|
||||
|
||||
nodes = append(nodes, &ArgDelimiter{})
|
||||
|
||||
continue
|
||||
case IDENT, STDIN_FLAG:
|
||||
tracef("parseCommand(...) handling %s", p.tok)
|
||||
|
||||
if cCfg.NValue.Contains(identIndex) {
|
||||
name := fmt.Sprintf("%d", identIndex)
|
||||
|
||||
tracef("parseCommand(...) checking for name of identIndex=%d", identIndex)
|
||||
|
||||
if len(cCfg.ValueNames) > identIndex {
|
||||
name = cCfg.ValueNames[identIndex]
|
||||
tracef("parseCommand(...) setting name=%s from config value names", name)
|
||||
} else if len(cCfg.ValueNames) == 1 && (cCfg.NValue == OneOrMoreValue || cCfg.NValue == ZeroOrMoreValue) {
|
||||
name = fmt.Sprintf("%s.%d", cCfg.ValueNames[0], identIndex)
|
||||
tracef("parseCommand(...) setting name=%s from repeating value name", name)
|
||||
}
|
||||
|
||||
values[name] = p.lit
|
||||
}
|
||||
|
||||
if p.tok == STDIN_FLAG {
|
||||
nodes = append(nodes, &StdinFlag{})
|
||||
} else {
|
||||
nodes = append(nodes, &Ident{Literal: p.lit})
|
||||
}
|
||||
|
||||
identIndex++
|
||||
case LONG_FLAG, SHORT_FLAG, COMPOUND_SHORT_FLAG:
|
||||
tok := p.tok
|
||||
|
||||
flagNode := p.parseFlag(cCfg.Flags)
|
||||
|
||||
tracef("parseCommand(...) appending %s node=%+#v", tok, flagNode)
|
||||
|
||||
nodes = append(nodes, flagNode)
|
||||
case ASSIGN:
|
||||
tracef("parseCommand(...) error on bare %s", p.tok)
|
||||
|
||||
p.addError("invalid bare assignment")
|
||||
|
||||
break
|
||||
default:
|
||||
tracef("parseCommand(...) breaking on %s", p.tok)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(nodes) > 0 {
|
||||
node.Nodes = nodes
|
||||
}
|
||||
|
||||
if len(values) > 0 {
|
||||
node.Values = values
|
||||
}
|
||||
|
||||
tracef("parseCommand(...) returning node=%+#v", node)
|
||||
return node
|
||||
}
|
||||
|
||||
func (p *parser) parseIdent() Node {
|
||||
node := &Ident{Literal: p.lit}
|
||||
return node
|
||||
}
|
||||
|
||||
func (p *parser) parseFlag(flags *Flags) Node {
|
||||
switch p.tok {
|
||||
case SHORT_FLAG:
|
||||
tracef("parseFlag(...) parsing short flag with config=%+#v", flags)
|
||||
return p.parseShortFlag(flags)
|
||||
case LONG_FLAG:
|
||||
tracef("parseFlag(...) parsing long flag with config=%+#v", flags)
|
||||
return p.parseLongFlag(flags)
|
||||
case COMPOUND_SHORT_FLAG:
|
||||
tracef("parseFlag(...) parsing compound short flag with config=%+#v", flags)
|
||||
return p.parseCompoundShortFlag(flags)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("token %v cannot be parsed as flag", p.tok))
|
||||
}
|
||||
|
||||
func (p *parser) parseShortFlag(flags *Flags) Node {
|
||||
node := &Flag{Name: string(p.lit[1])}
|
||||
|
||||
flCfg, ok := flags.Get(node.Name)
|
||||
if !ok {
|
||||
p.addError(fmt.Sprintf("unknown flag %q", node.Name))
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
return p.parseConfiguredFlag(node, flCfg)
|
||||
}
|
||||
|
||||
func (p *parser) parseLongFlag(flags *Flags) Node {
|
||||
node := &Flag{Name: string(p.lit[2:])}
|
||||
|
||||
flCfg, ok := flags.Get(node.Name)
|
||||
if !ok {
|
||||
p.addError(fmt.Sprintf("unknown flag %q", node.Name))
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
return p.parseConfiguredFlag(node, flCfg)
|
||||
}
|
||||
|
||||
func (p *parser) parseCompoundShortFlag(flags *Flags) Node {
|
||||
flagNodes := []Node{}
|
||||
|
||||
withoutFlagPrefix := p.lit[1:]
|
||||
|
||||
for i, r := range withoutFlagPrefix {
|
||||
node := &Flag{Name: string(r)}
|
||||
|
||||
if i == len(withoutFlagPrefix)-1 {
|
||||
flCfg, ok := flags.Get(node.Name)
|
||||
if !ok {
|
||||
p.addError(fmt.Sprintf("unknown flag %q", node.Name))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
flagNodes = append(flagNodes, p.parseConfiguredFlag(node, flCfg))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
flagNodes = append(flagNodes, node)
|
||||
}
|
||||
|
||||
return &CompoundShortFlag{Nodes: flagNodes}
|
||||
}
|
||||
|
||||
func (p *parser) parseConfiguredFlag(node *Flag, flCfg FlagConfig) Node {
|
||||
values := map[string]string{}
|
||||
nodes := []Node{}
|
||||
|
||||
identIndex := 0
|
||||
|
||||
for i := 0; p.tok != EOL; i++ {
|
||||
if !flCfg.NValue.Contains(identIndex) {
|
||||
tracef("parseConfiguredFlag(...) identIndex=%d exceeds expected=%v; breaking", identIndex, flCfg.NValue)
|
||||
break
|
||||
}
|
||||
|
||||
p.next()
|
||||
|
||||
switch p.tok {
|
||||
case ARG_DELIMITER:
|
||||
nodes = append(nodes, &ArgDelimiter{})
|
||||
|
||||
continue
|
||||
case ASSIGN:
|
||||
nodes = append(nodes, &Assign{})
|
||||
|
||||
continue
|
||||
case IDENT, STDIN_FLAG:
|
||||
name := fmt.Sprintf("%d", identIndex)
|
||||
|
||||
tracef("parseConfiguredFlag(...) checking for name of identIndex=%d", identIndex)
|
||||
|
||||
if len(flCfg.ValueNames) > identIndex {
|
||||
name = flCfg.ValueNames[identIndex]
|
||||
tracef("parseConfiguredFlag(...) setting name=%s from config value names", name)
|
||||
} else if len(flCfg.ValueNames) == 1 && (flCfg.NValue == OneOrMoreValue || flCfg.NValue == ZeroOrMoreValue) {
|
||||
name = fmt.Sprintf("%s.%d", flCfg.ValueNames[0], identIndex)
|
||||
tracef("parseConfiguredFlag(...) setting name=%s from repeating value name", name)
|
||||
} else {
|
||||
tracef("parseConfiguredFlag(...) setting name=%s", name)
|
||||
}
|
||||
|
||||
values[name] = p.lit
|
||||
|
||||
if p.tok == STDIN_FLAG {
|
||||
nodes = append(nodes, &StdinFlag{})
|
||||
} else {
|
||||
nodes = append(nodes, &Ident{Literal: p.lit})
|
||||
}
|
||||
|
||||
identIndex++
|
||||
default:
|
||||
tracef("parseConfiguredFlag(...) breaking on %s %q %v; setting buffered=true", p.tok, p.lit, p.pos)
|
||||
p.buffered = true
|
||||
|
||||
if len(nodes) > 0 {
|
||||
node.Nodes = nodes
|
||||
}
|
||||
|
||||
if len(values) > 0 {
|
||||
node.Values = values
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
if len(nodes) > 0 {
|
||||
node.Nodes = nodes
|
||||
}
|
||||
|
||||
if len(values) > 0 {
|
||||
node.Values = values
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (p *parser) parsePassthrough() Node {
|
||||
nodes := []Node{}
|
||||
|
||||
for ; p.tok != EOL; p.next() {
|
||||
nodes = append(nodes, &Ident{Literal: p.lit})
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &PassthroughArgs{Nodes: nodes}
|
||||
}
|
146
argh/parser_config.go
Normal file
146
argh/parser_config.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package argh
|
||||
|
||||
const (
|
||||
OneOrMoreValue NValue = -2
|
||||
ZeroOrMoreValue NValue = -1
|
||||
ZeroValue NValue = 0
|
||||
)
|
||||
|
||||
var (
|
||||
POSIXyParserConfig = NewParserConfig(
|
||||
nil,
|
||||
POSIXyScannerConfig,
|
||||
)
|
||||
)
|
||||
|
||||
type NValue int
|
||||
|
||||
func (nv NValue) Contains(i int) bool {
|
||||
tracef("NValue.Contains(%v)", i)
|
||||
|
||||
if i < int(ZeroValue) {
|
||||
return false
|
||||
}
|
||||
|
||||
if nv == OneOrMoreValue || nv == ZeroOrMoreValue {
|
||||
return true
|
||||
}
|
||||
|
||||
return int(nv) > i
|
||||
}
|
||||
|
||||
type ParserConfig struct {
|
||||
Prog CommandConfig
|
||||
|
||||
ScannerConfig *ScannerConfig
|
||||
}
|
||||
|
||||
func NewParserConfig(prog *CommandConfig, sCfg *ScannerConfig) *ParserConfig {
|
||||
if sCfg == nil {
|
||||
sCfg = POSIXyScannerConfig
|
||||
}
|
||||
|
||||
if prog == nil {
|
||||
prog = &CommandConfig{}
|
||||
}
|
||||
|
||||
prog.init()
|
||||
|
||||
pCfg := &ParserConfig{
|
||||
Prog: *prog,
|
||||
ScannerConfig: sCfg,
|
||||
}
|
||||
|
||||
return pCfg
|
||||
}
|
||||
|
||||
type CommandConfig struct {
|
||||
NValue NValue
|
||||
ValueNames []string
|
||||
Flags *Flags
|
||||
Commands *Commands
|
||||
}
|
||||
|
||||
func (cCfg *CommandConfig) init() {
|
||||
if cCfg.ValueNames == nil {
|
||||
cCfg.ValueNames = []string{}
|
||||
}
|
||||
|
||||
if cCfg.Flags == nil {
|
||||
cCfg.Flags = &Flags{}
|
||||
}
|
||||
|
||||
if cCfg.Commands == nil {
|
||||
cCfg.Commands = &Commands{}
|
||||
}
|
||||
}
|
||||
|
||||
func (cCfg *CommandConfig) GetCommandConfig(name string) (CommandConfig, bool) {
|
||||
tracef("CommandConfig.GetCommandConfig(%q)", name)
|
||||
|
||||
if cCfg.Commands == nil {
|
||||
cCfg.Commands = &Commands{Map: map[string]CommandConfig{}}
|
||||
}
|
||||
|
||||
return cCfg.Commands.Get(name)
|
||||
}
|
||||
|
||||
func (cCfg *CommandConfig) GetFlagConfig(name string) (FlagConfig, bool) {
|
||||
tracef("CommandConfig.GetFlagConfig(%q)", name)
|
||||
|
||||
if cCfg.Flags == nil {
|
||||
cCfg.Flags = &Flags{Map: map[string]FlagConfig{}}
|
||||
}
|
||||
|
||||
return cCfg.Flags.Get(name)
|
||||
}
|
||||
|
||||
type FlagConfig struct {
|
||||
NValue NValue
|
||||
Persist bool
|
||||
ValueNames []string
|
||||
}
|
||||
|
||||
type Flags struct {
|
||||
Parent *Flags
|
||||
Map map[string]FlagConfig
|
||||
|
||||
Automatic bool
|
||||
}
|
||||
|
||||
func (fl *Flags) Get(name string) (FlagConfig, bool) {
|
||||
tracef("Flags.Get(%q)", name)
|
||||
|
||||
if fl.Map == nil {
|
||||
fl.Map = map[string]FlagConfig{}
|
||||
}
|
||||
|
||||
flCfg, ok := fl.Map[name]
|
||||
if !ok {
|
||||
if fl.Automatic {
|
||||
return FlagConfig{}, true
|
||||
}
|
||||
|
||||
if fl.Parent != nil {
|
||||
flCfg, ok = fl.Parent.Get(name)
|
||||
return flCfg, ok && flCfg.Persist
|
||||
}
|
||||
}
|
||||
|
||||
return flCfg, ok
|
||||
}
|
||||
|
||||
type Commands struct {
|
||||
Map map[string]CommandConfig
|
||||
}
|
||||
|
||||
func (cmd *Commands) Get(name string) (CommandConfig, bool) {
|
||||
tracef("Commands.Get(%q)", name)
|
||||
|
||||
if cmd.Map == nil {
|
||||
cmd.Map = map[string]CommandConfig{}
|
||||
}
|
||||
|
||||
cmdCfg, ok := cmd.Map[name]
|
||||
return cmdCfg, ok
|
||||
}
|
88
argh/parser_error.go
Normal file
88
argh/parser_error.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package argh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// ParserError is largely borrowed from go/scanner.Error
|
||||
type ParserError struct {
|
||||
Pos Position
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e ParserError) Error() string {
|
||||
if e.Pos.IsValid() {
|
||||
return e.Pos.String() + ":" + e.Msg
|
||||
}
|
||||
|
||||
return e.Msg
|
||||
}
|
||||
|
||||
// ParserErrorList is largely borrowed from go/scanner.ErrorList
|
||||
type ParserErrorList []*ParserError
|
||||
|
||||
func (el *ParserErrorList) Add(pos Position, msg string) {
|
||||
*el = append(*el, &ParserError{Pos: pos, Msg: msg})
|
||||
}
|
||||
|
||||
func (el *ParserErrorList) Reset() { *el = (*el)[0:0] }
|
||||
|
||||
func (el ParserErrorList) Len() int { return len(el) }
|
||||
|
||||
func (el ParserErrorList) Swap(i, j int) { el[i], el[j] = el[j], el[i] }
|
||||
|
||||
func (el ParserErrorList) Less(i, j int) bool {
|
||||
e := &el[i].Pos
|
||||
f := &el[j].Pos
|
||||
|
||||
if e.Column != f.Column {
|
||||
return e.Column < f.Column
|
||||
}
|
||||
|
||||
return el[i].Msg < el[j].Msg
|
||||
}
|
||||
|
||||
func (el ParserErrorList) Sort() {
|
||||
sort.Sort(el)
|
||||
}
|
||||
|
||||
func (el ParserErrorList) Error() string {
|
||||
switch len(el) {
|
||||
case 0:
|
||||
return "no errors"
|
||||
case 1:
|
||||
return el[0].Error()
|
||||
}
|
||||
return fmt.Sprintf("%s (and %d more errors)", el[0], len(el)-1)
|
||||
}
|
||||
|
||||
func (el ParserErrorList) Err() error {
|
||||
if len(el) == 0 {
|
||||
return nil
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
func (el ParserErrorList) Is(other error) bool {
|
||||
if _, ok := other.(ParserErrorList); ok {
|
||||
return el.Error() == other.Error()
|
||||
}
|
||||
|
||||
if v, ok := other.(*ParserErrorList); ok {
|
||||
return el.Error() == (*v).Error()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func PrintParserError(w io.Writer, err error) {
|
||||
if list, ok := err.(ParserErrorList); ok {
|
||||
for _, e := range list {
|
||||
fmt.Fprintf(w, "%s\n", e)
|
||||
}
|
||||
} else if err != nil {
|
||||
fmt.Fprintf(w, "%s\n", err)
|
||||
}
|
||||
}
|
1028
argh/parser_test.go
Normal file
1028
argh/parser_test.go
Normal file
File diff suppressed because it is too large
Load Diff
94
argh/querier.go
Normal file
94
argh/querier.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package argh
|
||||
|
||||
type Querier interface {
|
||||
Program() (*Command, bool)
|
||||
AST() []Node
|
||||
}
|
||||
|
||||
func NewQuerier(nodes []Node) Querier {
|
||||
return &defaultQuerier{nodes: nodes}
|
||||
}
|
||||
|
||||
type defaultQuerier struct {
|
||||
nodes []Node
|
||||
}
|
||||
|
||||
func (dq *defaultQuerier) Program() (*Command, bool) {
|
||||
if len(dq.nodes) == 0 {
|
||||
tracef("Program nodes are empty")
|
||||
return nil, false
|
||||
}
|
||||
|
||||
tracef("Program node[0] is %T", dq.nodes[0])
|
||||
|
||||
v, ok := dq.nodes[0].(*Command)
|
||||
if ok && v.Name == "" {
|
||||
return v, false
|
||||
}
|
||||
|
||||
return v, ok
|
||||
}
|
||||
|
||||
func (dq *defaultQuerier) AST() []Node {
|
||||
ret := []Node{}
|
||||
|
||||
for i, node := range dq.nodes {
|
||||
tracef("AST i=%d node type=%T", i, node)
|
||||
|
||||
if _, ok := node.(*ArgDelimiter); ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := node.(*StopFlag); ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if v, ok := node.(*CompoundShortFlag); ok {
|
||||
if v.Nodes != nil {
|
||||
ret = append(ret, NewQuerier(v.Nodes).AST()...)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if v, ok := node.(*Command); ok {
|
||||
astNodes := NewQuerier(v.Nodes).AST()
|
||||
|
||||
if len(astNodes) == 0 {
|
||||
astNodes = nil
|
||||
}
|
||||
|
||||
ret = append(
|
||||
ret,
|
||||
&Command{
|
||||
Name: v.Name,
|
||||
Values: v.Values,
|
||||
Nodes: astNodes,
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if v, ok := node.(*Flag); ok {
|
||||
astNodes := NewQuerier(v.Nodes).AST()
|
||||
|
||||
if len(astNodes) == 0 {
|
||||
astNodes = nil
|
||||
}
|
||||
|
||||
ret = append(
|
||||
ret,
|
||||
&Flag{
|
||||
Name: v.Name,
|
||||
Values: v.Values,
|
||||
Nodes: astNodes,
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, node)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
60
argh/querier_test.go
Normal file
60
argh/querier_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package argh_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.meatballhat.com/x/box-o-sand/argh"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestQuerier_Program(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
args []string
|
||||
cfg *argh.ParserConfig
|
||||
exp string
|
||||
expOK bool
|
||||
}{
|
||||
{
|
||||
name: "typical",
|
||||
args: []string{"pizzas", "ahoy", "--treatsa", "fun"},
|
||||
cfg: &argh.ParserConfig{
|
||||
Prog: argh.CommandConfig{
|
||||
Commands: &argh.Commands{
|
||||
Map: map[string]argh.CommandConfig{
|
||||
"ahoy": argh.CommandConfig{
|
||||
Flags: &argh.Flags{
|
||||
Map: map[string]argh.FlagConfig{
|
||||
"treatsa": argh.FlagConfig{NValue: 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
exp: "pizzas",
|
||||
expOK: true,
|
||||
},
|
||||
{
|
||||
name: "minimal",
|
||||
args: []string{"pizzas"},
|
||||
exp: "pizzas",
|
||||
expOK: true,
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
args: []string{},
|
||||
expOK: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(ct *testing.T) {
|
||||
pt, err := argh.ParseArgs(tc.args, tc.cfg)
|
||||
require.Nil(ct, err)
|
||||
|
||||
prog, ok := argh.NewQuerier(pt.Nodes).Program()
|
||||
require.Equal(ct, tc.expOK, ok)
|
||||
require.Equal(ct, tc.exp, prog.Name)
|
||||
})
|
||||
}
|
||||
}
|
159
argh/scanner.go
Normal file
159
argh/scanner.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package argh
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type Scanner struct {
|
||||
r *bufio.Reader
|
||||
i int
|
||||
cfg *ScannerConfig
|
||||
}
|
||||
|
||||
func NewScanner(r io.Reader, cfg *ScannerConfig) *Scanner {
|
||||
if cfg == nil {
|
||||
cfg = POSIXyScannerConfig
|
||||
}
|
||||
|
||||
return &Scanner{
|
||||
r: bufio.NewReader(r),
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scanner) Scan() (Token, string, Pos) {
|
||||
ch, pos := s.read()
|
||||
|
||||
if s.cfg.IsBlankspace(ch) {
|
||||
_ = s.unread()
|
||||
return s.scanBlankspace()
|
||||
}
|
||||
|
||||
if s.cfg.IsAssignmentOperator(ch) {
|
||||
return ASSIGN, string(ch), pos
|
||||
}
|
||||
|
||||
if s.cfg.IsMultiValueDelim(ch) {
|
||||
return MULTI_VALUE_DELIMITER, string(ch), pos
|
||||
}
|
||||
|
||||
if ch == eol {
|
||||
return EOL, "", pos
|
||||
}
|
||||
|
||||
if ch == nul {
|
||||
return ARG_DELIMITER, string(ch), pos
|
||||
}
|
||||
|
||||
if unicode.IsGraphic(ch) {
|
||||
_ = s.unread()
|
||||
return s.scanArg()
|
||||
}
|
||||
|
||||
return ILLEGAL, string(ch), pos
|
||||
}
|
||||
|
||||
func (s *Scanner) read() (rune, Pos) {
|
||||
ch, _, err := s.r.ReadRune()
|
||||
s.i++
|
||||
|
||||
if errors.Is(err, io.EOF) {
|
||||
return eol, Pos(s.i)
|
||||
} else if err != nil {
|
||||
log.Printf("unknown scanner error=%+v", err)
|
||||
return eol, Pos(s.i)
|
||||
}
|
||||
|
||||
return ch, Pos(s.i)
|
||||
}
|
||||
|
||||
func (s *Scanner) unread() Pos {
|
||||
_ = s.r.UnreadRune()
|
||||
s.i--
|
||||
return Pos(s.i)
|
||||
}
|
||||
|
||||
func (s *Scanner) scanBlankspace() (Token, string, Pos) {
|
||||
buf := &bytes.Buffer{}
|
||||
ch, pos := s.read()
|
||||
buf.WriteRune(ch)
|
||||
|
||||
for {
|
||||
ch, pos = s.read()
|
||||
|
||||
if ch == eol {
|
||||
break
|
||||
} else if !s.cfg.IsBlankspace(ch) {
|
||||
pos = s.unread()
|
||||
break
|
||||
} else {
|
||||
_, _ = buf.WriteRune(ch)
|
||||
}
|
||||
}
|
||||
|
||||
return BS, buf.String(), pos
|
||||
}
|
||||
|
||||
func (s *Scanner) scanArg() (Token, string, Pos) {
|
||||
buf := &bytes.Buffer{}
|
||||
ch, pos := s.read()
|
||||
buf.WriteRune(ch)
|
||||
|
||||
for {
|
||||
ch, pos = s.read()
|
||||
|
||||
if ch == eol || ch == nul || s.cfg.IsAssignmentOperator(ch) || s.cfg.IsMultiValueDelim(ch) {
|
||||
pos = s.unread()
|
||||
break
|
||||
}
|
||||
|
||||
_, _ = buf.WriteRune(ch)
|
||||
}
|
||||
|
||||
str := buf.String()
|
||||
|
||||
if len(str) == 0 {
|
||||
return EMPTY, str, pos
|
||||
}
|
||||
|
||||
ch0 := rune(str[0])
|
||||
|
||||
if len(str) == 1 {
|
||||
if s.cfg.IsFlagPrefix(ch0) {
|
||||
return STDIN_FLAG, str, pos
|
||||
}
|
||||
|
||||
if s.cfg.IsAssignmentOperator(ch0) {
|
||||
return ASSIGN, str, pos
|
||||
}
|
||||
|
||||
return IDENT, str, pos
|
||||
}
|
||||
|
||||
ch1 := rune(str[1])
|
||||
|
||||
if len(str) == 2 {
|
||||
if s.cfg.IsFlagPrefix(ch0) && s.cfg.IsFlagPrefix(ch1) {
|
||||
return STOP_FLAG, str, pos
|
||||
}
|
||||
|
||||
if s.cfg.IsFlagPrefix(ch0) {
|
||||
return SHORT_FLAG, str, pos
|
||||
}
|
||||
}
|
||||
|
||||
if s.cfg.IsFlagPrefix(ch0) {
|
||||
if s.cfg.IsFlagPrefix(ch1) {
|
||||
return LONG_FLAG, str, pos
|
||||
}
|
||||
|
||||
return COMPOUND_SHORT_FLAG, str, pos
|
||||
}
|
||||
|
||||
return IDENT, str, pos
|
||||
}
|
39
argh/scanner_config.go
Normal file
39
argh/scanner_config.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package argh
|
||||
|
||||
var (
|
||||
// POSIXyScannerConfig defines a scanner config that uses '-'
|
||||
// as the flag prefix, which also means that "--" is the "long
|
||||
// flag" prefix, a bare "--" is considered STOP_FLAG, and a
|
||||
// bare "-" is considered STDIN_FLAG.
|
||||
POSIXyScannerConfig = &ScannerConfig{
|
||||
AssignmentOperator: '=',
|
||||
FlagPrefix: '-',
|
||||
MultiValueDelim: ',',
|
||||
}
|
||||
)
|
||||
|
||||
type ScannerConfig struct {
|
||||
AssignmentOperator rune
|
||||
FlagPrefix rune
|
||||
MultiValueDelim rune
|
||||
}
|
||||
|
||||
func (cfg *ScannerConfig) IsFlagPrefix(ch rune) bool {
|
||||
return ch == cfg.FlagPrefix
|
||||
}
|
||||
|
||||
func (cfg *ScannerConfig) IsMultiValueDelim(ch rune) bool {
|
||||
return ch == cfg.MultiValueDelim
|
||||
}
|
||||
|
||||
func (cfg *ScannerConfig) IsAssignmentOperator(ch rune) bool {
|
||||
return ch == cfg.AssignmentOperator
|
||||
}
|
||||
|
||||
func (cfg *ScannerConfig) IsBlankspace(ch rune) bool {
|
||||
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
|
||||
}
|
||||
|
||||
func (cfg *ScannerConfig) IsUnderscore(ch rune) bool {
|
||||
return ch == '_'
|
||||
}
|
53
argh/token.go
Normal file
53
argh/token.go
Normal file
@@ -0,0 +1,53 @@
|
||||
//go:generate stringer -type Token
|
||||
|
||||
package argh
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
ILLEGAL Token = iota
|
||||
EOL
|
||||
EMPTY // ''
|
||||
BS // ' ' '\t' '\n'
|
||||
IDENT // char group without flag prefix: 'some' 'words'
|
||||
ARG_DELIMITER // rune(0)
|
||||
ASSIGN // '='
|
||||
MULTI_VALUE_DELIMITER // ','
|
||||
LONG_FLAG // char group with double flag prefix: '--flag'
|
||||
SHORT_FLAG // single char with single flag prefix: '-f'
|
||||
COMPOUND_SHORT_FLAG // char group with single flag prefix: '-flag'
|
||||
STDIN_FLAG // '-'
|
||||
STOP_FLAG // '--'
|
||||
|
||||
nul = rune(0)
|
||||
eol = rune(-1)
|
||||
)
|
||||
|
||||
type Token int
|
||||
|
||||
// Position is adapted from go/token.Position
|
||||
type Position struct {
|
||||
Column int
|
||||
}
|
||||
|
||||
func (p *Position) IsValid() bool { return p.Column > 0 }
|
||||
|
||||
func (p Position) String() string {
|
||||
s := ""
|
||||
if p.IsValid() {
|
||||
s = fmt.Sprintf("%d", p.Column)
|
||||
}
|
||||
if s == "" {
|
||||
s = "-"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Pos is borrowed from go/token.Pos
|
||||
type Pos int
|
||||
|
||||
const NoPos Pos = 0
|
||||
|
||||
func (p Pos) IsValid() bool {
|
||||
return p != NoPos
|
||||
}
|
35
argh/token_string.go
Normal file
35
argh/token_string.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Code generated by "stringer -type Token"; DO NOT EDIT.
|
||||
|
||||
package argh
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ILLEGAL-0]
|
||||
_ = x[EOL-1]
|
||||
_ = x[EMPTY-2]
|
||||
_ = x[BS-3]
|
||||
_ = x[IDENT-4]
|
||||
_ = x[ARG_DELIMITER-5]
|
||||
_ = x[ASSIGN-6]
|
||||
_ = x[MULTI_VALUE_DELIMITER-7]
|
||||
_ = x[LONG_FLAG-8]
|
||||
_ = x[SHORT_FLAG-9]
|
||||
_ = x[COMPOUND_SHORT_FLAG-10]
|
||||
_ = x[STDIN_FLAG-11]
|
||||
_ = x[STOP_FLAG-12]
|
||||
}
|
||||
|
||||
const _Token_name = "ILLEGALEOLEMPTYBSIDENTARG_DELIMITERASSIGNMULTI_VALUE_DELIMITERLONG_FLAGSHORT_FLAGCOMPOUND_SHORT_FLAGSTDIN_FLAGSTOP_FLAG"
|
||||
|
||||
var _Token_index = [...]uint8{0, 7, 10, 15, 17, 22, 35, 41, 62, 71, 81, 100, 110, 119}
|
||||
|
||||
func (i Token) String() string {
|
||||
if i < 0 || i >= Token(len(_Token_index)-1) {
|
||||
return "Token(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Token_name[_Token_index[i]:_Token_index[i+1]]
|
||||
}
|
207
hyrule/README.md
Normal file
207
hyrule/README.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# Hyrule
|
||||
|
||||
A card game for 2-7 players.
|
||||
|
||||
## requirements
|
||||
|
||||
Use a standard card playing deck, keeping Jokers. Shuffle however
|
||||
you like.
|
||||
|
||||
## dealing
|
||||
|
||||
Each player is dealt 7 cards, kept secret from other players.
|
||||
|
||||
## turns
|
||||
|
||||
There are two variations for taking turns. "Chance rules" is
|
||||
strictly a matter of chance, and is recommended for impatient
|
||||
players because it tends to be faster-paced and has more of a
|
||||
*surprise!* element. "Strategy rules" introduces an element of
|
||||
strategy and is better suited for players with more _patience_.
|
||||
|
||||
### chance rules
|
||||
|
||||
Each player selects one card and places it face down in the middle
|
||||
playing area between themselves and other players.
|
||||
|
||||
On the count of 3, all said together in rhythm, each player flips
|
||||
over their own card. Precedence applies and the winning player
|
||||
takes all cards.
|
||||
|
||||
### strategy rules
|
||||
|
||||
On the first turn or when an equal number of cards have been
|
||||
captured by each player, the tallest player goes first. On each
|
||||
subsequent turn, the player with the most captured cards plays
|
||||
first. The play order then proceeds counter-clockwise.
|
||||
|
||||
When taking one's turn, the player may choose to either *play* or
|
||||
*swap*.
|
||||
|
||||
#### strategy *play*
|
||||
|
||||
When choosing to *play*, the player selects a card and places it
|
||||
face-up in the center play area. The remaining players take turns
|
||||
counter-clockwise, each placing a card face-up in the center play
|
||||
area. Precedence rules apply and the winning player takes all
|
||||
cards.
|
||||
|
||||
#### strategy *swap*
|
||||
|
||||
When choosing to *swap*, the player discards one card face-up in
|
||||
the discard pile (next to the stock pile) and then draws a card
|
||||
from the stock, which should be laying face-down. The goal here may
|
||||
be to take a chance at getting a better card than one is
|
||||
discarding, or to force the next player in the rotation to play, or
|
||||
both.
|
||||
|
||||
> **NOTE**: a player may only choose to *swap* if the player on the
|
||||
> previous turn _did not swap_.
|
||||
|
||||
## precedence
|
||||
|
||||
The following rules apply across suits with the exception of
|
||||
*jokers and fives* (explained below). Cards within a single suit
|
||||
are compared with higher-value cards winning. Cards are counted
|
||||
from ace (1) through king (13).
|
||||
|
||||
Ways to think about these rules could include:
|
||||
|
||||
- "rupee buys bomb" / money buys weapon
|
||||
- "rupee buys sword" / money buys weapon
|
||||
- "bomb blows up sword" / range weapon beats melee weapon
|
||||
- "bomb blows up heart" / range weapon beats unarmed
|
||||
- "sword cuts through heart" / melee weapon beats unarmed
|
||||
- "heart is stronger than rupee" / love conquers money
|
||||
|
||||
**NOTE:** In the case of a turn that involves 3 or more cards, the
|
||||
presence of both diamonds *and* hearts will result in the
|
||||
highest-value heart winning the turn.
|
||||
|
||||
### diamonds ("rupees")
|
||||
|
||||
Beats:
|
||||
|
||||
- clubs ("bombs")
|
||||
- spades ("swords")
|
||||
|
||||
### clubs ("bombs")
|
||||
|
||||
Beats:
|
||||
|
||||
- spades ("swords")
|
||||
- hearts
|
||||
|
||||
### spades ("swords")
|
||||
|
||||
Beats:
|
||||
|
||||
- hearts
|
||||
|
||||
### hearts
|
||||
|
||||
Beats:
|
||||
|
||||
- diamonds ("rupees")
|
||||
|
||||
### jokers ("tingles")
|
||||
|
||||
Beats everything except *fives*
|
||||
|
||||
### fives
|
||||
|
||||
The 5 of a given suit will be granted special status *only* when
|
||||
played against a joker ("tingle") and will win with the following
|
||||
sub-precedence, which is roughly the inverse of the main
|
||||
precedence:
|
||||
|
||||
- 5 of hearts
|
||||
- 5 of spades / "master sword"
|
||||
- 5 of clubs / "bomb cluster"
|
||||
- 5 of diamonds / "blue rupee"
|
||||
|
||||
## scoring
|
||||
|
||||
At the conclusion of a round of 7 cards, the player with the most
|
||||
cards is the winner. The face values of the cards are not
|
||||
considered at scoring time. A draw may be handled in a "run-off
|
||||
game" or ignored as you like.
|
||||
|
||||
## examples
|
||||
|
||||
In the following examples, the **winning card** is highlighted at
|
||||
the top of each list, followed by an explanation for the outcome.
|
||||
|
||||
### 2-player
|
||||
|
||||
- **2 of clubs**
|
||||
- 2 of spades
|
||||
|
||||
Clubs are higher-value than spades, or "bomb beats sword".
|
||||
|
||||
---
|
||||
|
||||
- **2 of clubs**
|
||||
- ace of clubs
|
||||
|
||||
Cards within the same suit are compared at face value with aces
|
||||
being *1*.
|
||||
|
||||
---
|
||||
|
||||
- **ace of hearts**
|
||||
- king of diamonds
|
||||
|
||||
Hearts take precedence over diamonds, or "love is stronger than
|
||||
money".
|
||||
|
||||
---
|
||||
|
||||
- **joker**
|
||||
- king of hearts
|
||||
|
||||
The opposing card is not a *five*, or "tingle takes _(thing)_".
|
||||
|
||||
---
|
||||
|
||||
- **5 of spades**
|
||||
- joker
|
||||
|
||||
The 5 of any suit will beat a joker given its special item status
|
||||
in that scenario, or "tingle is distracted by the beauty of
|
||||
_(thing)_".
|
||||
|
||||
### 3-player
|
||||
|
||||
- **2 of diamonds**
|
||||
- 2 of clubs
|
||||
- 2 of spades
|
||||
|
||||
Diamonds have higher precedence than clubs and spades, and there
|
||||
are no hearts present.
|
||||
|
||||
---
|
||||
|
||||
- **6 of clubs**
|
||||
- 6 of spades
|
||||
- 6 of hearts
|
||||
|
||||
Clubs have higher precedence than spades and hearts, and there are
|
||||
no diamonds present.
|
||||
|
||||
---
|
||||
|
||||
- **8 of hearts**
|
||||
- 8 of diamonds
|
||||
- 8 of spades
|
||||
|
||||
When both diamonds and hearts are present, hearts is highest
|
||||
precedence.
|
||||
|
||||
---
|
||||
|
||||
- **king of diamonds**
|
||||
- jack of diamonds
|
||||
- queen of diamonds
|
||||
|
||||
Within the same suit, cards are compared by face value.
|
BIN
hyrule/README.pdf
Normal file
BIN
hyrule/README.pdf
Normal file
Binary file not shown.
86
hyrule/cards.py
Normal file
86
hyrule/cards.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import dataclasses
|
||||
import enum
|
||||
import random
|
||||
|
||||
Suit = enum.Enum(
|
||||
"Suit",
|
||||
"""
|
||||
CLUBS
|
||||
DIAMONDS
|
||||
HEARTS
|
||||
SPADES
|
||||
""",
|
||||
)
|
||||
|
||||
FaceValue = enum.Enum(
|
||||
"FaceValue",
|
||||
"""
|
||||
ACE
|
||||
TWO
|
||||
THREE
|
||||
FOUR
|
||||
FIVE
|
||||
SIX
|
||||
SEVEN
|
||||
EIGHT
|
||||
NINE
|
||||
TEN
|
||||
JACK
|
||||
QUEEN
|
||||
KING
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Card:
|
||||
suit: Suit
|
||||
face_value: FaceValue
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.face_value} of {self.suit}"
|
||||
|
||||
|
||||
class Joker:
|
||||
def __str__(self):
|
||||
return "JOKER"
|
||||
|
||||
|
||||
class Deck:
|
||||
def __init__(self, n_jokers=2):
|
||||
self._cards = list(Deck.generate(n_jokers=n_jokers))
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Deck len={len(self)}>"
|
||||
|
||||
def __str__(self):
|
||||
as_string = []
|
||||
for card in self._cards:
|
||||
as_string.append(str(card))
|
||||
return "\n".join(as_string)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._cards)
|
||||
|
||||
def draw(self):
|
||||
if len(self._cards) > 0:
|
||||
return self._cards.pop()
|
||||
return None
|
||||
|
||||
def shuffle(self):
|
||||
random.shuffle(self._cards)
|
||||
return self
|
||||
|
||||
def cut(self):
|
||||
cut_point = random.randint(0, len(self))
|
||||
self._cards = self._cards[cut_point:] + self._cards[:cut_point]
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def generate(n_jokers=2):
|
||||
for _ in range(n_jokers):
|
||||
yield Joker()
|
||||
|
||||
for suit in Suit:
|
||||
for face_value in FaceValue:
|
||||
yield Card(suit=suit, face_value=face_value)
|
66
hyrule/hyrule.py
Normal file
66
hyrule/hyrule.py
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
import pprint
|
||||
import sys
|
||||
import typing
|
||||
|
||||
import cards
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||
)
|
||||
parser.add_argument(
|
||||
"--player-count", "-c", default=2, type=int, help="The number of players"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--turn-count",
|
||||
"-n",
|
||||
default=5,
|
||||
type=int,
|
||||
help="The number of turns to play",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
Hyrule(player_count=args.player_count, turn_count=args.turn_count).play()
|
||||
return 0
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Player:
|
||||
index: int
|
||||
hand: typing.Set[cards.Card]
|
||||
captured: typing.Set[cards.Card]
|
||||
|
||||
@property
|
||||
def score(self):
|
||||
return len(self.captured)
|
||||
|
||||
|
||||
class Hyrule:
|
||||
def __init__(self, player_count=2, turn_count=5):
|
||||
self._players = [
|
||||
Player(index=i, hand=set(), captured=set()) for i in range(player_count)
|
||||
]
|
||||
self._turn_count = turn_count
|
||||
|
||||
def play(self):
|
||||
for turn in self._each_turn():
|
||||
self._show_turn(turn)
|
||||
|
||||
def _each_turn(self):
|
||||
for turn_number in range(self._turn_count):
|
||||
yield self._simulate_turn(turn_number)
|
||||
|
||||
def _simulate_turn(self, turn_number):
|
||||
...
|
||||
|
||||
def _show_turn(self, turn):
|
||||
print(turn)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
2
hyrule/justfile
Normal file
2
hyrule/justfile
Normal file
@@ -0,0 +1,2 @@
|
||||
build:
|
||||
pandoc -r markdown -w pdf -o README.pdf README.md
|
@@ -1,9 +1,27 @@
|
||||
const SEP: &str = "\n---- bleh ----\n";
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
|
||||
println!("{}", SEP);
|
||||
|
||||
println!("0x{:x}", 0b1111_1010_1111_1010_1111_1010_1111);
|
||||
println!("0b{:b}", 0xfafafaf);
|
||||
println!("{}", 0xfafafaf);
|
||||
println!("{}", 0xfeefee);
|
||||
println!("0b{:b}", 0xfeefee);
|
||||
|
||||
println!("{}", SEP);
|
||||
|
||||
let sum = 5 + 10;
|
||||
let difference = 95.5 - 4.3;
|
||||
let product = 4 * 30;
|
||||
let quotient = 56.7 / 32.2;
|
||||
let remainder = 43 % 5;
|
||||
|
||||
println!("sum: {}", sum);
|
||||
println!("difference: {}", difference);
|
||||
println!("product: {}", product);
|
||||
println!("quotient: {}", quotient);
|
||||
println!("remainder: {}", remainder);
|
||||
}
|
||||
|
4
rustbyexample/.gitignore
vendored
4
rustbyexample/.gitignore
vendored
@@ -1,7 +1,11 @@
|
||||
/custom_types/*
|
||||
/hello/*
|
||||
/primitives/*
|
||||
/variable_bindings/*
|
||||
/types/*
|
||||
|
||||
!/custom_types/*.rs
|
||||
!/hello/*.rs
|
||||
!/primitives/*.rs
|
||||
!/variable_bindings/*.rs
|
||||
!/types/*.rs
|
||||
|
15
rustbyexample/types/alias.rs
Normal file
15
rustbyexample/types/alias.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
type NanoSecond = u64;
|
||||
type Inch = u64;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
type u64_t = u64;
|
||||
|
||||
fn main() {
|
||||
let nanoseconds: NanoSecond = 5 as u64_t;
|
||||
let inches: Inch = 2 as u64_t;
|
||||
|
||||
println!("{} nanoseconds + {} inches = {} unit?",
|
||||
nanoseconds,
|
||||
inches,
|
||||
nanoseconds + inches);
|
||||
}
|
52
rustbyexample/types/cast.rs
Normal file
52
rustbyexample/types/cast.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
#![allow(overflowing_literals)]
|
||||
|
||||
fn main() {
|
||||
let decimal = 65.4321_f32;
|
||||
// let integer: u8 = decimal;
|
||||
|
||||
let integer = decimal as u8;
|
||||
let character = integer as char;
|
||||
|
||||
// let character = decimal as char;
|
||||
|
||||
println!("Casting: {} -> {} -> {}", decimal, integer, character);
|
||||
|
||||
println!("1000 as u16 is: {}", 1000 as u16);
|
||||
|
||||
println!("1000 as u8 is: {}", 1000 as u8);
|
||||
|
||||
println!(" -1 as u8 is: {}", (-1i8) as u8);
|
||||
|
||||
println!("1000 mod 256 is: {}", 1000 % 256);
|
||||
|
||||
println!(" 128 as a i16 is: {}", 128 as i16);
|
||||
|
||||
println!(" 128 as a i8 is: {}", 128 as i8);
|
||||
|
||||
println!("1000 as a u8 is: {}", 1000 as u8);
|
||||
|
||||
println!(" 232 as a i8 is: {}", 232 as i8);
|
||||
|
||||
println!("300.0 as u8 is {}", 300.0_f32 as u8);
|
||||
|
||||
println!("-100.0 as u8 is {}", -100.0_f32 as u8);
|
||||
|
||||
println!("nan as u8 is {}", f32::NAN as u8);
|
||||
|
||||
unsafe {
|
||||
println!(
|
||||
"300.0 as u8 is {} (unchecked)",
|
||||
300.0_f32.to_int_unchecked::<u8>()
|
||||
);
|
||||
|
||||
println!(
|
||||
"-100.0 as u8 is {} (unchecked)",
|
||||
(-100.0_f32).to_int_unchecked::<u8>()
|
||||
);
|
||||
|
||||
println!(
|
||||
"nan as u8 is {} (unchecked)",
|
||||
f32::NAN.to_int_unchecked::<u8>()
|
||||
);
|
||||
}
|
||||
}
|
9
rustbyexample/types/inference.rs
Normal file
9
rustbyexample/types/inference.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
fn main() {
|
||||
let elem = 5u8;
|
||||
|
||||
let mut vec = Vec::new();
|
||||
|
||||
vec.push(elem);
|
||||
|
||||
println!("{:?}", vec);
|
||||
}
|
14
rustbyexample/types/literals.rs
Normal file
14
rustbyexample/types/literals.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
fn main() {
|
||||
let x = 1u8;
|
||||
let y = 2u32;
|
||||
let z = 3f32;
|
||||
|
||||
let i = 1;
|
||||
let f = 1.0;
|
||||
|
||||
println!("size of `x` in bytes: {}", std::mem::size_of_val(&x));
|
||||
println!("size of `y` in bytes: {}", std::mem::size_of_val(&y));
|
||||
println!("size of `z` in bytes: {}", std::mem::size_of_val(&z));
|
||||
println!("size of `i` in bytes: {}", std::mem::size_of_val(&i));
|
||||
println!("size of `f` in bytes: {}", std::mem::size_of_val(&f));
|
||||
}
|
19
rustbyexample/variable_bindings/declare.rs
Normal file
19
rustbyexample/variable_bindings/declare.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
fn main() {
|
||||
let a_binding;
|
||||
|
||||
{
|
||||
let x = 2;
|
||||
|
||||
a_binding = x * x;
|
||||
}
|
||||
|
||||
println!("a binding: {}", a_binding);
|
||||
|
||||
let another_binding;
|
||||
|
||||
//println!("another binding: {}", another_binding);
|
||||
|
||||
another_binding = 1;
|
||||
|
||||
println!("another binding: {}", another_binding);
|
||||
}
|
11
rustbyexample/variable_bindings/freeze.rs
Normal file
11
rustbyexample/variable_bindings/freeze.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
fn main() {
|
||||
let mut _mutable_integer = 7i32;
|
||||
|
||||
{
|
||||
let _mutable_integer = _mutable_integer;
|
||||
|
||||
// _mutable_integer = 50;
|
||||
}
|
||||
|
||||
_mutable_integer = 3;
|
||||
}
|
12
rustbyexample/variable_bindings/mut.rs
Normal file
12
rustbyexample/variable_bindings/mut.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
fn main() {
|
||||
let _immutable_binding = 1;
|
||||
let mut mutable_binding = 1;
|
||||
|
||||
println!("Before mutation: {}", mutable_binding);
|
||||
|
||||
mutable_binding += 1;
|
||||
|
||||
println!("After mutation: {}", mutable_binding);
|
||||
|
||||
// _immutable_binding += 1;
|
||||
}
|
12
rustbyexample/variable_bindings/scope.rs
Normal file
12
rustbyexample/variable_bindings/scope.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
fn main() {
|
||||
let long_lived_binding = 1;
|
||||
|
||||
{
|
||||
let short_lived_binding = 2;
|
||||
println!("inner short: {}", short_lived_binding);
|
||||
}
|
||||
|
||||
// println!("outer short: {}", short_lived_binding);
|
||||
|
||||
println!("outer long: {}", long_lived_binding);
|
||||
}
|
16
rustbyexample/variable_bindings/scope2.rs
Normal file
16
rustbyexample/variable_bindings/scope2.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
fn main() {
|
||||
let shadowed_binding = 1;
|
||||
|
||||
{
|
||||
println!("before being shadowed: {}", shadowed_binding);
|
||||
|
||||
let shadowed_binding = "abc";
|
||||
|
||||
println!("shadowed in inner block: {}", shadowed_binding);
|
||||
}
|
||||
println!("outside inner block: {}", shadowed_binding);
|
||||
|
||||
let shadowed_binding = 2;
|
||||
println!("shadowed in outer block: {}", shadowed_binding);
|
||||
}
|
||||
|
15
rustbyexample/variable_bindings/variable_bindings.rs
Normal file
15
rustbyexample/variable_bindings/variable_bindings.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
fn main() {
|
||||
let an_integer = 1u32;
|
||||
let a_boolean = true;
|
||||
let unit = ();
|
||||
|
||||
let copied_integer = an_integer;
|
||||
|
||||
println!("An integer: {:?}", copied_integer);
|
||||
println!("A boolean: {:?}", a_boolean);
|
||||
println!("Meet the unit value: {:?}", unit);
|
||||
|
||||
let _unused_variable = 3u32;
|
||||
|
||||
let noisy_unused_variable = 2u32;
|
||||
}
|
Reference in New Issue
Block a user