Шарики 115 уровень какое решение
Перейти к содержимому

Шарики 115 уровень какое решение

Фонтаны из шаров

Фонтаны из шаров — это один из самых популярных элементов декора на различных мероприятиях. Они создают неповторимую атмосферу праздника, привлекают внимание гостей своей красотой и оригинальностью. Композиции могут быть выполнены в любом стиле и цветовой гамме, что делает их универсальными для любой тематики торжества. Заказать их с доставкой можно в компании «Онлишар».

Что такое фонтан из воздушных шаров и для каких мероприятий подойдёт?

Покупайте фонтаны из шаров у нас

Гелиевые шарики

Воздушные шарики с гелием девочке

Шарики на юбилей

Человек-паук

Шары для мальчика

Фонтан из шаров

Фонтан из воздушных шаров

Гелиевые шары

Шары с конфетти

Фольгированные шары

Минни маус

Фонтан из шариков

Шары bubble

Композиция из шаров со звёздами

Свинка пеппа

Микки маус

Доставка шаров

Холодное сердце

Показать еще

ПОЧЕМУ ИМЕННО В ОНЛИШАР ВЫ ДОЛЖНЫ КУПИТЬ ВОЗДУШНЫЕ ШАРЫ?

Желтый, красный и белый воздушные шары

АССОРТИМЕНТ

Большой выбор моделей воздушных шариков для любого мероприятия

Желтый, красный и белый воздушные шары

ДОСТУПНАЯ ЦЕНА

Наши цены доступны для любого покупателя

ГАРАНТИЯ ПОЛЁТА

Гарантия полета воздушных шаров от 2-х дней и более (за счет обработки шаров специальным составом Hi-float)

Много разноцветных шариков

Высокое качество изготовления, только европейский товар

Шарики С Днём рождения Геймер

Сегодня — время порадовать себя и своих близких! А что прекраснее подойдет для этого, чем красочные воздушные шары с персонажами любимых мультфильмов?

Шарики С Днём рождения Геймер отличаются высоким качеством и яркостью. Не упустите возможность сделать торжество еще более запоминающимся. У нас есть множество товаров с различными героями из мультяшной вселенной. Выбирайте любимых персонажей и заказывайте эффектные воздушные шары для большого или камерного праздника прямо сейчас!

Вам не нужно тратить свое время на длительный поиск украшений и их самовывоз — мы сделаем все за вас! Добавляйте Шарики С Днём рождения Геймер в корзину, сразу же оформляйте заказ и наслаждайтесь ярким праздником в кругу родных.

— Оплата наличными курьеру

— Оплата на карту Сбербанк России

— Банк ТОЧКА номер карты 5140 1702 1407 1079

— Круглосуточная доставка воздушных шаров с гелием по Москве и Московской области

— Доставка по районам ЦАО ,САО, СВАО, ВАО, ЮВАО, ЮАО, ЮЗАО, ЗАО, СЗАО, Зеленоград

— Приём заказов по телефону с 9:00 до 22:00 без выходных

— Самовывоз с 9:30 до 20:30 без выходных

Минимальная сумма для заказа 1000 рублей, без учёта стоимости доставки (самовывоз в любом количестве от 1 шт)

Уважаемые покупатели учитывайте пробки в часы пик. Время доставок указано примерное, учитывая дорожную ситуацию водитель может задержаться в указанное время доставки, ситуации бывают разные, просьба отнестись с пониманием. Заказывайте заранее.

Шарики С Днём рождения Геймер

Научный форум dxdy

Привет!
Решаю следующую задачу из международной олимпиады школьников по математике.

Цитата:
Какое максимальное количество шаров диаметра 1 можно уложить в коробку размерами 10х10х1?

Сначала подумал, что в ответе 100. Но это неправильный ответ.
Потом, подумал, что, если крышка коробки не закрыта, а, вернее, ее просто
нет?И смысл задачи сводится к построению «горки» из шариков до последнего.

Порядок действий таков:
первый уровень — 10 на 10 шариков.
второй уровень — 9 на 9 шариков.
и так далее до.
9 уровень — 2 на 2 шарика.
10 уровень — 1 шарик.

Ответ получается -385 шариков.
Но он тоже неправильный.
В чем я ошибаюсь? Как решить эту задачу?

