Graphuary, день 28
Искажение
Всем спасибо за движ ❤️
Когда инструменты марсохода NASA Curiosity, находящегося на Красной планете с 6 августа 2012 года, зафиксировали в разреженной атмосфере присутствие молекулярного кислорода (O2), это стало неожиданностью.
Сегодня известно, что на кислород приходится всего около 0,13–0,16% от объема марсианской атмосферы, состоящей на 95% из углекислого газа (CO2). Это ничтожно мало по земным меркам, но факт его присутствия заслуживает особого внимания.
Итак, откуда на холодной и сухой планете, где нет ни растений, ни водорослей, ни каких-либо других организмов*, способных к фотосинтезу, взялся O2, запасы которого пополняются?
*По сей день никаких убедительных доказательств существования жизни на Марсе нет. Поэтому исходим из этого факта.
Исследования показывают, что основным источником кислорода на Марсе являются фотохимические процессы, протекающие в верхних слоях атмосферы. Под действием ультрафиолетового излучения Солнца молекулы CO2 и небольшого количества водяного пара (H2O) распадаются, высвобождая атомы кислорода. Часть этих атомов ненадолго объединяется в молекулы O2, которые после череды фотохимических реакций снова оказываются связанными в составе CO2.
Данная модель прекрасно объясняет присутствие кислорода в марсианской атмосфере. На этом можно было бы и закончить статью, но...
В 2019 году Curiosity, продолжая свою работу в кратере Гейла, обнаружил, что колебания уровня кислорода в атмосфере демонстрируют более сильную сезонную зависимость, чем предсказывает фотохимическая модель. Так, в весенне-летний период уровень O2 возрастает почти на 30%, а осенью и зимой возвращается к исходным значениям.
В попытках объяснить эту аномалию ученые выдвинули две гипотезы:
Марсианский реголит насыщен перхлоратами — солями, содержащими кислород в связанном виде. Лабораторные эксперименты вкупе с моделированием показывают, что под воздействием радиации такие соединения могут разлагаться, высвобождая реакционноспособные кислородсодержащие продукты, включая молекулярный кислород.
Пока доподлинно неизвестно, может ли этот механизм полностью объяснить наблюдаемую сезонность, но на роль потенциального источника "дополнительного" кислорода он определенно подходит.
Вторая гипотеза связана с залежами подповерхностного льда и возможным наличием карманов с рассолами — локальных скоплений воды с очень высокой концентрацией растворенных солей, что позволяет ей оставаться жидкой даже при очень низких температурах.
Учитывая, что Марс лишен надежной магнитосферы и плотной атмосферы, поверхности достигает большое количество космической радиации, которая, проникая в грунт, способна расщеплять молекулы воды — процесс, известный как радиолиз. В результате образуются кислородсодержащие соединения, способные вносить вклад в наблюдаемую сезонную изменчивость кислорода в атмосфере Марса.
Однако прямых доказательств того, что именно этот процесс заметно влияет на сезонное содержание O2 в атмосфере Марса, пока нет.
Несмотря на то, что традиционно кислород считается одним из лучших биомаркеров, на Марсе его концентрации крайне малы, чтобы приписывать ему биологическое происхождение. То, что было зафиксировано, без проблем укладывается в "абиогенные рамки" — фотохимия, поверхностная и радиационная химия.
Поэтому интерес ученых вызывает не сам факт присутствия O2, а его необычное поведение. Понимание этого механизма позволит лучше понять химические процессы, происходящие на Красной планете сегодня. Следовательно, это поможет уточнить оценки того, насколько Марс вообще мог быть пригоден для жизни в прошлом.
В конце концов, поиски возможных следов марсианской жизни должны начинаться с понимания того, располагала ли когда-нибудь планета-соседка условиями, подходящими для ее зарождения. И пока однозначного ответа нет.
28 день. Спасибо за прикольный движ, это было очень интересно и полезно. Впервые в таком участвовала, мне понравилось)
Завтра начнётся ломка )
@NapalmRain огромное спасибо за челлендж! Такие вещи никогда не проходят зря и делают нас в чём-то лучше.
Всем продержавшимся до конца спасибо за то, что мы стали лучше все вместе 🥰
Кто проживает в Красноярске знают какой тут (чистый ага) воздух. Воздух иногда выходит на первое место в мире по загрезнению.
А ещё у нас много по городу бронзовых сусликов на разные темы.
В общем у меня родилась идея сделать суслика в противогазе)))
Дома у меня есть три причины не рисовать с натуры: Фаня, Чернушка и Ириска, поэтому очень люблю ходить на мастер-классы в чужие мастерские. Эта работа - вторая попытка потрогать масляную пастель. Чуть более убедительная, чем первая, но до акварели ей не дотянуться: слишком легко ей работать )
Игра почти готова, но не хватает ещё парочки нюансов)
Сразу смотрим результат, а я пока распишу, что же тут происходит
1. Я добавил разные типы врагов. Как и в оригинале, это базовые, быстроходные и бронированные. Последние плюсом ко всему имеют бронебойные патроны.
2. Я добавил новый вид бонуса: «Строитель». Поднимая мастерок игрок может построить несколько бетонных и несколько кирпичных блоков. Именно для баланса этого бонуса и введены вражеские танки, которые могут уничтожать в том числе и бетонные стены.
Давайте смотреть
export class Bonus {
/**
* По всему коду натыкал как можно больше комментов, чтоб понятно было куда собака зарыта
*
* @param {number} x - Логическая координата X
* @param {number} y - Логическая координата Y
* @param {string} type - Тип бонуса (BonusType.STAR, BonusType.GRENADE, etc.)
*/
constructor(x, y, type) {
this.x = x;
this.y = y;
this.type = type;
// Время жизни бонуса
this.lifetime = Duration.BONUS_LIFETIME;
this.blinkTime = Duration.BONUS_BLINK_TIME;
// Состояние
this.collected = false;
this.expired = false;
// Анимация
this.animationFrame = 0;
this.animationTimer = 0;
this.animationSpeed = 6; // Кадров игры между сменой кадра анимации
// Мигание перед исчезновением
this.blinkTimer = 0;
this.blinkSpeed = 4; // Быстрое мигание
this.visible = true;
}
/**
* Обновление состояния бонуса
*/
update() {
if (this.collected || this.expired) return;
// Уменьшаем время жизни
this.lifetime--;
// Проверка истечения времени
if (this.lifetime <= 0) {
this.expired = true;
return;
}
// Анимация спрайта
this.animationTimer++;
if (this.animationTimer >= this.animationSpeed) {
this.animationTimer = 0;
this.animationFrame = (this.animationFrame + 1) % BONUS_SPRITE.frameCount;
}
// Мигание перед исчезновением
if (this.lifetime <= (Duration.BONUS_LIFETIME - this.blinkTime)) {
this.blinkTimer++;
if (this.blinkTimer >= this.blinkSpeed) {
this.blinkTimer = 0;
this.visible = !this.visible;
}
}
}
/**
* Сбор бонуса игроком
*/
collect() {
this.collected = true;
}
/**
* Проверка, истёк ли бонус
*/
isExpired() {
return this.expired || this.collected;
}
/**
* Получить bounding box в логических координатах
*/
getBounds() {
return {
x: this.x,
y: this.y,
width: BONUS_SIZE,
height: BONUS_SIZE
};
}
/**
* Отрисовка бонуса
*/
render() {
if (this.collected || this.expired) return;
if (!this.visible) return; // Мигание
const ctx = foregroundCtx;
// Конвертация логических координат в физические
const px = GAME_FIELD_X + this.x * GAME_SCALE;
const py = GAME_FIELD_Y + this.y * GAME_SCALE;
const size = BONUS_SIZE * GAME_SCALE;
const sprite = getBonusSprite(this.type);
if (sprite) {
const frameX = this.animationFrame * BONUS_SPRITE.frameWidth;
ctx.drawImage(
sprite,
frameX, 0,
BONUS_SPRITE.frameWidth, BONUS_SPRITE.frameHeight,
px, py,
size, size
);
} else {
// Fallback: цветной квадрат
this.renderFallback(ctx, px, py, size);
}
}
/**
* Fallback отрисовка (цветной квадрат с буквой)
*/
renderFallback(ctx, px, py, size) {
// Цвета для разных типов бонусов
const colors = {
[BonusType.STAR]: '#FFFF00',
[BonusType.GRENADE]: '#FF4500',
[BonusType.TIMER]: '#00BFFF',
[BonusType.SHOVEL]: '#8B4513',
[BonusType.TANK]: '#00FF00',
[BonusType.HELMET]: '#C0C0C0',
[BonusType.GUN]: '#FF00FF',
[BonusType.BUILDER]: '#334455'
};
const letters = {
[BonusType.STAR]: 'S',
[BonusType.GRENADE]: 'G',
[BonusType.TIMER]: 'T',
[BonusType.SHOVEL]: 'L',
[BonusType.TANK]: '+',
[BonusType.HELMET]: 'H',
[BonusType.GUN]: 'P',
[BonusType.BUILDER]: 'B'
};
ctx.fillStyle = colors[this.type] || '#FFFFFF';
ctx.fillRect(px + 2, py + 2, size - 4, size - 4);
ctx.fillStyle = '#000000';
ctx.font = `bold ${size * 0.6}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(letters[this.type] || '?', px + size / 2, py + size / 2);
}
}
Где и как учитываются бонусы?
Во-первый в main.js я сразу создаю бонус для строительства
// DEBUG: Тестовый бонус Builder рядом с игроком
const testBonus = new Bonus(PLAYER_SPAWN_POINT.x + 32, PLAYER_SPAWN_POINT.y - 32, BonusType.BUILDER);
bonuses.push(testBonus);
Это сделано для тестов, но, возможно, я даже оставлю в конечном варианте
Дальше в кор геймплее
const collectedBonus = checkTankBonusCollision(player, bonuses);
if (collectedBonus) {
applyBonusEffect(collectedBonus);
}
// И дальше по коду сам метод
function applyBonusEffect(bonus) {
switch (bonus.type) {
case BonusType.STAR:
// Апгрейд оружия (до уровня 3 максимум)
if (player && player.bulletLevel < 3) {
player.bulletLevel++;
// Уровень 1+ даёт 2 пули
if (player.bulletLevel >= 1) {
player.bulletCount = 2;
}
}
break;
case BonusType.GUN:
// Мгновенный максимальный апгрейд
if (player) {
player.bulletLevel = 3;
player.bulletCount = 2;
}
break;
case BonusType.TANK:
// Дополнительная жизнь
playerLives++;
break;
case BonusType.HELMET:
// Временная неуязвимость
if (player) {
player.hasShield = true;
player.shieldDuration = Duration.EFFECT_SHIELD;
}
break;
case BonusType.TIMER:
// Заморозка врагов
freezeTimer = Duration.EFFECT_TIMER;
break;
case BonusType.GRENADE:
// Уничтожение всех врагов на экране
for (const enemy of enemies) {
if (!enemy.destroyed) {
enemy.destroyed = true;
// Создаём взрывы
const centerX = enemy.x + TANK_SIZE / 2;
const centerY = enemy.y + TANK_SIZE / 2;
effects.push(createBigExplosion(centerX, centerY));
}
}
break;
case BonusType.SHOVEL:
// Укрепление базы
shovelTimer = Duration.EFFECT_SHOVEL;
fortifyBase();
break;
case BonusType.BUILDER:
// Режим строителя: даёт блоки для размещения
if (player) {
player.builderBricks += 8;
player.builderSteel += 4;
}
break;
}
bonus.collect();
}
Далее все таймеры эффекты глобальные сбрасываются вот тут
function updateGlobalEffects() {
// Таймер заморозки
if (freezeTimer > 0) {
freezeTimer--;
}
// Таймер укрепления базы
if (shovelTimer > 0) {
shovelTimer--;
if (shovelTimer <= 0 && baseFortified) {
restoreBase();
}
}
}
Вообще в данном случае было бы логично вынести логику сброса эффекта бонуса в сам бонус. Чтоб он там в фоне считался, а ядру просто отдавал событие или менял внутри себя флаг. Как будто там будет почище. Подумал об этом только сейчас, отрефакторю к следующему выпуску.
Следующий этап уже не про новые фичи, а про полировку, оптимизацию и доработку.
Писал я план ещё в самом начале, так что часть из этих пунктов уже сделана, но всё равно нужно будет пройтись по всем.
Зря, нужно было выкатить охрененную сумму за хранение и просрочку, забрали бы быстро и мозг не мучали.
ага, прям моё
Но и ножи!! Д ДА ДА