muzclubs , таких игр море - это жанр "push the box" или "sokoban". :) Только, да, в жанре предусмотрено непосредственное взаимодействие игрока с коробкой.
Можно подумать, наверное, так.
Можно построить взвешенный граф оптимального перемещения. В этом графе должны быть не только свободные клетки, но и все препятствия на пути оптимального перемещения. По этому графу станет видно, нужно ли вообще двигать препятствия. Пусть изначально переход на место препятствия будет вдвое дороже перехода на свободную клетку.
А далее начинается "оптимизация" графа. Армянское Радио правильную мыслю подсказывает.
Оптимизация заключается в том, чтобы попробовать отодвинуть самые "дешевые" препятствия, всякий раз пересчитывая стоимость всего пути.
Еще можно упростить себе жизнь, задав потенциальное поле удорожания маршрута на клетке каждого препятствия. В этом случае первым этапом будет создание потенциального поля. Оно создается от наиболее близких к свободным клеткам препятствий к центру их массы и буквально определяет удорожание пути при движении сквозь препятствия. А вторым шагом будет простой A*.
muzclubs , Мне это напоминает один жанр игрушек. Уж не бота ли ты решил для таких игрушек сделать? :)
Тебе надо немного подольше покурить предложенные материалы. Я чую там разгадку. Только ты дал очень мало информации по своей задаче.
Скажем, пусть препятствиями будут коробки. Игрок их только от себя толкать может? А что если на пути толчка вплотную еще одна коробка стоит? Или же игрок может просто взять коробку и переместить ее на любую другую свободную клетку вокруг себя? А ходить можно так же в 8 направлениях? А если хочется сходить по диагонали когда противоположная диагональ занята коробками, то что? Взаимодействие с коробкой является ходом или нет? И при взаимодействии игрок перемещается на место коробки или нет?
Я так весь день вопросы задавать могу. :) Думаю, если выделишь максимум возможных правил - решение с A* само подтянется.
LittleBuster , читерить - плохо. За это приходит злой одмин и люто банит. :)
Плюс, если ты выяснил что там есть защита, то неплохо было бы ее назвать.
Плюс, DirectInput/XInput никто не отменял, а с ними уже только хирургически сладить можно.
Плюс, раз уж там есть защита ввода, то и DRM должен быть, а это значит что и хирургически без тайных скиллов вмешаться вряд ли получится.
pashaa , Ты уж извини. Этому коду поможет только стирание. Там ошибки в каждой строчке, да и смысл не ловится. Этот код надо просто переписать.
И сам вопрос слишком прост, чтобы на него отвечать. В поисковике есть все ответы.
UnicodeDecodeError: 'ascii' codec can't decode byte 0xec in position 0: ordinal not in range(128)
Опять же, последняя строчка... У тебя в консоли кодировка странная. chcp даст нужную информацию и функционал.
По части запуска pip ты тоже делаешь неправильно. Если тебе нужен pip 3-го питона, то команда должна быть pip3.
Mercury13 , а тут, понимаешь, какой вопрос, такой и ответ... Размыто всё, конкретики никакой. Структуры бывают разные, программы бывают свои и чужие, форматы - текстовые, бинарные и не только.
Это просто всё, что мне пришло в голову при чтении вопроса. :)
Форматы файлов для меня далеко не пустой звук. Подсказать могу многое. Главное - это правильно задать вопрос...
Ну и мне совершенно не важно, насколько мой ответ решает вопрос в его описании. Мне важно просто поделиться имеющейся информацией и, возможно, подтолкнуть человека к дальнейшей прагматике по вопросу.
Cyapa , да, если будут вопросы, ты можешь или тут их задавать, или оформлять новый вопрос и сразу приглашать меня. :)
Новый вопрос будет даже лучше, чтобы все люди могли поделиться своими навыками.
А, я уже рисовал себе в уме какую-нибудь гирлянду из десятков устройств на проводах. :)
Побуду немного теоретиком кайфа и накидаю видение этого подхода.
Первое, чем надо озаботиться - это таймеры и их планировщик.
Это простая штука, ее обычно делают на основном потоке, в главном цикле приложения. Такой планировщик задействуется с определенной дискретностью (обычно 50-150 раз в секунду) для активации сработавших таймеров.
Любители особой экзотики или больших нагрузок делают для таймеров отдельную очередь задач для работника из пула потоков (можно сделать так, чтобы задачи из очереди таймеров вынимались только в соответствующее время, а в остальное время - чтобы работник разбирал другие очереди).
Что тут есть пул потоков и работники. Пул потоков предоставляет только управление небольшим количеством потоков. Потоки создаются при инициализации пула и живут до его деинициализации. Работник – это цикл обработки задач. Задачи поступают из очередей. Очередь задач помечается некоторым семейством. Вон, если применять асинхронный планировщик таймеров, то для него понадобится свое отдельное семейство и очередь (и эта очередь должна отдавать задачи только для уже сработавших таймеров). Задача всегда планируется для конкретной очереди.
Для каждого потока из пула потоков создается работник и передается в этот поток. Работнику назначается набор семейств очередей с установленными приоритетами. При наличии задач в очереди, работник забирает не все, уповая на то, что эту очередь обслуживают несколько работников. Обычно из очереди берут не больше N задач. Для очередей характерна синхронизация семафором, т.е. работники просто усыпляются на ожидании задач в случае их отсутствия.
Но есть еще один работник, чье поведение нехарактерно – это работник главного потока. Он не блокирует свой поток при отсутствии задач и для него определяются свое отдельное от всех семейство и очередь задач. Этот работник выполняет только то, что непременно надо выполнить в главном потоке процесса.
Каждую задачу можно сопроводить специальным обработчиком, который автоматически планируется в необходимую очередь сразу после завершения задачи. Во время планирования в этот обработчик передается результат выполнения задачи. Во время планирования задачи в ее очередь, к задаче добавляется этот самый обработчик с пометкой его очереди. Это дает возможность саму задачу выполнить асинхронно, а ее результат, например, обработать в главном потоке. Таким же образом можно организовать цепное планирование задач в зависимости от результатов предыдущих задач в цепи. Это очень простая схема проактора, полноценный проактор нам тут и не нужен.
Теперь - определение наличия нового устройства. Диспетчер новых устройств - это такой же актер, в список дел которого входит мониторинг оборудования и создание актеров для каждого нового устройства. Его полезно сделать актером потому, что может потребоваться сделать мониторинг асинхронным. Именно диспетчер новых устройств можно подписать на уведомление о новых устройствах.
В момент обнаружения нового устройства для него создается актер. Актер устройства планирует себе таймер для проверки живости устройства. В общем смысле это называется ping/heartbeat service. Если подключенное устройство само с некоторым интервалом отсылает сигнал своей живости - это heartbeat, его можно просто читать и обновлять состояние живости. В ином случае надо делать схему ping-pong с интервалом ожидания ответа(pong). Для ping service удобно запланировать два таймера, из которых только один будет включен в одно конкретное время. Ping-таймер отмеряет интервал между отсылкой пинга. Отсылка пинга останавливает Ping-таймер и запускает Pong-таймер. Последний считает интервал ожидания ответа от устройства. Таймеры снова меняются местами в момент получения ответа от устройства или истечения времени Pong-таймера. Именно в этом месте можно реализовать поведение при потере устройства.
Прием данных с устройства делается тоже по таймеру с маленьким интервалом. По этому таймеру планируется только опрос буфера чтения (эмм... у тебя там сокеты?) на возможность чтения.
А фактическое чтение выполняется только в случае успеха первой задачи (т.е. если проверка показала что читать можно).
Это - планирование двух асинхронных увязанных задач (проверка + чтение) с условием, что вторая задача выполнится только если результат первой задачи успешен.
Callback задачи чтения производит диспетчеризацию принятого буфера с помощью работника основного потока.
Для клиента устройства актер и будет этим самым устройством. Клиент использует публичный интерфейс актера для общения с устройством. Сам актер, по запросу клиента формирует или одну задачу, или цепочку задач.
Скажем, надо получить с устройства некоторые данные. У клиента устройства есть хендлер для этих данных. Клиент вызывает функцию актера устройства, передавая туда хендлер. Актер планирует задачи:
1) послать на устройство запрос данных из потока любого свободного работника (вот тут я не спросил, синхронные ли эти запросы);
2) вызвать клиентский хендлер с принятыми данными в момент их диспетчеризации.
Все остальные моменты, пожалуй, лучше уже тебе самостоятельно обдумать.
Cyapa , дьявол в деталях - это правда. :)
Смотри, в таком случае мне нужна информация. Ты случайно не IoT-хаб проектируешь?
Как узнается что в системе появилось еще одно устройство, с которым надо взаимодействовать?
Как у тебя сейчас сделано, чем именно занимаются потоки для устройств?
У меня еще рабочее время, разница между нами - 5 часов, а такой объем писанины и сторонней прагматики я смогу себе только после работы позволить. :)
Пул потоков в данном случае представлен пулом работников для параллельной обработки задач. Задачей является любой сервис (тик, посылка команды, проверка наличия данных в буфере приема, чтение буфера приема и.т.д) одного конкретного девайса.
Число работников в пуле фиксировано, никак не связано с числом девайсов и значительно меньше числа подключенных девайсов (2/4/8 работников против 20+ девайсов). Люди любят делать число работников в пуле по числу ядер процессора, крепить работников к отдельным ядрам и типа наслаждаться слабыми коллизиями планирования.
Помимо работников у тебя еще всегда есть главный поток процесса, который тоже является работником, но выполняет только те задачи, которые нужно выполнить только в главном потоке (например, запуск callback функций после выполнения асинхронных задач).
Сервисом конкретного устройства занимается один конкретный актер (Actor model). Актер занимается планированием задач сервиса устройства (используя схему Reactor/Proactor). Сервисом устройства можно назвать опрос на предмет потери соединения с устройством, пинг устройства, посылку команд на устройство, проверку ответа от устройства, может что-нибудь еще...
Актер устройства планирует действие, которое должно быть выполнено асинхронно одним из работников в потоке работника.
Почему именно Proactor, а не Reactor. Реактор применяется тогда, когда необходимо приостановить работу вызывающего потока на время асинхронной операции. Выглядит это как простой вызов функции. Чтение из файла или сокета в синхронном режиме, для примера.
Проактор применяется тогда, как есть возможность запланировать некоторые действия в определенной последовательности - поставить callback на исполнение после асинхронной операции.
Я думаю, Проактор для тебя может оказаться очень удобным.
Почему я предложил именно такую схему. По описанию я увидел что потоками ты задумал облегчить себе работу. Сделать по потоку на устройство, а внутри потока у тебя типа обычная синхронная программа с синхронным сервисом устройства. Так ведь?
В любом случае это означает что потоки у тебя большую часть времени спят. Именно в этом случае куда удобнее исправить все на предложенную схему. Плюсом, сервис устройств станет удобнее, особенно если разделишь задачи по пингу/проверке живости устройства с задачами отсылки/проверки наличия/приема данных.
В код я не смотрел, поэтому на ответ, а коммент. Однако, по описанию это никакой не circle sort, это quick sort с кривоватой и слабо понятной реализацией. Его можно и переписать более компактно, и эффективность повысить. В коде написаны, прям говоря, страсти... algolist.manual.ru/sort/quick_sort.php
Airat1995 , на счет шил и мыла тут все просто. Проблема, которую ты встретил и описал в этом вопросе, всегда присутствовала. "Как же нам решить проблему с перегруженной функцией, принимающей как указатель, так и целое число, для случая передачи в нее NULL?"
И многие люди решали эту проблему вводя самопальный тип cNull и константу Null (или как-то еще). Эти тип и константа всегда и полностью повторяли функционал nullptr с его типом. Проблема была в отсутствии стандарта на эту тему. Представь себе проект с 5-10 библиотек, в каждой из которых свой самопальный nullptr. :)
Теперь есть стандарт, есть nullptr и std::nullptr_t. Порядок восстановлен. Самое важное - это строгая типизация nullptr - он способен приводиться только к типам указателей.
Теперь касательно нетипизированного нуля. Давай условимся на одном правиле: почаще и побольше проверять свои догадки. :)
Я пользуюсь сразу двумя сервисами для этого: cpp.sh и gcc.godbolt.org.
Первый позволяет быстро покрутить код в работе, а второй позволяет заглянуть в нутро кода после его обработки разными компиляторами.
Вот, посмотри на эти примеры: cpp.sh/9es6c https://godbolt.org/g/wUS6Ju
Думаю, с этими примерами тебе все станет еще понятнее. Особенно - полезность введения в стандарт nullptr.
Ivan Sokolov , в библиотеках для встраиваемых систем NULL может иметь осмысленный адрес, обращение по которому запускает аварийный функционал контроллера. В иных ситуациях NULL может так и остаться нулем, просто все тот же аварийный код запускается уже через прерывание при попытке обращения по нулевому адресу. Но это все касается только макроса NULL и довольно старых библиотек для встраиваемых систем.
Но все это не касается самого вопроса. Стандарт однозначен и понятен, более того - очевиден.
А самое очевидное - то, что использование nullptr вместо NULL или 0 сразу избавляет от неоднозначности выбора между целочисленным аргументом и указателем.
Но вот использование нетипизированного нуля может привести только к неоднозначности.
romy4 , Ivan Sokolov , Airat1995 , числовой литерал 0 является свободно конвертируемым, он автоматически приводится к любому простому типу, включая указатели и вещественные числа. Константа nullptr тут не при чем и никак явно с нулем не связана. Airat1995 использовал 0, а не nullptr, поэтому у него и появилась неоднозначность перегрузки.
Можно подумать, наверное, так.
Можно построить взвешенный граф оптимального перемещения. В этом графе должны быть не только свободные клетки, но и все препятствия на пути оптимального перемещения. По этому графу станет видно, нужно ли вообще двигать препятствия. Пусть изначально переход на место препятствия будет вдвое дороже перехода на свободную клетку.
А далее начинается "оптимизация" графа. Армянское Радио правильную мыслю подсказывает.
Оптимизация заключается в том, чтобы попробовать отодвинуть самые "дешевые" препятствия, всякий раз пересчитывая стоимость всего пути.
Еще можно упростить себе жизнь, задав потенциальное поле удорожания маршрута на клетке каждого препятствия. В этом случае первым этапом будет создание потенциального поля. Оно создается от наиболее близких к свободным клеткам препятствий к центру их массы и буквально определяет удорожание пути при движении сквозь препятствия. А вторым шагом будет простой A*.