Победа! Было очень сложно, но вы справились, поздравляю!
+
+
+
+
+ Упс, железяка победила. Оказывается, предсказать выбор человека
+ не так уж и сложно, да?
+
+
+
+
+
{{ predictor.score }}
+
Ход {{ step }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/content.config.ts b/src/content.config.ts
new file mode 100644
index 0000000..4a24c4c
--- /dev/null
+++ b/src/content.config.ts
@@ -0,0 +1,15 @@
+import { defineCollection, z } from 'astro:content';
+import { glob } from 'astro/loaders';
+
+const articles = defineCollection({
+ loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/articles' }),
+ schema: z.object({
+ title: z.string(),
+ description: z.string().optional(),
+ keywords: z.array(z.string()).optional(),
+ tags: z.array(z.string()).optional(),
+ draft: z.boolean().default(false),
+ }),
+});
+
+export const collections = { articles };
diff --git a/src/content/articles/2019-05-01-predictor.mdx b/src/content/articles/2019-05-01-predictor.mdx
new file mode 100644
index 0000000..eba9697
--- /dev/null
+++ b/src/content/articles/2019-05-01-predictor.mdx
@@ -0,0 +1,134 @@
+---
+title: Гадалка Шеннона
+description: Демо-версия электронной гадалки Шеннона
+keywords: [гадалка, угадыватель, шеннон, чет-нечет]
+---
+import PredictorDemo from '../../components/PredictorDemo.vue';
+
+В студенческое время я наткнулся на интересную статью об [игре "Чет-нечет"][game]
+на домашней страничке пользователя [ltwood][ltwood].
+
+Правила очень простые. Игрок загадывает один вариант из двух: "чет" или "нечет",
+а оппонент пытается угадать выбор игрока. Если угадать не удалось, то очко получает
+загадавший, а если угадать получилось - то угадывающий. Кто первым наберет 20 очков,
+тот и молодец!
+
+Кажется, что в этой игре все случайно. Случайно загадывается число, потом случайно
+второй игрок пытается угадать что же было загадано. Я очень сильно удивился, когда
+попробовал поиграть в эту игру с программой и за десять попыток так ни разу и не выиграл.
+
+Парадокс в том, что мы _думаем_ что загадываем числа случайно. На самом деле все не так,
+и последовательность загаданных чисел не случайна.
+
+Исходного кода оригинальной гадалки в открытом доступе нет, есть только [описание алгоритма][algo],
+по которому я сделал свою реализацию на TypeScrypt.
+
+## Демоверсия
+
+Попробуйте набрать 50 очков и выиграть. Чтобы выбирать вариант с клавиатуры,
+кликните внутри серой рамки, а потом пользуйтесь клавишами "1" - нечет или "2" - чет.
+
+---
+
+
+
+---
+
+## Как Это работает
+
+Математически алгоритм на [странице][algo] сайта ltwood.
+Я рассмотрю простой пример, чтобы показать принцип.
+
+В основе алгоритма находится популяция "демонов" - автоматов, которые на основании ходов
+игрока и предсказанных значениях выдают новое предсказание. Демонами управляет
+супервайзер. Задача супервайзера в том, чтобы опросить всех демонов, выбрать ответ
+от одного их них, а после получения ответа игрока пометить тех, кто выдал правильный ответ.
+
+Алгоритм состоит из двух шагов:
+
+- предсказать следующих ход игрока;
+- учесть реальный ход игрока, добавив веса тому демону, который предугадал ход.
+
+Рассмотрим работу на примере одного демона.
+
+Пусть у нас есть демон, который смотрит на последний хода игрока
+и на свое последнее предсказание.
+
+Строим два вектора:
+
+- `[<1 ход демона>, <1 ход игрока>, 0]`
+- `[<1 ход демона>, <1 ход игрока>, 1]`
+
+В самом начале, когда у демона нет никакой информации о ходах игрока, эти векторы
+будут выглядеть как `[0]` и `[1]`. Но с накоплением данных, они всегда будут каждый
+по 5 элементов.
+
+После чего смотрим, который из таких наборов в прошлом приносил победу чаще,
+и соответственно выбираем или вариант с 0, или с 1.
+
+После получения действительного хода игрока, мы увеличиваем вес того набора,
+который оказался верным. И далее снова предсказываем ход.
+
+Теперь с числами.
+
+#### Ход 1
+
+У демона нет информации, наборы `[0]` и `[1]` равнозначны, выбираем `[0]`,
+а значит предсказываем ход игрока 0.
+
+Игрок загадывал 1. Обновляем веса:
+
+```
+[0] = 0
+[1] = 1
+```
+
+#### Ход 2
+
+Строим векторы на основе последних ходов:
+
+```
+0: [0, 1, 0]
+1: [0, 1, 1]
+```
+
+Для этих векторов тоже еще нет весов, так что снова выбираем первый, предсказываем 0.
+
+Игрок снова выбрал 1. Обновляем веса (помним, что еще были прошлые вектора из одного элемента):
+
+```
+[0] = 0
+[1] = 1
+[0, 1, 0] = 0
+[0, 1, 1] = 1
+```
+
+#### Ход 3
+
+Картина такая же, как на втором ходу, но отличие в том, что у нас есть веса с прошлого хода:
+
+```
+0: [0, 1, 0] - 0
+1: [0, 1, 1] - 1
+```
+
+Выбираем вариант 1, игрок снова выбирает 1. Предсказание удалось!
+
+## Расширение алгоритма
+
+Это был самый элементарный вариант. Понятно, что на таком далеко не уедешь,
+и никого не обыграешь. Чтобы хорошо предугадывать ходы игроков, используется
+несколько демонов с разной величиной просматриваемой истории. Следит за ними
+"супервайзер", который ведет для каждого демона рейтинг. На основе этого рейтинга
+выбираются ответы тех демонов, которые были наиболее успешны в своих предсказаниях.
+
+## Ссылки
+
+- [Код гадалки][repo]
+- [Описание алгоритма][algo]
+- [Описание игры у ltwood][game]
+
+[ltwood]: https://sites.google.com/site/ltwood/
+[game]: https://sites.google.com/site/ltwood/projects/heshby
+[algo]: https://sites.google.com/site/ltwood/projects/heshby/algorithm
+[repo]: https://github.com/anwinged/predictor
diff --git a/src/content/articles/2019-06-01-php-serialization.md b/src/content/articles/2019-06-01-php-serialization.md
new file mode 100644
index 0000000..4ab963b
--- /dev/null
+++ b/src/content/articles/2019-06-01-php-serialization.md
@@ -0,0 +1,39 @@
+---
+title: Сериализация в PHP
+description: Проблема долговременного хранения сериализованных данных
+keywords: [php, serialization, сериализация, пхп]
+---
+
+В PHP есть две функции для сериализации и десериализации данных: `serialize()` и
+`unserialize()`. Функции встроены в язык, не требуют дополнительных модулей.
+
+В один момент кто-то решает использовать их для долговременного хранения
+объектов. В базе данных, на диске, еще где-то.
+
+```
+namespace Test\Serialize;
+
+class A {}
+
+$a = new A();
+serialize($a);
+```
+
+И тут начинаются проблемы.
+
+Дело в том, что при сериалзации объектов классов кроме самих данных объекта
+сохраняется еще и информация о классе. Его имя, пространство имен.
+
+Результатом сериализации в примере выше будет:
+
+```
+O:16:"Test\Serialize\A":0:{}O:16:"Test\Serialize\A":0:{}
+```
+
+Если теперь произвести рефакторинг, переместить класс, изменить пространство
+имен, то десериализация уже не сработает. И будет больно.
+
+Не делайте так.
+
+Контролируйте процесс сериализации. Например, используйте JSON и специальные
+функции для превращения объекта в массив и обратно.
diff --git a/src/content/articles/2019-06-28-storytelling.md b/src/content/articles/2019-06-28-storytelling.md
new file mode 100644
index 0000000..35eba67
--- /dev/null
+++ b/src/content/articles/2019-06-28-storytelling.md
@@ -0,0 +1,85 @@
+---
+title: Сторителлинг
+description: Конспект видеолекций Алексея Каптерева о сторителлинге и презентации
+keywords: [story, storytelling, lectures, история, сторителлинг, каптерев]
+---
+
+Это конспект трех лекций Алексея Каптерева, где он рассказывает о презентациях.
+Как придумать историю, как подготовить слайды и что рассказывать.
+
+- [https://youtu.be/PaoKhNLyxdk](https://youtu.be/PaoKhNLyxdk)
+- [https://youtu.be/18M9ZJRU2wI](https://youtu.be/18M9ZJRU2wI)
+- [https://youtu.be/KyyAgiw8B6I](https://youtu.be/KyyAgiw8B6I)
+
+## История
+
+Самый главный вопрос: "Чего я хочу?", а второй - "Что им нужно?" Далее нужно
+найти пересечение ответов на эти два вопроса. "Что я хочу, чтобы у вас
+получилось?"
+
+Эмоции = Мотивация
+
+Я хочу, чтобы они
+
+- поняли, что...
+- изменили мнение о...
+- сделали...
+
+Кто? Кому? Что? Зачем? Как?
+
+ История = интересная тема + близость темы + сценарий
+
+ История = факты + смысл + эмоции
+
+Основные составляющие истории:
+
+- **Герой** Кто хочет? Я сам, клиент, клиент клиента.
+- **Цель** Что хочет?
+- **Проблема / слабость** Что мешает, какая проблема? Почему он не может без
+ этого жить?
+- **Злодей** Кто мешает?
+- **Решение** Пути обхода? В чем инсайт?
+- **Цена** Плата за решение?
+- **Мораль** В чем призыв к действию? Если вы сделаете Х, то будет Y, иначе Z.
+
+Факт -> Проблема -> Решение -> Но не все так просто -> Теперь уж точно
+
+План, зум (уточнение) частей плана - деревья представления.
+
+Концовка - самое тяжелое, начните с концовки.
+
+Говорите то, что вас зажигает.
+
+Чтобы заинтересовать человека в письменном виде, нужно сделать письмо
+максимально похожим на устную речь. С героем, целью и так далее.
+
+## Презентации
+
+Таблицы для анализа, презентации для результата.
+
+1. Монотонность - враг истории.
+2. Дело не в "умении выступать" (владейте сутью, слова найдутся).
+3. История - это не сложно. Экспозиция, проблематизация, решение, выводы.
+4. Не начинайте с PowerPoint.
+5. Вы не можете сказать всего (Скажите 5% от того, что вы знаете). Одна хорошая
+ мысль и _очень_ много дисциплины.
+6. Лучше один раз увидеть (чем 100 раз услышать и 10 раз потрогать).
+7. Слайды - они как дети. Уродливые, зато свои. Не увлекаться форматированием.
+ Чтобы выделить главное, нужно ответить на вопрос "Какова цель этого слайда?"
+8. Дизайн - это вычитание. Пропорции, цвета, типографика.
+9. Контакт глаз. При выступлении смотрите людям в глаза. Это значит, что вы не
+ врете.
+10. Правда делает нас свободными. Верьте в то, что говорите. Аудитория хочет
+ правды, настоящности. Правда, сострадание, самоирония.
+
+## Слайды
+
+Цель слайда: напомнить, впечатлить, объяснить, доказать. Определить цель, делать
+простым, смотреть на слайд со стороны аудитории.
+
+- Фотографии. Размер (большой), смысл, честность, логика. Не использовать
+ фотографии для схем.
+- Схемы. Простота, пошаговость, направление. Пошаговое разжевывание схем.
+ Схемы "как это устроено" и "как это работает". Схема-исория лучше
+ схемы-модели.
+- Статистика. Результат (а не анализ), ничего лишнего, честность.
diff --git a/src/content/articles/2019-08-08-yandex-disk-image-hosting.md b/src/content/articles/2019-08-08-yandex-disk-image-hosting.md
new file mode 100644
index 0000000..9869bd4
--- /dev/null
+++ b/src/content/articles/2019-08-08-yandex-disk-image-hosting.md
@@ -0,0 +1,69 @@
+---
+title: Яндекс.Диск для хостинга картинок
+keywords: [яндекс.диск, хостинг картинок, yandex disk, image hosting, hosting]
+---
+
+У [Яндекс.Диска][ya-disk] есть замечательная функция. Он может создавать превью
+загруженных фотографий. Эта функциональность не афишируется, но описана
+в [документации][ya-api-preview].
+
+У меня есть фотография на Диске `/img/kemsky.jpg`. Чтобы получить ее превью,
+нужно выполнить запрос:
+
+```
+GET /img/kemsky.jpg?preview&size=XS
+User-Agent: my_application/0.0.1
+Host: webdav.yandex.ru
+Authorization: OAuth 0c4182a7c2cf4521964a72ff57a34a07
+```
+
+Но есть проблема. Для запросов нужен токен. Без токена не получится использовать
+это API для публичного хостинга.
+
+Решение - сервер [Caddy][caddy] в качестве прокси. Caddy очень
+удобно использовать в качестве фронтенда для внутренних сервисов.
+Он просто настраивается, а самое главное - поддерживает автоматический
+выпуск и обновление SSL-сертификатов буквально одной строчкой конфига.
+Скроем токен в конфигурации сервера, и будем передавать его при обращении
+к Яндекс.Диску:
+
+```
+preview.vakhrushev.me {
+ proxy /img https://webdav.yandex.ru {
+ transparent
+ header_upstream User-Agent "yandex-disk-previewer/1.0"
+ header_upstream Authorization "OAuth 0c4182a7c2cf4521964a72ff57a34a07"
+ }
+
+ tls anwinged@ya.ru
+}
+```
+
+Директива `proxy /img` будет направлять все запросы с `preview.vakhrushev.me/img`
+на `https://webdav.yandex.ru/img`. Таким образом во внешний
+мир будет смотреть только директория `img`, а остальные останутся скрытыми.
+
+Кроме OAuth авторизации можно использовать Basic, передавая логин и
+[пароль приложения][app-password]. Мне этот способ удобнее,
+чтобы не заморачиваться с OAuth. Логин и пароль я храню
+зашифрованными с помощью [Ansible Vault][vault].
+И строчка с заголовком тогда будет выглядеть так:
+
+```
+header_upstream Authorization "Basic {{ '{{' }} (yandex_disk.login ~ ':' ~ yandex_disk.password) | b64encode {{ '}}' }}"
+```
+
+А так будет выглядеть ссылка на картинку:
+
+```
+https://preview.vakhrushev.me/img/kemsky.jpg?preview&size=XXL
+```
+
+
+
+[ya-disk]: https://disk.yandex.ru
+[ya-api]: https://yandex.ru/dev/disk/doc/dg/concepts/quickstart-docpage/
+[ya-api-preview]: https://yandex.ru/dev/disk/doc/dg/reference/preview-docpage/
+[caddy]: https://caddyserver.com/
+[app-password]: https://yandex.ru/support/passport/authorization/app-passwords.html
+[vault]: https://docs.ansible.com/ansible/latest/user_guide/vault.html
diff --git a/src/content/articles/2019-09-26-highload-videos.md b/src/content/articles/2019-09-26-highload-videos.md
new file mode 100644
index 0000000..a02e80b
--- /dev/null
+++ b/src/content/articles/2019-09-26-highload-videos.md
@@ -0,0 +1,25 @@
+---
+title: Как проектировать хайлоад, видео
+keywords: [highload, высоконагруженные системы, онтико, олег бунин]
+---
+
+Нашел три замечательных видеоролика о высоконагруженных системах.
+Архитектура, подходы, планирование, проблемы.
+
+## Часть 1
+
+
+
+
+
+## Часть 2
+
+
+
+
+
+## Часть 3
+
+
+
+
diff --git a/src/content/articles/2020-06-27-interesting-programming-blogs.md b/src/content/articles/2020-06-27-interesting-programming-blogs.md
new file mode 100644
index 0000000..dd6c451
--- /dev/null
+++ b/src/content/articles/2020-06-27-interesting-programming-blogs.md
@@ -0,0 +1,16 @@
+---
+title: Интересные блоги о программировании
+description: Сборник интересных блогов и статей о программирования для себя
+keywords: [блоги, программирование, сборник, чистый код, ооп, haskell]
+---
+
+## Дизайн приложений
+
+- Сайт Мартина Фаулера - [martinfowler.com](https://martinfowler.com/).
+ Архитектура, рефакторинг, чистый код.
+- Блог Роберта Мартина, или дядюшки Боба, о чистом коде - [The Clean Code Blog](https://blog.cleancoder.com/).
+
+## Функциональное программирование
+
+- Хорошие статьи о функциональном программировании с примерами на F# - [F# for fun and profit](https://fsharpforfunandprofit.com/).
+- Блог [Alexis King](https://lexi-lambda.github.io/) о Haskell и Racket.
diff --git a/src/content/articles/2020-06-27-type-discriminant.md b/src/content/articles/2020-06-27-type-discriminant.md
new file mode 100644
index 0000000..c36b351
--- /dev/null
+++ b/src/content/articles/2020-06-27-type-discriminant.md
@@ -0,0 +1,122 @@
+---
+title: О полях-дискриминаторах
+description: Заметка о том, как записывать конфигурацию для сложных объектов
+keywords: [чистый код, дискриминатор, php, конфигурация]
+---
+
+Поля-дискриминаторы - это удобный прием для обработки нескольких типов
+данных со схожей структурой.
+
+Лучше начать с примера.
+
+Допустим, у нас есть объект-фильтр для целых чисел. Можно применить фильтр
+к последовательности чисел и получить новую последовательность.
+Его параметры выглядят следующим образом:
+
+```json
+{
+ "from": 0,
+ "to": 10
+}
+```
+
+Отлично.
+Фильтр пропустит только числа от 0 до 10.
+По такой конфигурации без проблем можно создать объект.
+
+Теперь добавим второй фильтр - он будет отсекать нечетные числа.
+
+```json
+{
+ "odd": false
+}
+```
+
+## Создаем фильтр на основе структуры
+
+Теперь есть два фильтра, и нужно понимать какой их них создать.
+Простое и наивное решение - смотреть на структуру полей.
+Если есть поле `odd`, то фильтр нечетных чисел, иначе - фильтр по диапазону.
+
+```php
+if (isset($config['odd'])) {
+ // фильтр нечетных чисел
+} else {
+ // фильтр диапазона
+}
+```
+
+У этого решения масса недостатков.
+Например, если поле `odd` понадобится двум фильтрам сразу, то условие усложнится.
+Или появятся поля с разными названиями, но одинаковым смыслом.
+
+## Добавляем поле-дискриминатор
+
+Решение проблемы в добавление специального поля, в котором содержится имя фильтра.
+Назовем это поле `type`. Тогда конфигурации фильтров будут выглядеть следующим образом:
+
+```json
+{
+ "type": "range",
+ "from": 0,
+ "to": 10
+}
+```
+
+```json
+{
+ "type": "odd_control",
+ "odd": false
+}
+```
+
+Поле `type` - это и есть поле-дискриминатор.
+По нему можно точно определить какой фильтр перед нами.
+
+```php
+switch ($config['type']) {
+ case 'range':
+ // фильтр диапазона
+ break;
+ case 'odd_control':
+ // фильтр нечетных чисел
+ break;
+ default:
+ throw new \LogicException(sprintf(
+ 'Unknown filter type "%s"', $config['type']));
+}
+```
+
+## Выделить зависимые поля
+
+Решение еще можно улучшить.
+Сейчас на одном уровне в структуре конфига есть и обязательные поля, и необязательные поля.
+Мы можем перенести все необязательные поля на дополнительный уровень,
+например, в поле `params`.
+Эти поля необязательны для всей конфигурации, но обязательны для конкретного фильтра.
+
+```json
+{
+ "type": "range",
+ "params": {
+ "from": 0,
+ "to": 10
+ }
+}
+```
+
+Что дает такой маневр?
+На верхнем уровне конфигурации у нас будет постоянная структура.
+Поле `params` можно до определенного времени рассматривать как черный ящик,
+просто зная, что это некий словарь параметров.
+Когда будет понятно какой фильтр создавать, тогда этот набор параметров
+послужит базой для создания объекта-фильтра.
+
+## Заключение
+
+- Если структура конфигурации предполагает создание нескольких разных объектов,
+ то лучше использовать специальное поле-дискриминатор для точного указания
+ типа создаваемого объекта.
+- Все поля, которые зависят от типа, лучше перенести на дополнительный уровень,
+ тем самым сохранив структуру верхнего уровня постоянной (на верхнем уровне
+ будет всегда один и тот же набор полей).
diff --git a/src/content/articles/2020-11-08-nullable-fields.md b/src/content/articles/2020-11-08-nullable-fields.md
new file mode 100644
index 0000000..1e04535
--- /dev/null
+++ b/src/content/articles/2020-11-08-nullable-fields.md
@@ -0,0 +1,177 @@
+---
+title: Организация доступа к nullable полям класса
+description: Заметка о том, лучше организовать доступ к полям класса, которые могут содержать значение null
+keywords: [чистый код, php, "null", поля класса]
+---
+
+Нередкая ситуация, когда в классе есть поле, которое может содержать `null`.
+
+```php
+class User
+{
+ private Email $email;
+ private ?string $name;
+}
+```
+
+Пользователь может указать имя, а может и не указывать,
+ограничившись только почтовым адресом.
+
+А далее мы пишем код, которые работает с полем имени.
+
+```php
+class User
+{
+ private ?string $name;
+
+ public function hasName(): bool
+ {
+ return $this->name !== null;
+ }
+
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ public function setName(?string $name): void
+ {
+ $this->name = $name;
+ }
+}
+```
+
+И использование этого кода:
+
+```php
+/** @var User $user */
+
+if ($user->hasName()) {
+ do_something_with_name($user->getName());
+}
+
+function do_something_with_name(string $name) {}
+```
+
+Выглядит хорошо.
+Сначала убедились, что имя установлено, а потом использовали его.
+
+Но статический анализатор нам обязательно припомнит, что мы пытаемся передать
+в функцию `do_something_with_name` значение типа `string|null`, хотя функция
+ожидает значение типа `string`.
+И получается дурацкая ситуация, что формально мы должны дописать
+еще одну проверку.
+
+```php
+/** @var User $user */
+
+if ($user->hasName()) {
+ $name = $user->getName();
+ if ($name !== null) {
+ do_something_with_name($name);
+ }
+}
+
+function do_something_with_name(string $name) {}
+```
+
+Статический анализатор наш друг, он помогает находить ошибки и несоответствия
+в коде.
+И здесь он нашел такое формальное несоответствие типов.
+
+Статический анализатор прав, а мы, как проектировщики интерфейса, не правы.
+На самом деле мы смешали два подхода, когда описывали методы в нашем классе:
+
+1. Получить и проверить
+2. Проверить и получить
+
+## Получить и проверить
+
+И сразу начнем с примера использования.
+
+```php
+$name = $user->getName();
+if ($name !== null) {
+ do_something_with_name($name);
+}
+```
+
+Сначала мы получаем значение поля, а потом проверяем, соответствует ли это
+значение нашим требованиям. Класс при этом будет построен вот так:
+
+```php
+class User
+{
+ private ?string $name;
+
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ public function setName(?string $name): void
+ {
+ $this->name = $name;
+ }
+}
+```
+
+Заметьте, здесь уже нет метода `hasName()`, потому что этот метод перестал быть
+нужным. Его роль исполняет метод `getName()`.
+
+## Проверить и получить
+
+Второй подход: сначала проверяем значение, а потом работаем с ним:
+
+```php
+if ($user->hasName()) {
+ do_something_with_name($user->getName());
+}
+```
+
+Структура класса:
+
+```php
+class User
+{
+ private ?string $name;
+
+ public function hasName(): bool
+ {
+ return $this->name !== null;
+ }
+
+ public function getName(): string
+ {
+ if ($this->name === null) {
+ throw new \LogicException('Name is not set');
+ }
+
+ return $this->name;
+ }
+
+ public function setName(?string $name): void
+ {
+ $this->name = $name;
+ }
+}
+```
+
+Смотрите отличия.
+Метод `hasName()` остается.
+А вот метод `getName()` теперь возвращает значение типа `string`.
+Он выбросит исключение, если мы попытаемся получить значение,
+которое не установлено.
+
+## Использование
+
+Теперь встает вопрос, когда и какой подход следует использовать.
+
+- Если ситуация, когда поле не установлено, скорее исключительная, нежели
+ обычная, то можно использовать второй подход, а проверку опустить.
+ Исключение в методе `getName()` позволит обнаружить странное поведение.
+- Если в пустом поле нет ничего не обычного, то подход "получить и проверить"
+ будет удобнее, все равно нужно делать проверку.
+
+В любом случае, нужно смотреть на уместность того или иного подхода в каждом
+случае, и не использовать их одновременно.