Re: Шары в коробке
06.07.2009, 00:52
а какой ответ считается правильным?
Re: Шары в коробке
06.07.2009, 01:06

Это ссылка на задачу.
Ответ не известен))

Еще пробовал ставить 11 на 11 шаров на второй уровень, но тоже не так((

Re: Шары в коробке
06.07.2009, 01:46

гм. но если размеры корочки заданы 3-х мерные, то все-таки наверное она типа с крышкой, т.е. помещать надо в эти размеры, иначе зачем указывать это 1?! с толку сбить? вряд ли, я думаю.
Если плотно класть шары по диагонали, получается 61, но это меньше 100.

— Пн июл 06, 2009 02:49:35 —

да, а их можно на кусочки резать? или ногами утаптывать?

— Пн июл 06, 2009 03:30:21 —

А, дошло! 105, верно?
Кладутся первые 10 шаров, следующие 9 в точности касаются с шарами из 1-го ряда, всего — 11 рядов, 6 — по 10, 5 — по 9, итого — 105, но я не знаю, как доказать, что нельзя больше.

Re: Шары в коробке
06.07.2009, 04:59

Да вот, походу, неправильно.
Я указал ссылку. на сайте проверяют ответ.
Уже кучу вариантов перебробовал, но все равно не то.
Не знаю, может ошибка в проверочной системе.

Мой ответ пока что — 385. Делаем пирамиду до последнего шарика.
Буду благодарен человеку, который сможет решить и доказать ,что его ответ единственно правилен))

Re: Шары в коробке
06.07.2009, 07:37

Заслуженный участник

Как минимум 105 (если укладывать шары шестиугольной мозаикой; получится 11 рядов по 9-10 шаров в каждом). Но за последним рядом остается еще достаточно места, чтобы, перетасовав шары последнего ряда, «втиснуть» туда еще несколько шаров (сколько — не знаю).

Re: Шары в коробке
06.07.2009, 08:14

Заслуженный участник

Последний раз редактировалось venco 06.07.2009, 08:51, всего редактировалось 1 раз.

Высота не зря задана.
В прямоугольник 8х10 вмещаются 5 рядов по 10 и между ними 4 ряда по 9 в гексагональной упаковке. Плюс ещё 2 ряда по 10.
Всего — 106.

Re: Шары в коробке
06.07.2009, 08:45

Заслуженный участник

EtCetera в сообщении #226763 писал(а):

Как минимум 105 (если укладывать шары шестиугольной мозаикой; получится 11 рядов по 9-10 шаров в каждом). Но за последним рядом остается еще достаточно места, чтобы, перетасовав шары последнего ряда, «втиснуть» туда еще несколько шаров (сколько — не знаю).

Как минимум 106: запаса хватит, чтобы один ряд по 9 превратить в по 10, а вот дальше ресурсов вроде не видать.

Re: Шары в коробке
06.07.2009, 10:18

Рассмотрим сечение коробки на высоте <img decoding=.5$» />. Каждый шар там дает круг диаметра 1. Поэтому из соображений площади получим $\frac<n\pi>.</p> <p>Это слишком грубая оценка.<br />Рассмотрим для каждого шара те шары, которые его «окружают». в срединной плоскости рассмотрим радикальную ось шара и каждого его соседа. Эти радикальные оси образуют выпуклый многоугольник, содержащий наш круг. и не пересекающийся с другими кругами. Интуитивно очевидно (но доказывать мерзко наверняка), что оптимальный вариант — шесть шаров касаются нашего. Получаем правильный шестиугольник с радиусом вписанной окружности <img decoding=$» />, его площадь $\frac<\sqrt<3>>$» />. Это дает оценку 115.</p> <p>Наконец, следует проделать аналог этой операции с краями — у краев много пустого места.</p> <h2>Взламываем Ball Sort Puzzle</h2> <p><img fetchpriority=

Ball Sort Puzzle — это популярная мобильная игра на IOS/Android. Суть её заключается в перестановке шариков до тех пор, пока в колбах не будут шарики одного цвета. При этом шарик можно перетаскивать либо в пустую колбу, либо на такой же шарик.

Так случилось, что я в неё залип. Очнулся примерно через месяц, на 725 уровне. Он мне никак не давался — насколько бы глубоко я ни пытался продумать свою стратегию. В итоге — с этим вопросом я вышел в интернет, и заодно выяснил несколько интересных особенностей головоломки.

