Beispiel #1
0
    def stedman(stage: int, start_row: Optional[str] = None) -> RowGenerator:
        """ Generates Stedman on a given stage (even bell Stedman will cause an exception). """
        assert stage % 2 == 1

        if stage == 5:
            return PlaceNotationGenerator.stedman_doubles(start_row)

        stage_bell = convert_to_bell_string(stage)
        stage_bell_1 = convert_to_bell_string(stage - 1)
        stage_bell_2 = convert_to_bell_string(stage - 2)

        notation = f"3.1.{stage_bell}.3.1.3.1.3.{stage_bell}.1.3.1"

        return PlaceNotationGenerator(
            stage,
            notation,
            bob=CallDef({
                3: stage_bell_2,
                9: stage_bell_2
            }),
            single=CallDef({
                3: f"{stage_bell_2}{stage_bell_1}{stage_bell}",
                9: f"{stage_bell_2}{stage_bell_1}{stage_bell}"
            }),
            start_row=start_row)
Beispiel #2
0
    def stedman_doubles() -> RowGenerator:
        """ Generates Stedman on a given stage (even bell Stedman will cause an exception). """
        notation = "3.1.5.3.1.3.1.3.5.1.3.1"

        return PlaceNotationGenerator(5,
                                      notation,
                                      bob=CallDef({}),
                                      single=CallDef({
                                          6: "345",
                                          12: "145"
                                      }))
Beispiel #3
0
    def grandsire(stage: int) -> RowGenerator:
        """ Generates Grandsire on a given stage. """
        stage_bell = convert_to_bell_string(stage)

        cross_notation = stage_bell if stage % 2 else '-'

        main_body = [
            "1" if i % 2 else cross_notation for i in range(2 * stage)
        ]
        main_body[0] = "3"
        notation = ".".join(main_body)

        return PlaceNotationGenerator(stage,
                                      notation,
                                      bob=CallDef({-1: "3"}),
                                      single=CallDef({-1: "3.123"}))
Beispiel #4
0
    def test_call_parsing(self):
        test_cases = [
            ("14", {
                0: "14"
            }),
            ("3.123", {
                0: "3.123"
            }),
            ("  0 \t:  \n 16   ", {
                0: "16"
            }),
            ("20: 70", {
                20: "70"
            }),
            ("20: 70/ 14", {
                20: "70",
                0: "14"
            }),
        ]

        for (input_arg, expected_call_dict) in test_cases:
            with self.subTest(input=input_arg,
                              expected_call_dict=expected_call_dict):
                self.assertEqual(CallDef(expected_call_dict),
                                 parse_call(input_arg))
Beispiel #5
0
def parse_call(input_string: str) -> CallDef:
    """ Parse a call string into a dict of lead locations to place notation strings. """
    def exit_with_message(message: str) -> NoReturn:
        """ Raises a parse error with the given error message. """
        raise CallParseError(input_string, message)

    # A dictionary that will be filled with the parsed calls
    parsed_calls: Dict[int, str] = {}

    for segment in input_string.split("/"):
        # Default the location to 0 and initialise place_notation_str with None
        location = 0
        place_notation_str = None

        if ":" in segment:
            # Split the segment into location and place notations
            try:
                location_str, place_notation_str = segment.split(":")
            except ValueError:
                exit_with_message(
                    f"Call specification '{segment.strip()}' should contain at most one ':'."
                )

            # Strip whitespace from the string segments so that they can be parsed more easily
            assert place_notation_str is not None
            location_str = location_str.strip()
            place_notation_str = place_notation_str.strip()

            # Parse the first section as an integer
            try:
                location = int(location_str)
            except ValueError:
                exit_with_message(
                    f"Location '{location_str}' is not an integer.")
        else:
            # If there's only one section, it must be the place notation so all we need to do is to
            # strip it of whitespace (and `location` defaults to 0 so that's also set correctly)
            place_notation_str = segment.strip()

        # Produce a nice error message if the pn string is empty
        if place_notation_str == "":
            exit_with_message("Place notation strings cannot be empty.")

        # Insert the new call definition into the dictionary
        if location in parsed_calls:
            exit_with_message(
                f"Location {location} has two conflicting calls: \
'{parsed_calls[location]}' and '{place_notation_str}'.")

        parsed_calls[location] = place_notation_str

    return CallDef(parsed_calls)
Beispiel #6
0
    def json_to_call(name: str) -> Optional[CallDef]:
        """ Helper function to generate a call with a given name from the json. """
        if name not in json:
            logger.warning(f"No field '{name}' in the row generator JSON")
            return None

        call: Dict[int, str] = {}
        for key, value in json[name].items():
            try:
                index = int(key)
            except ValueError as e:
                raise_error(name, f"Call index '{key}' is not a valid integer",
                            e)
            call[index] = value
        return CallDef(call)
