Реализация паттерна состояние

Код ниже подсчитывает из строки с символами количество очков по следующей схеме:

«Х» – 20 очков

«4/» - 15 очков

«34» – сумма 3+4=7

«-4» - сумма 0+4=4

Подсчет делится на 2 хода, и определенные комбинации дают определенные очки. Ранее подсчет у меня велся с помощью функции result_count, которая брала из get_score оба хода и высчитывала.

Теперь мне нужно реализовать с помощью паттерна состояние чтобы каждый ход рассчитывался отдельно. Я читал по нему информацию, но так до конца не пойму, как можно его применить в этом коде. Я создал класс, берущий список обоих ходов, в котором есть 2 метода, рассчитывающие отдельно 1 и 2 ход путем разбивки внутри себя двух значений. Но насколько я понимаю, это не та конструкция, должно быть реализовано какое-то переключение между методами, а не так чтобы они оба вызывались. Подскажите, что тут нужно изменить для правильного решения?

analysed_res = {}
total = 0
values = []


def get_score(game_result):
    global analysed_res, total
    analysed_res = {}
    total = 0
    frames = 0
    for _ in game_result:
        for key, value in enumerate(zip(game_result.replace('X', 'X-')[0::2], game_result.replace('X', 'X-')[1::2]),
                                    start=1):
            analysed_res[key] = value
    for number, analysed_value in analysed_res.items():
        frames += 1
        #result_count(analysed_value)
        values.append(analysed_value)


def result_count(analysed_value):
    global total
    if 'X' in analysed_value:
        total += 20
    elif '/' in analysed_value:
        total += 15
    elif '-' in analysed_value:
        total += 0
    else:
        total += int(analysed_value[0]) + int(analysed_value[1])
    return analysed_value


class Switcher:

    def __init__(self, value):
        self.value = value
        self.first_score = 0
        self.second_score = 0

    def first_move(self):
        for value in self.value:
            one = value[0]
            if one == 'X':
                self.first_score += 20
            elif one == '-' or value[1] == '/':
                pass
            else:
                self.first_score += int(one)


    def second_move(self):
        for value in self.value:
            two = value[1]
            if two == '/':
                self.second_score += 15
            elif two == '-':
                pass
            else:
                self.second_score += int(two)


if __name__ == "__main__":
    result = '123/XXXXXX--X'
    get_score(game_result=result)
    s = Switcher(values)
    s.first_move()
    s.second_move()
    print(s.first_score, s.second_score)
Всего 10 кеглей. Игра состоит из 10 фреймов. В одном фрейме до 2х бросков, цель - сбить все кегли.
Результаты фрейма записываются символами:
  «Х» – «strike», все 10 кеглей сбиты первым броском
  «<число>/», например «4/» - «spare», в первый бросок сбиты 4 кегли, во второй – остальные
  «<число><число>», например, «34» – в первый бросок сбито 3, во второй – 4 кегли.
  вместо <число> может стоять прочерк «-», например «-4» - ни одной кегли не было сбито за первый бросок
Результат игры – строка с записью результатов фреймов. Символов-разделителей между фреймами нет.
Например, для игры из 4 фреймов запись результатов может выглядеть так:
  «Х4/34-4»
Предлагается упрощенный способ подсчета количества очков:
  «Х» – strike всегда 20 очков
  «4/» - spare всегда 15 очков
  «34» – сумма 3+4=7
  «-4» - сумма 0+4=4
То есть для игры «Х4/34-4» сумма очков равна 20+15+7+4=46

Ответы (3 шт):

Автор решения: MiniMax

Решение с использованием паттерна состояние

from abc import ABCMeta, abstractmethod

class State(metaclass=ABCMeta):
    @abstractmethod
    def throw(self, throw_result):
        pass

class First_attempt(State):
    def __init__(self, game):
        self.game = game

    def throw(self, throw_result):
        if throw_result == 'X':
            self.game.score += 20
        else:
            if throw_result.isdigit():
                self.game.first_attempt_points = int(throw_result)
                self.game.state = self.game.second_attempt
            elif throw_result == '-':
                self.game.state = self.game.second_attempt

class Second_attempt(State):
    def __init__(self, game):
        self.game = game

    def throw(self, throw_result):
        if throw_result == '/':
            self.game.score += 15
        elif throw_result.isdigit():
            self.game.score += self.game.first_attempt_points + int(throw_result)

        self.game.first_attempt_points = 0
        self.game.state = self.game.first_attempt