Во-первых, — игра бесконечна почти бесконечна. По крайней мере уже сейчас на YouTube есть прохождения всех уровней вплоть до 5350, а в телеграмме гуляют скриншоты 10к+ уровней. Вторая особенность, и вот это уже некрасиво, — не у всех уровней есть решение.

Ну это ни в какие ворота — против нас играет коварный ИИ. Нужно действовать соответственно!

  • Придумаем алгоритм, решающий эту головоломку (Python)
  • Научимся парсить скриншот игры, чтобы скармливать алгоритму задачки (OpenCV)
  • Напишем телеграм бота, который будет принимать скриншоты и возвращать решения
  • Выстроим CI/CD через GitHub Actions и задеплоим бота на Яндекс.Функции

Алгоритмическое решение задачи

Решать такую задачу было весьма занимательно. Поэтому предлагаю заинтересованному читателю попробовать решить её самостоятельно.

Я же в первую очередь решил побить проблему на сущности. Это сделает алгоритм чуть более элегантным, а также поможет в будущем парсить скриншоты игры:

class Color: def __init__(self, symbol, verbose_name, emoji): self.symbol = symbol self.verbose_name = verbose_name self.emoji = emoji def __repr__(self) -> str: return f'Color()' def __str__(self) -> str: return self.emoji

Beta-редактор хабра ломается на рендеринге emoji :poop:

class Ball

class Ball: def __init__(self, color: Color): self.color = color def __eq__(self, other: 'Ball'): return self.color is other.color def __repr__(self): return f'Ball()' def __str__(self) -> str: return str(self.color)

class Flask

class Flask: def __init__(self, column: List[Color], num: int, max_size: int): self.num = num self.balls = [Ball(color) for color in column] self.max_size = max_size @property def is_full(self): return len(self.balls) == self.max_size @property def is_empty(self) -> bool: return not self.balls def pop(self) -> Ball: return self.balls.pop(-1) def push(self, ball: Ball): self.balls.append(ball) def __iter__(self): return iter(self.balls) def __getitem__(self, item: int) -> Ball: return self.balls[item] def __len__(self) -> int: return len(self.balls) def __str__(self) -> str: return str(self.balls) 

class Move

class Move: def __init__(self, i, j, i_color: Color): self.i = i self.j = j self.emoji = i_color.emoji def __eq__(self, other: 'Move') -> bool: return (self.i, self.j) == (other.i, other.j) def __repr__(self) -> str: return f'Ball()' def __str__(self) -> str: return f' -> ' 

Для решения будем использовать метод поиска с возвратом (Backtracking).

Решение задачи методом поиска с возвратом сводится к последовательному расширению частичного решения. Если на очередном шаге такое расширение провести не удается, то возвращаются к более короткому частичному решению и продолжают поиск дальше. Данный алгоритм позволяет найти все решения поставленной задачи, если они существуют.

В случае с нашей игрой это метод применяется так: мы рекурсивно обходим все возможные перестановки шариков (move) до тех пор, пока

  • Либо нас не выкинет наш критерий остановки — решённый пазл
  • Либо в нашем хранилище состояний ( states ) не будет всех возможных перестановок — в таком случае решения нет
 def solve(self) -> bool: if self.is_solved: return True for move in self.get_possible_moves(): new_state = self.commit_move(move) if new_state in self.states: # Cycle! self.rollback_move(move) continue self.states.add(new_state) if self.solve(): return True self.rollback_move(move) return False

Алгоритм достаточно прямолинейный и далеко не всегда выдаёт оптимальное решение. Тем не менее он справляется с решением большинства задачек из игры за 1 сек.

Проверим алгоритм на чём-нибудь попроще:

def test_3x3(): data_in = [ [color.RED, color.GREEN, color.RED], [color.GREEN, color.RED, color.GREEN], [], ] puzzle = BallSortPuzzle(data_in) result = puzzle.solve() assert result is True play_moves(data_in, puzzle.moves)

Алгоритм в действии

Полная версия программы доступна на github.

Распознавание скриншотов игры

Мы будем работать с .jpg картинками двух видов

Скриншоты уровней игры

Каждый чётный раунд игры состоит из 11 колб и 36 шариков, а нечётный — 14 колб и 48 шариков. Чётные и нечётные раунды отличаются расположением колб, но на счастье всё остальное у них одинаковое — по 4 шарика в колбе, 2 колбы пустые, цвета используются одни и те же.

