484 lines
23 KiB
Python
484 lines
23 KiB
Python
from designer import right_column, StepByStepDisplay
|
||
import flet as ft
|
||
import sys
|
||
import os
|
||
|
||
# Добавляем путь к модулю step_by_step_solver
|
||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
from step_by_step_solver import StepByStepSolver, Operation
|
||
|
||
class Button_quinquagesimal_calculator(ft.CupertinoButton):
|
||
def __init__(self, text: str = "50-ричный калькулятор"):
|
||
self.result_left = ft.Text(value="0", size=30, width=300)
|
||
self.result_right = ft.Text(value="0", size=30, width=300)
|
||
self.result_mult = ft.Text(value="0", size=30, width=300)
|
||
self.result_div = ft.Text(value="0", size=30, width=300)
|
||
self.decimal_left = ft.Text(value="(0₁₀)", size=14, color="#B0B0B0", width=300)
|
||
self.decimal_right = ft.Text(value="(0₁₀)", size=14, color="#B0B0B0", width=300)
|
||
self.decimal_mult = ft.Text(value="(0₁₀)", size=14, color="#B0B0B0", width=300)
|
||
self.decimal_div = ft.Text(value="(0₁₀)", size=14, color="#B0B0B0", width=300)
|
||
|
||
# Поля ввода для вычитания
|
||
self.input_left_1 = None
|
||
self.input_left_2 = None
|
||
|
||
# Поля ввода для сложения
|
||
self.input_right_1 = None
|
||
self.input_right_2 = None
|
||
|
||
# Поля ввода для умножения
|
||
self.input_mult_1 = None
|
||
self.input_mult_2 = None
|
||
|
||
# Поля ввода для деления
|
||
self.input_div_1 = None
|
||
self.input_div_2 = None
|
||
|
||
# Контейнеры для пошагового решения
|
||
self.steps_left = ft.Column(scroll=ft.ScrollMode.AUTO, spacing=5)
|
||
self.steps_right = ft.Column(scroll=ft.ScrollMode.AUTO, spacing=5)
|
||
self.steps_mult = ft.Column(scroll=ft.ScrollMode.AUTO, spacing=5)
|
||
self.steps_div = ft.Column(scroll=ft.ScrollMode.AUTO, spacing=5)
|
||
|
||
# Допустимые символы: 0-9, A-Z, a-n (всего: 10 + 26 + 14 = 50)
|
||
self.digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn"
|
||
|
||
# Солвер для 50-ричной системы с нашим алфавитом
|
||
self.solver = StepByStepSolver(base=50, digits=self.digits)
|
||
|
||
super().__init__(
|
||
bgcolor="#00000000",
|
||
disabled_bgcolor="#3A1E51",
|
||
text=text,
|
||
color="white",
|
||
on_click=self.test_data
|
||
)
|
||
|
||
def validate_base50(self, value: str) -> bool:
|
||
"""Проверяет, является ли строка корректным 50-ричным числом.
|
||
|
||
Допустимые символы: 0-9, A-Z, a-n (0-49), опциональный знак минус
|
||
"""
|
||
if value is None or value == "":
|
||
return True
|
||
v = value.strip()
|
||
if v == "":
|
||
return True
|
||
if v[0] == '-':
|
||
v = v[1:]
|
||
# В 50-ричной системе используются цифры 0-9, A-Z, a-n (всего 50 символов)
|
||
valid_chars = self.digits # 0-9, A-Z, a-n
|
||
return all(c in valid_chars for c in v)
|
||
|
||
def to_base50(self, decimal_num: int) -> str:
|
||
"""Преобразование десятичного числа в 50-ричное"""
|
||
if decimal_num == 0:
|
||
return "0"
|
||
|
||
is_negative = decimal_num < 0
|
||
decimal_num = abs(decimal_num)
|
||
|
||
result = ""
|
||
while decimal_num > 0:
|
||
remainder = decimal_num % 50
|
||
result = self.digits[remainder] + result
|
||
decimal_num //= 50
|
||
|
||
return ("-" + result) if is_negative else result
|
||
|
||
def from_base50(self, base50_str: str) -> int:
|
||
"""Преобразование 50-ричного числа в десятичное"""
|
||
if not base50_str:
|
||
return 0
|
||
|
||
base50_str = base50_str.strip()
|
||
if base50_str == "" or base50_str == "0":
|
||
return 0
|
||
|
||
is_negative = base50_str.startswith('-')
|
||
if is_negative:
|
||
base50_str = base50_str[1:]
|
||
|
||
result = 0
|
||
for char in base50_str:
|
||
if char in self.digits:
|
||
digit_value = self.digits.index(char)
|
||
result = result * 50 + digit_value
|
||
else:
|
||
raise ValueError(f"Недопустимый символ '{char}' для 50-ричной системы")
|
||
|
||
return -result if is_negative else result
|
||
|
||
def show_steps_left(self, num1: str, num2: str, operation: Operation):
|
||
"""Отображает пошаговое решение для левой колонки (вычитание)"""
|
||
StepByStepDisplay.show_steps(self.steps_left, self.solver, num1, num2, operation)
|
||
|
||
def show_steps_right(self, num1: str, num2: str, operation: Operation):
|
||
"""Отображает пошаговое решение для правой колонки (сложение)"""
|
||
StepByStepDisplay.show_steps(self.steps_right, self.solver, num1, num2, operation)
|
||
|
||
def show_steps_mult(self, num1: str, num2: str, operation: Operation):
|
||
"""Отображает пошаговое решение для колонки умножения"""
|
||
StepByStepDisplay.show_steps(self.steps_mult, self.solver, num1, num2, operation)
|
||
|
||
def show_steps_div(self, num1: str, num2: str, operation: Operation):
|
||
"""Отображает пошаговое решение для колонки деления"""
|
||
StepByStepDisplay.show_steps(self.steps_div, self.solver, num1, num2, operation)
|
||
|
||
def calculate_subtraction(self, e):
|
||
try:
|
||
val1 = (self.input_left_1.value or "").strip()
|
||
val2 = (self.input_left_2.value or "").strip()
|
||
|
||
# Проверка корректности 50-ричных чисел
|
||
if not self.validate_base50(val1) or not self.validate_base50(val2):
|
||
self.result_left.value = "Ошибка: неверный формат"
|
||
self.decimal_left.value = "Используйте 0-9, A-Z, a-n"
|
||
e.page.update()
|
||
return
|
||
|
||
# Преобразуем в десятичные числа
|
||
num1 = self.from_base50(val1 if val1 else "0")
|
||
num2 = self.from_base50(val2 if val2 else "0")
|
||
|
||
# Вычисляем результат
|
||
result = num1 - num2
|
||
|
||
# Преобразуем обратно в 50-ричный формат
|
||
base50_result = self.to_base50(result)
|
||
|
||
self.result_left.value = base50_result
|
||
self.decimal_left.value = f"({result}₁₀)"
|
||
e.page.update()
|
||
|
||
# Получаем пошаговое решение (убираем знак минус для солвера)
|
||
clean_val1 = val1.lstrip('-') if val1.startswith('-') else val1
|
||
clean_val2 = val2.lstrip('-') if val2.startswith('-') else val2
|
||
self.show_steps_left(clean_val1 if clean_val1 else "0", clean_val2 if clean_val2 else "0", Operation.SUBTRACTION)
|
||
|
||
except ValueError as ex:
|
||
self.result_left.value = "Ошибка"
|
||
self.decimal_left.value = str(ex)
|
||
e.page.update()
|
||
|
||
def calculate_addition(self, e):
|
||
try:
|
||
val1 = (self.input_right_1.value or "").strip()
|
||
val2 = (self.input_right_2.value or "").strip()
|
||
|
||
# Проверка корректности 50-ричных чисел
|
||
if not self.validate_base50(val1) or not self.validate_base50(val2):
|
||
self.result_right.value = "Ошибка: неверный формат"
|
||
self.decimal_right.value = "Используйте 0-9, A-Z, a-n"
|
||
e.page.update()
|
||
return
|
||
|
||
# Преобразуем в десятичные числа
|
||
num1 = self.from_base50(val1 if val1 else "0")
|
||
num2 = self.from_base50(val2 if val2 else "0")
|
||
|
||
# Вычисляем результат
|
||
result = num1 + num2
|
||
|
||
# Преобразуем обратно в 50-ричный формат
|
||
base50_result = self.to_base50(result)
|
||
|
||
self.result_right.value = base50_result
|
||
self.decimal_right.value = f"({result}₁₀)"
|
||
e.page.update()
|
||
|
||
# Получаем пошаговое решение
|
||
clean_val1 = val1.lstrip('-') if val1.startswith('-') else val1
|
||
clean_val2 = val2.lstrip('-') if val2.startswith('-') else val2
|
||
self.show_steps_right(clean_val1 if clean_val1 else "0", clean_val2 if clean_val2 else "0", Operation.ADDITION)
|
||
|
||
except ValueError as ex:
|
||
self.result_right.value = "Ошибка"
|
||
self.decimal_right.value = str(ex)
|
||
e.page.update()
|
||
|
||
def calculate_multiplication(self, e):
|
||
try:
|
||
val1 = (self.input_mult_1.value or "").strip()
|
||
val2 = (self.input_mult_2.value or "").strip()
|
||
|
||
# Проверка корректности 50-ричных чисел
|
||
if not self.validate_base50(val1) or not self.validate_base50(val2):
|
||
self.result_mult.value = "Ошибка: неверный формат"
|
||
self.decimal_mult.value = "Используйте 0-9, A-Z, a-n"
|
||
e.page.update()
|
||
return
|
||
|
||
# Преобразуем в десятичные числа
|
||
num1 = self.from_base50(val1 if val1 else "0")
|
||
num2 = self.from_base50(val2 if val2 else "0")
|
||
|
||
# Вычисляем результат
|
||
result = num1 * num2
|
||
|
||
# Преобразуем обратно в 50-ричный формат
|
||
base50_result = self.to_base50(result)
|
||
|
||
self.result_mult.value = base50_result
|
||
self.decimal_mult.value = f"({result}₁₀)"
|
||
e.page.update()
|
||
|
||
# Получаем пошаговое решение
|
||
clean_val1 = val1.lstrip('-') if val1.startswith('-') else val1
|
||
clean_val2 = val2.lstrip('-') if val2.startswith('-') else val2
|
||
self.show_steps_mult(clean_val1 if clean_val1 else "0", clean_val2 if clean_val2 else "0", Operation.MULTIPLICATION)
|
||
|
||
except ValueError as ex:
|
||
self.result_mult.value = "Ошибка"
|
||
self.decimal_mult.value = str(ex)
|
||
e.page.update()
|
||
|
||
def calculate_division(self, e):
|
||
try:
|
||
val1 = (self.input_div_1.value or "").strip()
|
||
val2 = (self.input_div_2.value or "").strip()
|
||
|
||
# Проверка корректности 50-ричных чисел
|
||
if not self.validate_base50(val1) or not self.validate_base50(val2):
|
||
self.result_div.value = "Ошибка: неверный формат"
|
||
self.decimal_div.value = "Используйте 0-9, A-Z, a-n"
|
||
e.page.update()
|
||
return
|
||
|
||
# Преобразуем в десятичные числа
|
||
num1 = self.from_base50(val1 if val1 else "0")
|
||
num2 = self.from_base50(val2 if val2 else "0")
|
||
|
||
# Проверка деления на ноль
|
||
if num2 == 0:
|
||
self.result_div.value = "Ошибка"
|
||
self.decimal_div.value = "Деление на ноль!"
|
||
e.page.update()
|
||
return
|
||
|
||
# Вычисляем результат (целочисленное деление)
|
||
result = num1 // num2
|
||
|
||
# Преобразуем обратно в 50-ричный формат
|
||
base50_result = self.to_base50(result)
|
||
|
||
self.result_div.value = base50_result
|
||
self.decimal_div.value = f"({result}₁₀)"
|
||
e.page.update()
|
||
|
||
# Получаем пошаговое решение
|
||
clean_val1 = val1.lstrip('-') if val1.startswith('-') else val1
|
||
clean_val2 = val2.lstrip('-') if val2.startswith('-') else val2
|
||
self.show_steps_div(clean_val1 if clean_val1 else "0", clean_val2 if clean_val2 else "0", Operation.DIVISION)
|
||
|
||
except ValueError as ex:
|
||
self.result_div.value = "Ошибка"
|
||
self.decimal_div.value = str(ex)
|
||
e.page.update()
|
||
|
||
def test_data(self, e):
|
||
# Создаем поля ввода для 50-ричных чисел
|
||
# Явно перечисляем ВСЕ 50 допустимых символов в regex
|
||
# 0-9 (10), A-Z (26), a-n (14) = 50 символов
|
||
allowed_chars = r"^-?[0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn]*$"
|
||
|
||
self.input_left_1 = ft.TextField(
|
||
label="50-ричное число 1",
|
||
width=250,
|
||
text_align=ft.TextAlign.RIGHT,
|
||
value="0",
|
||
input_filter=ft.InputFilter(regex_string=allowed_chars),
|
||
hint_text="Например: 1A (60₁₀) или Z (35₁₀) или a (36₁₀)"
|
||
)
|
||
self.input_left_2 = ft.TextField(
|
||
label="50-ричное число 2",
|
||
width=250,
|
||
text_align=ft.TextAlign.RIGHT,
|
||
value="0",
|
||
input_filter=ft.InputFilter(regex_string=allowed_chars),
|
||
hint_text="Например: B (11₁₀) или n (49₁₀)"
|
||
)
|
||
|
||
self.input_right_1 = ft.TextField(
|
||
label="50-ричное число 1",
|
||
width=250,
|
||
text_align=ft.TextAlign.RIGHT,
|
||
value="0",
|
||
input_filter=ft.InputFilter(regex_string=allowed_chars),
|
||
hint_text="Например: 1A (60₁₀) или c (38₁₀)"
|
||
)
|
||
self.input_right_2 = ft.TextField(
|
||
label="50-ричное число 2",
|
||
width=250,
|
||
text_align=ft.TextAlign.RIGHT,
|
||
value="0",
|
||
input_filter=ft.InputFilter(regex_string=allowed_chars),
|
||
hint_text="Например: C (12₁₀) или k (46₁₀)"
|
||
)
|
||
|
||
self.input_mult_1 = ft.TextField(
|
||
label="50-ричное число 1",
|
||
width=250,
|
||
text_align=ft.TextAlign.RIGHT,
|
||
value="0",
|
||
input_filter=ft.InputFilter(regex_string=allowed_chars),
|
||
hint_text="Например: A (10₁₀) или d (39₁₀)"
|
||
)
|
||
self.input_mult_2 = ft.TextField(
|
||
label="50-ричное число 2",
|
||
width=250,
|
||
text_align=ft.TextAlign.RIGHT,
|
||
value="0",
|
||
input_filter=ft.InputFilter(regex_string=allowed_chars),
|
||
hint_text="Например: 5 (5₁₀) или B (11₁₀)"
|
||
)
|
||
|
||
self.input_div_1 = ft.TextField(
|
||
label="50-ричное число 1",
|
||
width=250,
|
||
text_align=ft.TextAlign.RIGHT,
|
||
value="0",
|
||
input_filter=ft.InputFilter(regex_string=allowed_chars),
|
||
hint_text="Например: 14 (54₁₀) или Z (35₁₀)"
|
||
)
|
||
self.input_div_2 = ft.TextField(
|
||
label="50-ричное число 2",
|
||
width=250,
|
||
text_align=ft.TextAlign.RIGHT,
|
||
value="0",
|
||
input_filter=ft.InputFilter(regex_string=allowed_chars),
|
||
hint_text="Например: 2 (2₁₀) или A (10₁₀)"
|
||
)
|
||
|
||
right_column.controls = [ft.Container(
|
||
bgcolor="#55257D",
|
||
padding=ft.padding.all(10),
|
||
border_radius=10,
|
||
expand=True,
|
||
content=ft.Column(
|
||
scroll=ft.ScrollMode.AUTO,
|
||
expand=True,
|
||
controls=[
|
||
ft.Text("50-ричный калькулятор", size=20, color="white", weight=ft.FontWeight.BOLD),
|
||
ft.Text("Система счисления с основанием 50", size=11, color="#B0B0B0", italic=True),
|
||
ft.Text("Используйте: 0-9 (0-9), A-Z (10-35), a-n (36-49)", size=11, color="#B0B0B0"),
|
||
ft.Text("Примеры: 10₍₅₀₎=50₁₀, A₍₅₀₎=10₁₀, Z₍₅₀₎=35₁₀, n₍₅₀₎=49₁₀", size=10, color="#90EE90"),
|
||
ft.Row(
|
||
expand=True,
|
||
alignment=ft.MainAxisAlignment.CENTER,
|
||
vertical_alignment=ft.CrossAxisAlignment.START,
|
||
controls=[
|
||
ft.Column(
|
||
expand=True,
|
||
alignment=ft.MainAxisAlignment.START,
|
||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||
controls=[
|
||
self.input_left_1,
|
||
ft.Text("——— Вычитание", size=12, color="white"),
|
||
self.input_left_2,
|
||
ft.Container(
|
||
content=ft.Column([
|
||
self.result_left,
|
||
self.decimal_left
|
||
], horizontal_alignment=ft.CrossAxisAlignment.CENTER),
|
||
bgcolor="#3A1E51",
|
||
padding=10,
|
||
border_radius=5,
|
||
),
|
||
ft.CupertinoButton("=", on_click=self.calculate_subtraction),
|
||
ft.Container(
|
||
expand=True,
|
||
content=self.steps_left,
|
||
bgcolor="#1A0A2A",
|
||
padding=10,
|
||
border_radius=5,
|
||
)
|
||
]
|
||
),
|
||
ft.Column(
|
||
expand=True,
|
||
alignment=ft.MainAxisAlignment.START,
|
||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||
controls=[
|
||
self.input_right_1,
|
||
ft.Text("—|— Сложение", size=12, color="white"),
|
||
self.input_right_2,
|
||
ft.Container(
|
||
content=ft.Column([
|
||
self.result_right,
|
||
self.decimal_right
|
||
], horizontal_alignment=ft.CrossAxisAlignment.CENTER),
|
||
bgcolor="#3A1E51",
|
||
padding=10,
|
||
border_radius=5,
|
||
),
|
||
ft.CupertinoButton("=", on_click=self.calculate_addition),
|
||
ft.Container(
|
||
expand=True,
|
||
content=self.steps_right,
|
||
bgcolor="#1A0A2A",
|
||
padding=10,
|
||
border_radius=5,
|
||
)
|
||
]
|
||
),
|
||
ft.Column(
|
||
expand=True,
|
||
alignment=ft.MainAxisAlignment.START,
|
||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||
controls=[
|
||
self.input_mult_1,
|
||
ft.Text("——— Умножение", size=12, color="white"),
|
||
self.input_mult_2,
|
||
ft.Container(
|
||
content=ft.Column([
|
||
self.result_mult,
|
||
self.decimal_mult
|
||
], horizontal_alignment=ft.CrossAxisAlignment.CENTER),
|
||
bgcolor="#3A1E51",
|
||
padding=10,
|
||
border_radius=5,
|
||
),
|
||
ft.CupertinoButton("=", on_click=self.calculate_multiplication),
|
||
ft.Container(
|
||
expand=True,
|
||
content=self.steps_mult,
|
||
bgcolor="#1A0A2A",
|
||
padding=10,
|
||
border_radius=5,
|
||
)
|
||
]
|
||
),
|
||
ft.Column(
|
||
expand=True,
|
||
alignment=ft.MainAxisAlignment.START,
|
||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||
controls=[
|
||
self.input_div_1,
|
||
ft.Text("——— Деление", size=12, color="white"),
|
||
self.input_div_2,
|
||
ft.Container(
|
||
content=ft.Column([
|
||
self.result_div,
|
||
self.decimal_div
|
||
], horizontal_alignment=ft.CrossAxisAlignment.CENTER),
|
||
bgcolor="#3A1E51",
|
||
padding=10,
|
||
border_radius=5,
|
||
),
|
||
ft.CupertinoButton("=", on_click=self.calculate_division),
|
||
ft.Container(
|
||
expand=True,
|
||
content=self.steps_div,
|
||
bgcolor="#1A0A2A",
|
||
padding=10,
|
||
border_radius=5,
|
||
)
|
||
]
|
||
)
|
||
]
|
||
)
|
||
],
|
||
)
|
||
)]
|
||
e.page.update() |