class Game:
    def __init__(self):
        self.first_attempt = First_attempt(self)
        self.second_attempt = Second_attempt(self)

        self.score = 0
        self.first_attempt_points = 0

        self.state = self.first_attempt

    def throw(self, throw_result):
        self.state.throw(throw_result)

Тестирование

scores = [
            "X4/34-4",
            "123/XXXXXX--X"
        ]

for score in scores:
    game = Game()

    for throw_result in score:
        game.throw(throw_result)

    print(game.score)

Результат

46
158
→ Ссылка
Автор решения: NykSu

Немного другой вариант.

class Score():
    score = 0
    index = 0
    strscore = ''
    
    @classmethod
    def add_score(cls, addscore):
        cls.score += addscore
        
    @classmethod
    def add_str_score(cls, sscore):
        cls.score = 0
        cls.index = 0
        cls.strscore = sscore
    
    @classmethod
    def inext(cls):
        cls.index += 1
        return cls.index
    
    @classmethod
    def idx(cls):
        return cls.index

class SecondSignScore():
    cc = Score()
    
    def __new__(cls, index, score):
        if index < len(cls.cc.strscore):
            if cls.cc.strscore[index] == '/':
                cls.cc.add_score(15)
                cls.cc.inext()
            else:
                sc = 0 if cls.cc.strscore[index] == '-' else int(cls.cc.strscore[index])
                cls.cc.inext()
                cls.cc.add_score(score + sc)
        return cls.cc.score
        
class FirstSignScore():
    cc = Score()
    
    def __new__(cls, index):
        if index < len(cls.cc.strscore):
            if cls.cc.strscore[index] == 'X':
                cls.cc.add_score(20)
                cls.cc.inext()
            else:
                sc = 0 if cls.cc.strscore[index] == '-' else int(cls.cc.strscore[index])                
                return SecondSignScore(cls.cc.inext(), sc)
        return cls.cc.score

Собственно вызов решения:

Score.add_str_score('123/XXXXXX--X')

res = 0

while Score.idx() < len(Score.strscore):
    res = FirstSignScore(Score.idx())

print(res)

Ответ ожидаемый. :)

Но всё же, лучше без классов и без паттерна State.

Хоть кто-то предложит и более простое решение, на мой взгляд и это довольно лакончино:

import re

def decode_score(score):
    f_score = re.sub(r'-', "0", score)
    f_score = re.sub(r'\d/', "/", f_score)
    x_score = re.findall(r'X', f_score)
    s_score = re.findall(r'/', f_score)
    d_score = list(map(int, re.findall(r'\d', f_score)))
    return len(x_score) * 20 + len(s_score) * 15 + sum(d_score)

if __name__ == "__main__":
    result = '123/XXXXXX--X'
    
    print(decode_score(result))
→ Ссылка
Автор решения: виктор инторинов
from abc import ABC, abstractmethod


class State(ABC):
    @abstractmethod
    def strike(self):
        pass

    @abstractmethod
    def spare(self):
        pass

    @staticmethod
    def count(element):
        if element == '-':
            return 0
        if element != '0':
            return int(element)


class Bowling:
    def __init__(self):
        self.state = None
        self.move_counter = 0
        self.total_score = 0

    def change_state(self, state):
        self.state = state

    def char_state(self, element):
        if element == 'X':
            self.total_score += self.state.strike()
        elif element == '/':
            self.total_score += self.state.spare()
        elif element.isdigit() or element == '-':
            self.total_score += self.state.count(element)

    def split(self, game_result):
        frame = game_result[self.move_counter] + game_result[self.move_counter + 1] \
            if game_result[self.move_counter] != 'X' else game_result[self.move_counter]
        yield frame
        self.move_counter += 1 if len(frame) == 2 else 0

    def switch(self, game_result):
        while self.move_counter < len(game_result):
            frame_generation = self.split(game_result)
            for frame in frame_generation:
                if len(frame) == 1:
                    self.change_state(FirstMove())
                    self.char_state(frame[0])
                elif frame[1] == '/' and frame[0].isdigit() and frame[0] != '0':
                    self.change_state(SecondMove())
                    self.char_state(frame[1])
                else:
                    self.change_state(FirstMove())
                    self.char_state(frame[0])
                    self.change_state(SecondMove())
                    self.char_state(frame[1])
                self.move_counter += 1


class FirstMove(State):
    def strike(self):
        return 20

    def spare(self):
        pass


class SecondMove(State):
    def strike(self):
        pass

    def spare(self):
        return 15
→ Ссылка