Первое, что хочется сделать — это обрезать рабочую область, оставив только колбы с шариками. В противном случае наша программа за шарики может принимать элементы управления, фон или рекламу. Для этого отрежем по четверти сверху и снизу изображения.

class ImageParser: def __init__(self, file_bytes: np.ndarray, debug=False): self.image_orig = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR) self.image_cropped = self.get_cropped_image(self.image_orig) @staticmethod def get_cropped_image(image): height, width, _ = image.shape quarter = int(height / 4) cropped_img = image[quarter : height - quarter] return cropped_img

Рабочая область

Теперь будем искать кружочки. В библиотеке OpenCV ровно для этих целей существует метод HoughCircles. Чтобы его использовать нужно перевести изображение в чёрно-белый вид, а также «эмпирически подобрать» параметры поиска. Чтобы найденные кружочки потом расфасовать по колбам, нормализуем и отсортируем их.

 @staticmethod def normalize_circles(circles): last_y = 0 for circle in circles: if math.isclose(circle[1], last_y, abs_tol=3): circle[1] = last_y else: last_y = circle[1] return circles def get_normalized_circles(self) -> List[Any]: image_cropped_gray = cv2.cvtColor(self.image_cropped, cv2.COLOR_BGR2GRAY) circles = cv2.HoughCircles(image_cropped_gray, cv2.HOUGH_GRADIENT, 2, 20, maxRadius=27) if circles is None: raise ImageParserError("No circles :shrug:") circles = np.round(circles[0, :]).astype("int16") ind = np.lexsort((circles[:, 0], circles[:, 1])) circles = circles[ind] circles = self.normalize_circles(circles) ind = np.lexsort((circles[:, 0], circles[:, 1])) circles = circles[ind] return circles

Отсортированные шарики слева-направо, сверху-вниз

Дальше будем определять цвет шарика.

Из-за того, что Telegram жмёт картинки мы не можем просто взять цвет центрального пикселя — он может быть артефактом компрессии. Поэтому найдём доминирующий цвет, — тот который в кружочке встречается чаще всего.

 @staticmethod def get_dominant_color(circle) -> Color: colors, count = np.unique(circle.reshape(-1, circle.shape[-1]), axis=0, return_counts=True) dominant = colors[count.argmax()] return dominant

Найденные кружочки

Этот доминирующий цвет мы будем сравнивать с изначально заданными цветами, и искать ближайший. В данной задаче нам на помощь приходит Евклидово расстояние и теорема Пифагора: если представить цвет точкой в трёхмерном пространстве, то расстояние до любой другой точки этого пространства:

Посчитаем такое расстояние до каждого из изначально заданных цветов и найдём минимальное

RBG_TO_COLOR = < (147, 42, 115): VIOLET, (8, 74, 125): BROWN, (229, 163, 85): L_BLUE, (68, 140, 234): ORANGE, (196, 46, 59): BLUE, (51, 100, 18): GREEN, (35, 43, 197): RED, (87, 216, 241): YELLOW, (125, 214, 97): L_GREEN, (123, 94, 234): PINK, (16, 150, 120): LIME, (102, 100, 99): GRAY, >COLORS = np.array(list(RBG_TO_COLOR.keys())) def get_closest_color(color: np.ndarray) -> Color: distances = np.sqrt(np.sum((COLORS - color) ** 2, axis=1)) index_of_smallest = np.where(distances == np.amin(distances)) smallest_distance = COLORS[index_of_smallest].flat return RBG_TO_COLOR[tuple(smallest_distance)] # type: ignore

Далее нам остаётся только распределить шарики по колбам. Итоговый class ImageParser доступен на github.

Преобразуем программу в Telegram Bot

Узнать про то, как сделать телеграм бота на Python можно сразу из нескольких статей на хабре. Я лишь опишу пару нюансов, с которыми столкнулся.

Так как наш бот хостится на Яндекс.Функции — триггером к его запуску будет запрос на заданный нами webhook.

Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url, containing a JSON-serialized Update.

Если в сообщении есть массив photo , то можно увеличить вероятность распознавания шариков выбрав фотографию с максимальным весом:

if photos := message.get('photo'): # here photos is an array with same photo of different sizes # get one with the highest resolution hd_photo = max(photos, key=lambda x: x['file_size'])