Beispiel #7
0
        def parse_call_dict(
                unparsed_calls: CallDef) -> Dict[int, List[Places]]:
            """ Parse a dict of type `int => str` to `int => [PlaceNotation]`. """
            parsed_calls = {}

            for i, place_notation_str in unparsed_calls.items():
                # Parse the place notation string into a list of place notations, adjust the
                # call locations by the length of their calls (so that e.g. `0` always refers to
                # the lead end regardless of how long the calls are).
                converted_place_notations = convert_pn(place_notation_str)

                # Add the processed call to the output dictionary
                parsed_calls[(i - 1) %
                             self.lead_len] = converted_place_notations

            return parsed_calls
Beispiel #8
0
class PlaceNotationGenerator(RowGenerator):
    """ A row generator to generate rows given a place notation. """

    # Dict Lead Index: String PlaceNotation
    # 0 for end of the lead
    DefaultBob: ClassVar[CallDef] = CallDef({0: '14'})
    DefaultSingle: ClassVar[CallDef] = CallDef({0: '1234'})

    def __init__(self,
                 stage: int,
                 method: str,
                 bob: CallDef = None,
                 single: CallDef = None,
                 start_index: int = 0) -> None:
        super().__init__(stage)

        if bob is None:
            bob = PlaceNotationGenerator.DefaultBob
        if single is None:
            single = PlaceNotationGenerator.DefaultSingle

        self.method_pn = convert_pn(method)
        self.lead_len = len(self.method_pn)
        # Store the method place notation as a string for the summary string
        self.method_pn_string = method
        self.start_index = start_index

        def parse_call_dict(
                unparsed_calls: CallDef) -> Dict[int, List[Places]]:
            """ Parse a dict of type `int => str` to `int => [PlaceNotation]`. """
            parsed_calls = {}

            for i, place_notation_str in unparsed_calls.items():
                # Parse the place notation string into a list of place notations, adjust the
                # call locations by the length of their calls (so that e.g. `0` always refers to
                # the lead end regardless of how long the calls are).
                converted_place_notations = convert_pn(place_notation_str)

                # Add the processed call to the output dictionary
                parsed_calls[(i - 1) %
                             self.lead_len] = converted_place_notations

            return parsed_calls

        self.bobs_pn = parse_call_dict(bob)
        self.singles_pn = parse_call_dict(single)

        self._generating_call_pn: List[Places] = []

    def summary_string(self) -> str:
        """ Returns a short string summarising the RowGenerator. """
        return f"place notation '{self.method_pn_string}'"

    def _gen_row(self, previous_row: Row, stroke: Stroke, index: int) -> Row:
        assert self.lead_len > 0
        lead_index = (index + self.start_index) % self.lead_len

        if self._has_bob and self.bobs_pn.get(lead_index):
            self._generating_call_pn = list(self.bobs_pn[lead_index])
            self.logger.info(f"Bob at index {lead_index}")
            self.reset_calls()
        elif self._has_single and self.singles_pn.get(lead_index):
            self._generating_call_pn = list(self.singles_pn[lead_index])
            self.logger.info(f"Single at index {lead_index}")
            self.reset_calls()

        if self._generating_call_pn:
            place_notation = self._generating_call_pn.pop(0)
        else:
            place_notation = self.method_pn[lead_index]

        return self.permute(previous_row, place_notation)

    def start_stroke(self) -> Stroke:
        return Stroke.from_index(self.start_index)

    @staticmethod
    def grandsire(stage: int) -> RowGenerator:
        """ Generates Grandsire on a given stage. """
        stage_bell = convert_to_bell_string(stage)

        cross_notation = stage_bell if stage % 2 else '-'

        main_body = [
            "1" if i % 2 else cross_notation for i in range(2 * stage)
        ]
        main_body[0] = "3"
        notation = ".".join(main_body)

        return PlaceNotationGenerator(stage,
                                      notation,
                                      bob=CallDef({-1: "3"}),
                                      single=CallDef({-1: "3.123"}))

    @staticmethod
    def stedman(stage: int) -> RowGenerator:
        """ Generates Stedman on a given stage (even bell Stedman will cause an exception). """
        assert stage % 2 == 1

        if stage == 5:
            return PlaceNotationGenerator.stedman_doubles()

        stage_bell = convert_to_bell_string(stage)
        stage_bell_1 = convert_to_bell_string(stage - 1)
        stage_bell_2 = convert_to_bell_string(stage - 2)

        notation = f"3.1.{stage_bell}.3.1.3.1.3.{stage_bell}.1.3.1"

        return PlaceNotationGenerator(
            stage,
            notation,
            bob=CallDef({
                3: stage_bell_2,
                9: stage_bell_2
            }),
            single=CallDef({
                3: f"{stage_bell_2}{stage_bell_1}{stage_bell}",
                9: f"{stage_bell_2}{stage_bell_1}{stage_bell}"
            }))

    @staticmethod
    def stedman_doubles() -> RowGenerator:
        """ Generates Stedman on a given stage (even bell Stedman will cause an exception). """
        notation = "3.1.5.3.1.3.1.3.5.1.3.1"

        return PlaceNotationGenerator(5,
                                      notation,
                                      bob=CallDef({}),
                                      single=CallDef({
                                          6: "345",
                                          12: "145"
                                      }))