Чтобы скачать картинку, придётся сделать 2 запроса к Telegram API

# Получение данных о файле, нас интересует ключ ответа file_path GET https://api.telegram.org/bot/getFile?file_id= # Получение самого файла GET https://api.telegram.org/file/bot/

В остальном же всё просто — получаем картинку, скармливаем её парсеру и затем алгоритму-решателю.

def handler(event: Optional[dict], context: Optional[dict]): body = json.loads(event['body']) # type: ignore print(body) message = body['message'] chat_id = message['chat']['id'] if photos := message.get('photo'): # here photos is an array with same photo of different sizes hd_photo = max(photos, key=lambda x: x['file_size']) # get one with the highest resolution try: file = telegram_client.download_file(hd_photo['file_id']) except TelegramClientError: text = "Cant download the image from TG :(" else: file_bytes = np.asarray(bytearray(file.read()), dtype=np.uint8) try: image_parser = ImageParser(file_bytes) colors = image_parser.to_colors() except ImageParserError as exp: text = f"Cant parse image: " else: puzzle = BallSortPuzzle(colors) # type: ignore solved = puzzle.solve() if solved: text = get_telegram_repr(puzzle) else: text = "This lvl don't have a solution" else: return < 'statusCode': 200, 'headers': , 'body': '', 'isBase64Encoded': False, > msg = < 'method': 'sendMessage', 'chat_id': chat_id, 'text': text, 'parse_mode': 'Markdown', 'reply_to_message_id': message['message_id'], >return < 'statusCode': 200, 'headers': , 'body': json.dumps(msg, ensure_ascii=False), 'isBase64Encoded': False, >

Отмечу ещё один нюанс: телеграм очень строго следует политике экранирования спецсимволов. Для Markdown это:

To escape characters ‘_’, ‘*’, ‘`’, ‘[‘ outside of an entity, prepend the characters ‘\’ before them.

Любой такой неэкранированный символ — и вы не увидите ответа в телеграм-чате. И останется только гадать — является ли это ошибка интеграции или вот такой коварный баг. Будьте осторожны.

Деплой бота в Яндекс.Функцию

Про создание Я.Функции также есть отличная статья от @mzaharov. Там подробно описан процесс заведения функции, а также установки вебхука для телеграмм бота.

Я расскажу как сделал Continuous Delivery при помощи GitHub Actions. Каждая сборка мастера увенчивается деплоем новой версии функции. Такой подход заставляет придерживаться модели разработки GithubFlow с его главным манифестом

Anything in the master branch is always deployable.

Каждая сборка мастера состоит из 3ёх этапов

  • lint (black, flake8, isort, mypy) — проверка кода на соответствие всем стандартам Python 2020
  • test — тестируем программу с помощью pytest, поддерживая качество и покрытие кода
  • deploy — непосредственно заливаем новую версию приложения в облако

Деплоить будем с помощью Yandex-Serveless-Action — уже готового Action для использования в своих пайплайнах

 deploy: name: deploy needs: pytest runs-on: ubuntu-latest if: github.ref == 'refs/heads/master' steps: - uses: actions/checkout@master - uses: goodsmileduck/yandex-serverless-action@v1 with: token: $> function_id: $> runtime: 'python38' memory: '256' execution_timeout: "120" entrypoint: 'main.handler' environment: "\ TELEGRAM_BOT_TOKEN=$>" source: 'app'

Переменные окружения программы и сборки спрячем в GitHub Secrets на уровне репозитория.

Результат

Бота можно найти в telegram по позывному @ballsortpuzzlebot.

Присоединяйтесь к маленькому community любителей этой игры в telegram. Бот был добавлен в группу и внимательно следит за всеми отправленными картинками.

Бонус! Уровни, у которых нет решения

Lvl 4091Lvl 6071

Мой алгоритм умывает руки — говорит что перебрал все возможные комбинации и решения нет. Возможно это баг алгоритма.. или QA-отдел мобильной игры просто забил на эти уровни, так как не предполагал, что кто-то так далеко зайдёт)

Заключение

Для меня это был интересный опыт скрещивания технологий (Telegram API + Python + OpenCV + Lambda). Надеюсь он окажется полезен кому-нибудь ещё.

Найденные баги, предложения по оптимизации алгоритма, или даже PR в репозиторий крайне приветствуются

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *