Создание игры Dice Game (игра в кости) на JavaScript. Вот нашлось время и силы познакомиться с игрой такого типа. Сразу оговорюсь, что материал и пример не мой, а взят из зарубежного ресурса для изучения.

В примере рассмотрен основной принцип создания игры Dice. Можно сказать - чисто схематично. Однако этого достаточно, чтобы остальное дополнить по необходимости.

Код оказался на удивление прост. Я не сомневаюсь, что в Интернете есть примеры более сложного и совершенного JavaScript-кода для игр подобного типа. Но цель статьи - познакомиться с основным принципом создания игры, не более.

HTML разметка и стили

Разметка очень простая и состоит из четырех элементов:

<div class="wrapper">
    <div class="column">
        <div id="dice-side-1" class="dice">0</div>
        <div id="dice-side-2" class="dice">0</div>
    </div>
    <div class="column">
        <button type="button" class="dice-roll">roll dice</button>
        <h2 id="status"></h2>
    </div>
</div>

Элементы

1
id="dice-side-1"
и
1
id="dice-side-2"
- это иммитация двух игральных костей; как если бы эти кости были обращены лицевой стороной к зрителю.

Кнопка

1
class="dice-roll"
служит для управления; с помощью нее мы будем “бросать” игральные кости.

Заголовок

1
id="status"
носит информативный характер - в нем будет выводиться текущая информация.

CSS-стили в комментариях не нуждаются, ибо они небольшого размера и “прозрачные”:

body {
    position: relative;
}

.wrapper {
    width: 400px;
    margin: 0 auto;
    display: flex;
    justify-content: space-between;
}

.column {
    display: flex;
    align-items: center;
}

#status {
    position: absolute;
    top: 1rem;
    right: 1rem;
    min-width: 400px;
    margin: 0;
}

.dice {
    width: 32px;
    float: left;
    background-color: lightcoral;
    border: 1px solid black;
    padding: 10px;
    font-size: 24px;
    text-align: center;
    margin: 5px;
    border-radius: 5px;
}

.dice-roll {
    cursor: pointer;
    text-transform: capitalize;
}

JavaScript - оживляем игральные кости

Далее приступим к более интересной части задачи - созданию js-кода для “оживления” наших игральных костей.

Для этого “выберем” из HTML кнопку и “повесим” на нее функцию rollDice для обработки клика по кнопке:

window.addEventListener('DOMContentLoaded', function () {

    var buttonRollDice = document.querySelector('.dice-roll');
    buttonRollDice.addEventListener('click', rollDice, false);

}, false);

Затем начнем описывать функцию rollDice. Создадим три переменные, в которые поместим обе игральные кости и информативный заголовок:

window.addEventListener('DOMContentLoaded', function () {

    function rollDice () {

        var diceSide1 = document.getElementById('dice-side-1');
        var diceSide2 = document.getElementById('dice-side-2');
        var status = document.getElementById('status');

    }

}, false);

Сгенерируем два случайных числа из диапазона от 1 до 6. Игральная кость имеет шесть сторон - поэтому такой диапазон. Эти числа будут иммитацией одной из шести сторон каждой игральной кости.

Другими словами. На всех сторонах игрального кубика “выбито” точками число - от 1 до 6. Поэтому можно сказать иначе - диапазон от 1 до 6 - это диапазон возможных значений, которые выпадают на каждой из игральных костей:

window.addEventListener('DOMContentLoaded', function () {

    function rollDice () {

        var diceSide1 = document.getElementById('dice-side-1');
        var diceSide2 = document.getElementById('dice-side-2');
        var status = document.getElementById('status');

        var side1 = Math.floor( Math.random() * 6 ) + 1;
        var side2 = Math.floor( Math.random() * 6 ) + 1;
        var diceTotal = side1 + side2;

    }

}, false);

Переменная

1
diceTotal
служит для информативных целей - будем показывать сообщение о том, сколько в сумме выпало очков на игральных костях.

Осталось поместить случайно сгенерированную пару чисел в HTML-код. Помимо этого вывести информационное сообщение, сколько очков в сумме выпало. И предупредить, если на обеих костях выпавшее число одинаковое - тогда предоставить игроку еще один ход:

window.addEventListener('DOMContentLoaded', function () {

    function rollDice () {

        var diceSide1 = document.getElementById('dice-side-1');
        var diceSide2 = document.getElementById('dice-side-2');
        var status = document.getElementById('status');

        var side1 = Math.floor( Math.random() * 6 ) + 1;
        var side2 = Math.floor( Math.random() * 6 ) + 1;
        var diceTotal = side1 + side2;

        diceSide1.innerHTML = side1;
        diceSide2.innerHTML = side2;

        status.innerHTML = 'You rolled ' + diceTotal + '.';

        if ( side1 === side2 ) {
            status.innerHTML += ' Doubles! You get a free turn!';
        }
    }

}, false);

Наш JavaScript-код готов и выглядит таким несложным способом:

window.addEventListener('DOMContentLoaded', function () {

    function rollDice () {

        var diceSide1 = document.getElementById('dice-side-1');
        var diceSide2 = document.getElementById('dice-side-2');
        var status = document.getElementById('status');

        var side1 = Math.floor( Math.random() * 6 ) + 1;
        var side2 = Math.floor( Math.random() * 6 ) + 1;
        var diceTotal = side1 + side2;

        diceSide1.innerHTML = side1;
        diceSide2.innerHTML = side2;

        status.innerHTML = 'You rolled ' + diceTotal + '.';

        if ( side1 === side2 ) {
            status.innerHTML += ' Doubles! You get a free turn!';
        }
    }

    var buttonRollDice = document.querySelector('.dice-roll');
    buttonRollDice.addEventListener('click', rollDice, false);

}, false);

Готовый пример Dice Game можно потестить по этой ссылке - JavaScript Dice Game.

Заключение

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

Плюс - добавить анимацию. И получится весьма неплохой результат, как мне представляется.


На этом все.

Знакомство с техникой Drag-and-Drop и механизмом ее реализации с помощью JavaScript. Данный обзор не претендует на полноту покрытия материала. Задача статьи - познакомиться с созданием Drag-and-Drop на JavaScript. Понять сам принцип механизма и научиться применять основные инструменты для его реализации.

Определение Drag-and-Drop

Сам механизм Drag-and-Drop интуитивно понятен - “схватил-перетащил-бросил”. Преимущество внедрения Drag-and-Drop в интерфейсы заключается в упрощении реализации задач; в уменьшении количества пунктов меню типа “Copy-Paste”.

События Drag-and-Drop

Механизм Drag-and-Drop имеет в своем составе целую группу событий, с помощью которых можно контролировать процесс перетаскивания:

  • 1
    
    dragstart
    
    - пользователь начинает перетаскивание элемента
  • 1
    
    dragenter
    
    - перетаскиваемый элемент входит в область целевого объекта
  • 1
    
    dragover
    
    - перетаскиваемый элемент перемещается в области целевого объекта
  • 1
    
    dragleave
    
    - перетаскиваемый элемент покидает область целевого объекта
  • 1
    
    drag
    
    - момент начала процесса перетаскивания объекта
  • 1
    
    drop
    
    - момент, когда отпускается зажатая клавиша мыши (перетаскиваемый объект “роняется”)
  • 1
    
    dragend
    
    - момент завершения процесса перетаскивания объекта

Объект dataTransfer

Механизм Drag-and-Drop также имеет в своем составе объект dataTransfer, который служит для вспомогательных целей. В этом объекте хранится необходимая информация о событии перетаскивания. Помимо этого, в объект dataTransfer можно добавлять данные; а также считывать из него данные.

Свойства (наиболее важные) объекта dataTransfer:

  • 1
    
    dataTransfer.effectAllowed
    
    - задаем тип перетаскивания, которое пользователь может выполнять с элементом
  • 1
    
    dataTransfer.dropEffect
    
    - задаем внешний вид курсора мыши в соответствии с заданным типом перетаскивания

Методы (наиболее важные) объекта dataTransfer:

  • 1
    
    setData()
    
    - добавляет данные в нужном формате
  • 1
    
    clearData()
    
    - удаляет данные
  • 1
    
    setDragImage()
    
    - устанавливает изображение для перетаскивания с координатами курсора (0, 0 — левый верхний угол)
  • 1
    
    getData()
    
    - возвращает данные

Ниже будет рассматриваться практический пример реализации Drag-and-Drop на JavaScript.

HTML разметка

Базовая HTML-разметка будет простой:

<h2 id="dropStatus">application status</h2>
<h1 id="dropTitle">drop zone</h1>
<div id="dropZone"></div>
<div id="objectsZone">
    <div id="object1" class="objects">object 1</div>
    <div id="object2" class="objects">object 2</div>
    <div id="object3" class="objects">object 3</div>
</div>
<hr/>
<button type="button" id="readDropZone">get object data</button>

Что для чего служит в этой разметке?

Заголовок

1
id="dropStatus"
будет отображать текущее состояние процесса Drag-and-Drop. В него мы будет отправлять информацию о текущем состоянии Drag-and-Drop при помощи событий, о который говорилось выше.

Заголовок

1
id="dropTitle"
служит просто для декоративных целей.

Блок

1
id="dropZone"
является целевой областью - в нее мы будет перетаскивать объекты.

Объекты

1
id="object1"
,
1
id="object2"
,
1
id="object3"
- это перетаскиваемые объекты; их мы будем перемещать в область блока
1
id="dropZone"
.

Кнопка

1
id="readDropZone"
будет выводить информацию об перемещенных объектах.

В итоге разметка совместно со стилями будут выглядеть таким образом - JavaScript - Drag’n’Drop - Part 1.

JavaScript - разбираемся с событиями

Прежде чем детально рассматривать работу каждой из будущих функций по обработке событий, мне кажется, будет лучше просто понять, какие события и куда мы будем “вешать”.

Итак, начнем с перетаскиваемых элементов

1
id="object1"
,
1
id="object2"
,
1
id="object3"
. На каждый из них мы повесим два события:

  • 1
    
    dragstart
    
    - событие начала процесса перетаскивания элемента
  • 1
    
    dragend
    
    - событие окончания процесса перетаскивания элемента

Для каждого из элементов, при возникновении на нем события, мы будем запускать соответствующую функцию

1
dragStart
или
1
dragEnd
:

var objects = document.querySelectorAll('#objectsZone > .objects');
...
if ( objects ) {
    [].forEach.call(objects, function (el) {
        el.setAttribute('draggable', 'true');
        el.addEventListener('dragstart', dragStart, false);
        el.addEventListener('dragend', dragEnd, false);
    });
}

Обратим внимание на строку

1
el.setAttribute('draggable', 'true');
- здесь мы динамически добавляем для всех элементов с классом
1
.objects
атрибут
1
draggable="true"
, тем самым делая (благодаря HTML5) эти элементы доступными для перетаскивания.

На элемент

1
id="dropZone"
мы “повесим” гораздо больше событий:

  • 1
    
    dragenter
    
    - перетаскиваемый объект (например,
    1
    
    id="object1"
    
    ) входит в область целевого объекта (
    1
    
    id="dropZone"
    
    )
  • 1
    
    dragleave
    
    - перетаскиваемый объект (например,
    1
    
    id="object1"
    
    ) выходит из области целевого объекта (
    1
    
    id="dropZone"
    
    )
  • 1
    
    dragover
    
    - перетаскиваемый объект (например,
    1
    
    id="object1"
    
    ) перемещается внутри области целевого объекта (
    1
    
    id="dropZone"
    
    )
  • 1
    
    drop
    
    - перетаскиваемый объект (например,
    1
    
    id="object1"
    
    ) помещается внутри целевого объекта (
    1
    
    id="dropZone"
    
    )

И конечно же, для каждого события будет своя функция. JavaScript-код в итоге будет выглядеть таким образом:

var dropZone = document.querySelector('#dropZone');
...
if ( dropZone ) {
    dropZone.addEventListener('dragenter', dragEnter, false);
    dropZone.addEventListener('dragleave', dragLeave, false);
    dropZone.addEventListener('dragover', dragOver, false);
    dropZone.addEventListener('drop', dragDrop, false);
}

Ну и на кнопку

1
id="readDropZone"
мы “повесим” обычный код с функцией
1
readZone
:

var dropButton = document.querySelector('#readDropZone');
...
if ( dropButton ) {
    dropButton.addEventListener('click', readZone, false);
}

Если суммировать все вышесказанное, то общий вид handler’ов в нашем случае будет выглядеть таким образом:

// LISTENERS

if ( objects ) {
    [].forEach.call(objects, function (el) {
        el.setAttribute('draggable', 'true');
        el.addEventListener('dragstart', dragStart, false);
        el.addEventListener('dragend', dragEnd, false);
    });
}

if ( dropZone ) {
    dropZone.addEventListener('dragenter', dragEnter, false);
    dropZone.addEventListener('dragleave', dragLeave, false);
    dropZone.addEventListener('dragover', dragOver, false);
    dropZone.addEventListener('drop', dragDrop, false);
}

if ( dropButton ) {
    dropButton.addEventListener('click', readZone, false);
}

Далее будет детально останавливаться на каждой из функций - что она делает и для чего.

Функция dragStart (event)

Начнем с начала и запустим функцию для обработки старта события перетаскивания - события

1
dragstart
. Хочу сразу оговориться, что в процессе написания кода для обработки события перетаскивания важно четко представлять себе, какое событие и на каком элементе происходит.

В данном случае мы будем обрабатывать событие

1
dragstart
, которое возникает на перетаскиваемом элементе (
1
id="object1"
,
1
id="object2"
или
1
id="object3"
- не важно).

Событие

1
dragstart
в момент своего возникновения автоматически генерирует объект dataTransfer, который (как мне кажется) можно в общих чертах сравнить с событийным объектом Event; последний также хранит в себе множество данных о произошедшем событии. Некоторыми методами и свойствами объекта Event мы воспользуемся в нашем примере:

var dropStatus = document.querySelector('#dropStatus');
...
function dragStart (event) {
    dropStatus.innerHTML = 'Dragging the ' + event.target.getAttribute('id');
    event.dataTransfer.dropEffect = 'move';
    event.dataTransfer.setData('text', event.target.getAttribute('id'));
}

Функция

1
dragStart
при возникновении события “берет” элемент
1
dropStatus
и методом
1
innerHTML
“пихает” внутрь него строку, часть которой представляет из себя значение атрибута
1
id
элемента, на котором произошло событие (
1
event.target
).

Для объекта dataTransfer задается значение его свойства

1
dropEffect
-
1
move
.

В третьей строке для объекта dataTransfer с помощью метода

1
setData()
задается имя переменной
1
text
и значение для этой переменной - ID текущего элемента.

Функции dragEnter(), dragLeave(), dragOver()

Три функции, каждая из которых отслеживает событие, возникающее на элементе

1
dropZone
:

function dragEnter (event) {
    dropStatus.innerHTML = 'You are dragging over ' + event.target.getAttribute('id');
    this.classList.add('over');
}

function dragLeave (event) {
    dropStatus.innerHTML = 'You left the ' + event.target.getAttribute('id');
    this.classList.remove('over');
    this.removeAttribute('class');
}

function dragOver (event) {
    event.preventDefault();
}

Первые две функции -

1
dragEnter (event)
и
1
dragLeave (event)
очень похожи между собой. Каждая из них манипулирует содержимым заголовка
1
dropStatus
, сигнализируя о происходящем событии.

Третья функция

1
dragOver (event)
может показаться странной. Все ее назначение - это отмена действия по-умолчанию. Что это за действие по-умолчанию? Дело в том, что у браузеров имеется свой собственный (помимо HTML5) механизм реализации события перетаскивания Drag-and-Drop. И если его не отключить, то он не даст срабатывать нашему механизму.

Функция dragDrop (event)

Самая большая и самая важная функция в нашем коде. Она также срабатывает на событие, возникающее на элементе

1
dropZone
:

var droppedIN = false;
...
function dragDrop (event) {
    event.preventDefault();
    var elementID = event.dataTransfer.getData('text');
    var element = document.getElementById(elementID);
    event.target.appendChild(element);
    element.removeAttribute('draggable');
    element.classList.add('dragged');
    element.style.cursor = 'default';
    droppedIN = true;
    dropStatus.innerHTML = 'Element ' + elementID + ' dropped into the ' + event.target.getAttribute('id');
}

В строке

1
event.preventDefault();
мы снова отменяем действие по-умолчанию. На этот раз это касается самого перетаскиваемого элемента - ведь он может быть ссылкой и браузер выполнит переход по ней (действие по-умолчанию), что нам совсем не нужно.

В строке:

var elementID = event.dataTransfer.getData('text');

… мы из объекта dataTransfer получаем ID перетаскиваемого элемента. Вы же помните, что в функции

1
dragStart (event)
с помощью строки:

event.dataTransfer.setData('text', event.target.getAttribute('id'));

…мы его как раз получали?

Далее находим перетаскиваемый элемент по его ID:

var element = document.getElementById(elementID);

И помещаем его внутрь текущего активного элемента:

event.target.appendChild(element);

Далее убираем у перетаскиваемого элемента атрибут

1
draggable
- он больше не перетаскиваемый. Визуально сигнализируем об этом, изменив вид курсора мыши:

element.style.cursor = 'default';

И сообщаем об изменившемся статусе в заголовке:

dropStatus.innerHTML = 'Element ' + elementID + ' dropped into the ' + event.target.getAttribute('id');

Отдельного упоминания стоит строка

1
droppedIN = true;
. Это флаг, с помощью которого мы определяем, произошло ли событие
1
drop
или нет.

Может случиться так, что объект мы перетащили в область элемента

1
dropZone
, но передумали его помещать туда. И “отпустили” перетаскиваемый элемент за областью элемента
1
dropZone
. В результате событие
1
dragend
произошло, но событие
1
drop
не выполнилось.

Такую ситуацию обрабатывает функция

1
dragEnd()
:

function dragEnd() {
    if ( droppedIN === false ) {
        dropStatus.innerHTML = 'You let the ' + event.target.getAttribute('id') + ' to go!';
    }
    droppedIN = false;
}

Функция readZone ()

Последняя функция из нашего примера - это функция-счетчик. Ее задача - просто посчитать, сколько элементов на данный момент мы “бросили” в область

1
dropZone
:

function readZone () {
    var dropZoneChild = dropZone.children;
    for ( var i = 0; i < dropZoneChild.length; i++ ) {
        alert('Object ' + dropZoneChild[i].getAttribute('id') + ' is in ' + dropZone.getAttribute('id'));
    }
}

Нажимаем кнопку

1
dropButton
и alert’ом последовательно выводим все элементы, помещенные внутрь объекта
1
dropZone
.

Вот, в принципе, и все, что можно вкратце сказать. Осталось только взглянуть на готовый пример работы кода - JavaScript - Drag’n’Drop - Part 2.

На этом все. Здоровая критика и полезные замечания только приветствуются.


Этот скромный обзор не смог бы появиться, если бы не было двух полезных для меня ресурсов:

Есть более детальный обзор и более интересный пример задачи на JavaScript Drag-and-Drop размещен здесь:

Данная статья планируется как пошаговый обзор создания простой JavaScript-игры класса “Ball and Paddle” на Canvas. Примерами такой игры могут послужить старые DOS-е игры наподобие таких - Ball and Paddle.

Пример кода из этой статьи взят из видео-курса достаточно известного Интернет-ресурса, посвященного фронтенд-разработке - Udemy.

Почему Canvas и почему игра? Лично для меня процесс познания JavaScript сильно облегчается благодаря Canvas - так интереснее. А создание игры на Canvas - это еще интереснее!

Итак, с чего начнем? Дальше в меру своих сил буду стараться детально пошагово рассказывать, что делает тот или иной кусок кода. И начнем с базового набора - создания Canvas.

Базовый Canvas

HTML-разметка страницы будет предельно простой:

<body>
  <canvas id="canvas"></canvas>
  <script src="script.js"></script>
</body>

В JavaScript’е создадим две глобальные переменные - одну для элемента Canvas, вторую - для 2d-контекста Canvas. Когда parser браузера построит DOM-дерево документа (событие

1
DOMContentLoaded
), инициализируем обе переменные, выполним проверку удачного получения 2d-контекста Canvas и если проверка будет пройдена успешно, то динамически зададим размеры Canvas:

var canvas = null;
var ctx = null;

window.addEventListener('DOMContentLoaded', function () {
  canvas = document.querySelector('#canvas');
  ctx = canvas.getContext('2d');
  if ( ctx ) {
    canvas.width = 800;
    canvas.height = 500;
  }
}, false);

Базовые элементы игры

Основа Canvas была создана в предыдущем шаге. В этом шаге создадим три фигуры, которые будут учавствовать в игре. Таковыми фигурами будут:

  • фон игры
  • мячик (ball)
  • площадка (paddle)

Ниже я приведу JavaScript-код создания всех трех элементов, но сам код комментировать не буду, так как он очень простой и относится к основам Canvas:

var canvas = null;
var ctx = null;

window.addEventListener('DOMContentLoaded', function () {

  canvas = document.querySelector('#canvas');
  ctx = canvas.getContext('2d');

  if ( ctx ) {

    canvas.width = 800;
    canvas.height = 500;

    ctx.fillStyle = '#000';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    ctx.fillStyle = 'firebrick';
    ctx.beginPath();
    ctx.arc(50, 50, 10, 0, 360*Math.PI/180, true);
    ctx.fill();
    ctx.closePath();

    ctx.fillStyle = '#fff';
    ctx.fillRect(100, canvas.height-40, 100, 10);

  }
}, false);

Живой результат вышеприведенного кода можно посмотреть на этой странице - Lesson1-1. Это то, что должно получиться и что послужит заготовкой для игры.

Анимация мячика

В этом шаге предстоит сделать более интересные вещи. Во-первых, мы сделаем так, чтобы мячик начал двигаться как по-горизонтали, так и по-вертикали. А во-вторых, сделаем так, чтобы он вел себя как настоящий резиновый мячик - при ударе о стену отскакивал от нее и мчался в противоположном направлении.

Сделать это достаточно просто. Для этого нам понадобится одна из так называемых тайминговых функций JavaScript -

1
setInterval()
. А также немного воображения.

Анимация мячика будем делать по-простому принципу, по которому делается любой мультфильм или кино - мячик будет отрисовываться с заданной частотой (

1
1000/frames
), но каждый раз в новой позиции. В результате будет создаваться иллюзия его движения. Каждая новая позиция мячика - это его координата по оси X или Y с новым значением соответственно.

Чтобы мячик двигался достаточно быстро, изменять значения координат (

1
ballX += ballStepX
и
1
ballY += ballStepY
) мячика по оси X и Y будем с определенным шагом (
1
ballStepX
и
1
ballStepY
) - допустим, со значениями 5 или 6:

var canvas = null;
var ctx = null;

var frames = 24;

var ballX = 50;
var ballY = 50;
var ballStepX = 5;
var ballStepY = 6;
var ballRadius = 10;

window.addEventListener('DOMContentLoaded', function () {

  canvas = document.querySelector('#canvas');
  ctx = canvas.getContext('2d');

  if ( ctx ) {

    canvas.width = 800;
    canvas.height = 400;

    setInterval( function () {

      ballX += ballStepX;
      ballY += ballStepY;

      if ( ballX < 0 || ballX > canvas.width) {
        ballStepX *= -1;
      }
      if ( ballY < 0 || ballY > canvas.height ) {
        ballStepY *= -1;
      }

      ctx.fillStyle = '#000';
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      ctx.fillStyle = 'firebrick';
      ctx.beginPath();
      ctx.arc(ballX, ballY, ballRadius, 0, 360*Math.PI/180,true);
      ctx.fill();
      ctx.closePath();

      ctx.fillStyle = '#fff';
      ctx.fillRect(100, canvas.height-40, 100, 10);

    }, 1000/frames);

  }
}, false);

Эффект отскакивания от стенок (как резиновый мячик) обеспечивает проверка условий в участке кода:

...
if ( ballX < 0 || ballX > canvas.width) {
  ballStepX *= -1;
}
if ( ballY < 0 || ballY > canvas.height ) {
  ballStepY *= -1;
}
...

Здесь все просто - при выполнении условия знак переменной

1
ballStepX
или
1
ballStepY
будет меняться на противоположный. В результате значение переменной
1
ballX
или
1
ballY
будет возрастать или уменьшаться. Как следствие, мячик будет двигаться в одну или в другую сторону.

Живой пример приведенного выше кода можно посмотреть и изучить на этой странице - Lesson1-2.

Двигаем paddle

В этом шаге нужно заставить двигаться paddle при помощи мыши. Для этого по событию

1
mousemove
внутри элемента Canvas будем получать значение X-координаты курсора мыши. И передавать это значение элементу paddle, его X-координате левого верхнего угла. Тем самым мы заставим paddle двигаться. За все эти действия будет отвечать функция
1
mouseCoords()
:

...
function mouseCoords (event) {
  var canvasOffset = canvas.getBoundingClientRect();
  var htmlElement = document.documentElement;
  mouseX = event.clientX - canvasOffset.left - htmlElement.scrollLeft;
  paddleX = mouseX - paddleWidth/2;
}
...

Обратите внимание на последнюю строку функции -

1
paddleX = mouseX - paddleWidth/2;
. Переменная
1
paddleX
необходима для того, чтобы при выходе за границы Canvas элемент paddle скрывался ровно на половину своей ширины.

Также не забудем создать переменные для paddle и передать их в код для отрисовки фигуры:

...
var paddleX = null;
var paddleWidth = 100;
var paddleHeight = 10;
var paddleOffset = 40;
...
ctx.fillStyle = '#fff';
ctx.fillRect(paddleX, canvas.height - paddleOffset, paddleWidth, paddleHeight);
...

Живой пример приведенного выше кода можно посмотреть и изучить на этой странице - Lesson1-3. Подвигайте курсором мыши право-влево, чтобы увидеть эффект.

Мячик отскакивает от paddle

На этом этапе нужно сделать так, чтобы мячик отскакивал от paddle, когда последний оказывается на его пути. Выполнить эту задачу просто - ведь мячик уже отскакивает от “стен” Canvas. Следовательно, нужно научить мячик “видеть” еще и paddle.

Для этого сначала нужно опеределить внешние границы paddle - все его четыре стороны:

...
var paddleLeftEdge = paddleX;
var paddleRightEdge = paddleLeftEdge + paddleWidth;
var paddleTopEdge = canvas.height - paddleOffset;
var paddleBottomEdge = paddleTopEdge + paddleHeight;
...

Когда значения всех сторон будут определены, то можно будет подставить эти значения в условие - и дело сделано:

...
if ( ballX > paddleLeftEdge && ballX < paddleRightEdge && ballY > paddleTopEdge && ballY < paddleBottomEdge ) {
  ballStepY *= -1;
}
...

Живой пример приведенного выше кода можно посмотреть и изучить на этой странице - Lesson1-4. Подвигайте курсором мыши право-влево и постарайтесь поймать мячик с помощью paddle, чтобы увидеть эффект.

Угол отскока мячика

В этом шаге сделаем так, чтобы наша игра смотрелась более правильной с точки зрения физики и обычной природы. То есть, при разном угле попадания на paddle мячик должен отскакивать от него с разной скоростью. Чем острее угол падения, тем с большей скоростью отскакивает от paddle мячик.

Решается эта задача несколькими строками кода:

...
if ( ballX > paddleLeftEdge && ballX < paddleRightEdge && ballY > paddleTopEdge && ballY < paddleBottomEdge ) {
  ballStepY *= -1;
  var paddleCenter = paddleLeftEdge + paddleWidth/2;
  var ballDistance = ballX - paddleCenter;
  ballStepX = ballDistance * 0.35;
}
...

В первой строке

1
var paddleCenter = paddleLeftEdge + paddleWidth/2;
находится X-координата середины paddle. В строке
1
var ballDistance = ballX - paddleCenter;
определяется расстояние, на котором мячик соприкоснулся с paddle относительно его середины. В строке
1
ballStepX = ballDistance * 0.35;
полученная дистанция присваивается шагу приращения по оси Х мячика -
1
ballStepX
.

Логично предположить, что чем больше величина дистанции точки соприкосновения мячика относительно середины paddle, тем выше новая скорость движения мячика по-горизонтали. Чтобы эта скорость не была слишком высокой, ее необходимо уменьшить, умножив на 0.35, к примеру.

Живой пример приведенного выше кода можно посмотреть и изучить на этой странице - Lesson1-5.

Оптимизация кода

На данный момент наша задача по построению игры практически решена. Но остался один организационный момент.

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

Одна из таких функций уже была создана ранее - это функция

1
mouseCoords()
. Давайте преобразуемся и весь оставшийся код подобным образом:

...
function drawRect (leftX, leftY, boxWidth, boxHeight, boxFillColor) {
  ctx.fillStyle = boxFillColor;
  ctx.fillRect(leftX, leftY, boxWidth, boxHeight);
}
...
function drawBall(centerX, centerY, radius, fillColor) {
  ctx.fillStyle = fillColor;
  ctx.beginPath();
  ctx.arc(centerX, centerY, radius, 0, 360*Math.PI/180, true);
  ctx.fill();
  ctx.closePath();
}
...
function drawAll() {
  drawRect(0, 0, canvas.width, canvas.height, '#000');
  drawBall(ballX, ballY, ballRadius, 'firebrick');
  drawRect(paddleX, canvas.height - paddleOffset, paddleWidth, paddleHeight, '#fff');
}
...

Готовый пример преобразованного в функции кода можно посмотреть на этой странице - Lesson1-6.


На этом все.

Попытка разобраться с интересной возможностью canvas, которая называется “манипуляция с пикселями” (raw pixel). Основная суть этой возможности заключается в том, что можно получить информацию о цвете и альфа-канале любого пикселя, расположенного в произвольном месте canvas.

Образно выражаясь, можно сделать снимок (снять цветовой отпечаток) с любого участка canvas. Причем, этот отпечаток может быть любого размера (20х20 пикселей, 100х100 пикселей, 1х1 пиксель) - какой потребуется.

Техника Raw Pixel возможна благодаря объекту ImageData, у которого есть три свойства:

  • ImageData.width - ширина объекта в пикселях
  • ImageData.height - высота объекта в пикселях
  • ImageData.data - массив данных

Первые два свойства примитивно просты - это геометрические размеры объекта ImageData.

Самым интересным свойством объекта ImageData является последнее -

1
ImageData.data
.

Данное свойство в свою очередь является объектом, а если быть точнее - одномерным массивом. В этом массиве на каждый пиксель из “отпечатка” отводится 4 байта:

  • imageData.data[0] — значение красного цвета (число от 0 до 255);
  • imageData.data[1] — значение зеленого цвета (число от 0 до 255);
  • imageData.data[2] — значение синего цвета (число от 0 до 255);
  • imageData.data[3] — значение прозрачности (число от 0 до 255);

В результате получается значение цвета в формате RGBA.

У Canvas есть несколько методов для работы с объектом ImageData:

  • getImageData()
  • putImageData()
  • toDataURL()
  • createIamgeData()

Наиболее интересные и полезные два первых метода -

1
getImageData
и
1
putImageData
.

Метод getImageData

Метод

1
getImageData
позволяет создать экземпляр объекта ImageData на основе существующего canvas. Другими словами, этот метод “делает снимок” существующего canvas и преобразует этот “снимок” в объект ImageData.

Создадим простой пример для наглядного отображения работы метода

1
getImageData
:

window.addEventListener('DOMContentLoaded', function () {

  var ctx = document.querySelector('#canvas').getContext('2d');

  if ( ctx ) {

    var rawPixel;

    ctx.canvas.width = 400;
    ctx.canvas.height = 400;

    ctx.fillStyle = '#00f';
    ctx.fillRect(0, 0, 100, 100);

    ctx.fillStyle = 'rgba(0, 255, 0, .5)';
    ctx.fillRect(30,30,100,100);

    rawPixel = ctx.getImageData(40, 40, 1, 1);
    console.log(rawPixel.data[0], rawPixel.data[1], rawPixel.data[2], rawPixel.data[3]);

    rawPixel = ctx.getImageData(20, 20, 1, 1);
    console.log(rawPixel.data[0], rawPixel.data[1], rawPixel.data[2], rawPixel.data[3]);

  }
});

Что происходит в выше приведенном коде? Все просто - создаются два блока с синим и зеленым цветом, причем блок с зеленым цветом намеренно накладывается на блок с синим цветом.

А затем с помощью метода

1
getImageData
делаем снимок (снимаем отпечаток - если хотите) размером 1х1 пиксель с уже готового рисунка в canvas.

В первом случае левый верхний угол “отпечатка” будет находиться в точке (40, 40) координатной сетки canvas; во-втором случае левый верхний угол “отпечатка” будет находиться в точке (20, 20). В обоих случая ширина и высота “отпечатка” (снимка) равна 1x1 пиксель - то есть, будет делаться “снимок” размером (площадью) в 1 пиксель.

Результат метода

1
getImageData
помещается в переменную
1
rawPixel
. Так как эта переменная не что иное, как ссылка на конкретный экземпляр объекта ImageData, то мы можем воспользоваться свойством
1
data
этого объекта, а точнее - массивом данных. Обращаясь по индексу к каждому из элементов массива, в итоге мы получаем значение цвета данного пикселя в формате RGBA.

Площадь “снимка” можно произвольно увеличить и тогда массив данных также увеличиться. К примеру, такой код:

rawPixel = ctx.getImageData(20, 20, 2, 2);

… создаст массив вида:

Canvas getImageData

Как видим, к любому элементу этого массива можно обратиться по его индексу, чтобы получить значение цвета конкретного пикселя.

Сделаем приведенный выше пример более интересным (и наглядным для понимания) - добавим в него динамики. Преобразуем его так, чтобы в любой момент времени в отдельном информационном блоке выводился цвет (в формате RGBA) того участка canvas, над которым в данный момент находится курсор мыши. Будем считать, что курсор мыши имеет размер 1х1 пиксель:

  var picker = document.querySelector('#picker').getContext('2d');
  var colorBox = document.querySelector('#colorBox');

  var image = new Image();
  image.src = 'images/rhino.jpg';

  function getColor(event) {
    var cx = event.clientX - picker.canvas.offsetLeft;
    var cy = event.clientY - picker.canvas.offsetTop;
    var currentColor = picker.getImageData(cx, cy, 1, 1);
    colorBox.style.background = 'rgba(' + currentColor.data[0] + ',' + currentColor.data[1] + ',' + currentColor.data[2] + ',' + currentColor.data[3] + ')';
    colorBox.textContent = 'rgba(' + currentColor.data[0] + ',' + currentColor.data[1] + ',' + currentColor.data[2] + ',' + currentColor.data[3] + ')';
  }

  if ( picker ) {

    picker.canvas.width = 400;
    picker.canvas.height = 300;

    image.addEventListener('load', function () {
      picker.drawImage(image, 0, 0, picker.canvas.width, picker.canvas.height);
      image.crossOrigin = "Anonymous";
    });

    picker.canvas.addEventListener('mousemove', getColor);

  }

В этом коде функция

1
getColor
при движении курсора мыши (событие
1
mousemove
) над областью canvas (
1
picker.canvas
) считывает координаты этого курсора в переменные
1
cx
и
1
cy
. Значения этих переменных передаются в качестве параметров методу
1
getImageData
, результат работы которого помещается в переменную
1
currentColor
.

Из переменной

1
currentColor
с помощью свойства
1
data
достаются значения (как элементы массива, по индексу) для каждого из RGBA-каналов. Все четыре значения конкатенируются и передаются в виде строкового значения - как фоновый RGBA-цвет для блока
1
colorBox
.

Для пущей наглядности с помощью свойства

1
textContent
в блок
1
colorBox
передается текущее значение цвета.

Представленный выше функционал - не что иное, как обычный Color Picker в любом графическом редакторе. Просто в данном примере достаточно изменить событие

1
mousemove
на событие
1
click
, чтобы все заработало как надо.

Метод putImageData

Возможности метода

1
putImageData
значительно шире, так как этот метод позволяет редактировать canvas. Другими словами, с помощью метода
1
getImageData
получается конкретный экземпляр объекта ImageData из текущего canvas.

Затем при помощи произвольной функции производится преобразование данных этого объекта.

Отредактировванные данные возвращаются обратно в canvas с помощью метода

1
putImageData
.

Давайте на конкретном примере рассмотрим описанный выше пример:

window.addEventListener('DOMContentLoaded', function () {

  var ctx = document.querySelector('#canvas').getContext('2d');

  if ( ctx ) {

    ctx.canvas.width = 400;
    ctx.canvas.height = 300;

    var image = new Image();
    image.src = 'images/rhino.jpg';

    image.addEventListener('load', function () {
      imageDraw(this);
    });

    function imageDraw(img) {

      ctx.drawImage(img,0,0,ctx.canvas.width,ctx.canvas.height);
      var imageData = ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height);

      function imageInvert() {
        for ( var i = 0; i < imageData.data.length; i += 4 ) {
          imageData.data[i] = 255 - imageData.data[i];
          imageData.data[i + 1] = 255 - imageData.data[i + 1];
          imageData.data[i + 2] = 255 - imageData.data[i + 2];
        }
        ctx.putImageData(imageData,0,0);
      }

      function grayScaleImage() {
        for ( var i = 0; i < imageData.data.length; i += 4 ) {
          var averageColor = ( imageData.data[i] + imageData.data[i+1] + imageData.data[i+2] ) / 3;
          imageData.data[i] = imageData.data[i+1] = imageData.data[i+2] = averageColor;
        }
        ctx.putImageData(imageData,0,0);
      }

      document.querySelector('#graScaleColor').addEventListener('click', grayScaleImage);
      document.querySelector('#invertColor').addEventListener('click', imageInvert);

    }

  }

}, false);

В приведенном выше коде динамически (с помощью конструктора) создается экземпляр изображения и задается значение для его атрибута

1
src
. Затем на это изображение “вешается” функция, задача которой - отрисовать это изображение в canvas.

Изображение отрисовывается в canvas:

ctx.drawImage(img,0,0,ctx.canvas.width,ctx.canvas.height);

… и тут же с него снимается отпечаток - создается объект ImageData:

var imageData = ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height);

Полученный объект

1
imageData
обрабатывается двумя произвольными функциями -
1
imageInvert()
и
1
grayScaleImage()
при событии
1
click
на кнопках:

document.querySelector('#graScaleColor').addEventListener('click', grayScaleImage);
document.querySelector('#invertColor').addEventListener('click', imageInvert);

Функция

1
imageInvert()
инвертирует цвета - “пробегается” по массиву
1
imageData.data
и производит простое вычитание текущего значения цвета из 255:

imageData.data[i] = 255 - imageData.data[i];
imageData.data[i + 1] = 255 - imageData.data[i + 1];
imageData.data[i + 2] = 255 - imageData.data[i + 2];

Функция

1
grayScaleImage()
также “пробегается” по массиву
1
imageData.data
, но при этом производит усреднение значения цвета для каждого из пикселов:

var averageColor = ( imageData.data[i] + imageData.data[i+1] + imageData.data[i+2] ) / 3;
imageData.data[i] = imageData.data[i+1] = imageData.data[i+2] = averageColor;

Приведем еще один пример инвертации цвета. В произвольной функции будет производится перемена цвета местами - значение красного канала будет помешаться в зеленый канал; значение зеленого цвета будет помещаться в красный канал:

window.addEventListener('DOMContentLoaded', function () {

  var ctxCanvas = document.querySelector('#replaceImage').getContext('2d');
  var originImage = document.querySelector('#originImage');

  if ( ctxCanvas ) {

    ctxCanvas.canvas.width = originImage.naturalWidth;
    ctxCanvas.canvas.height = originImage.naturalHeight;
    ctxCanvas.drawImage(originImage, 0, 0);
    ctxCanvas.canvas.style.display = 'none';

    function shiftColors () {
      var imageData = ctxCanvas.getImageData(0, 0, ctxCanvas.canvas.width, ctxCanvas.canvas.height);
      for ( var i = 0; i < imageData.data.length; i += 4 ) {
        var dump = imageData.data[i];
        imageData.data[i+1] = imageData.data[i];
        imageData.data[i] = dump;
      }
      ctxCanvas.putImageData(imageData, 0, 0);
      ctxCanvas.canvas.style.display = 'inline';
    }

    document.querySelector('#btnReplace').addEventListener('click', shiftColors, false);

  }

}, false);

В этом коде canvas задаются размеры оригинального изображения:

ctxCanvas.canvas.width = originImage.naturalWidth;
ctxCanvas.canvas.height = originImage.naturalHeight;

Затем в цикле производится взаимозамещение красного и зеленого каналов:

var dump = imageData.data[i];
imageData.data[i+1] = imageData.data[i];
imageData.data[i] = dump;

Метод toDataURL()

Еще одним интересным методом при работе с замещением пикселей является метод

1
toDataURL()
. Суть его проста - также как и метод
1
getImageData()
, этот метод получается “снимок” текущего canvas и сохраняет результат в виде изображения в двух форматах на выбор -
1
jpg
или
1
png
.

Синтаксис этого метода таков:

var imageJPG = canvas.toDataURL('image/png');
var imagePNG = canvas.toDataURL('image/jpg',1);

Стоит обратить внимание на явное указание (с помощью MIME) формата, в котором производится сохранение изображения. Помимо этого, при сохранении в формате

1
jpg
возможно указание второго параметра, который служит для задания качества сохраняемого изображения (от 0 до 1).

Кроме этого, стоит обратить внимание, что изображение кодируется в base64 формате и именно в этом виде может быть использовано; но никак не в форматах

1
jpg
или
1
png
.

Для внесения большей ясности давайте рассмотрим еще один интересный пример, в котором будет показана работа метода

1
toDataURL()
:

window.addEventListener('DOMContentLoaded', function () {

  var mouseDown = false;
  var drawCtx = document.querySelector('#draw').getContext('2d');

  var link = document.createElement('a');
  link.innerHTML = 'download image';
  link.href = '#';
  link.download = 'result.png';
  document.body.insertBefore(link, drawCtx.canvas);

  if ( drawCtx ) {

    drawCtx.canvas.width = 400;
    drawCtx.canvas.height = 400;
    drawCtx.fillStyle = '#f00';

    function drawCanvas (event) {
      if ( mouseDown ) {
        var xCoor = event.clientX - drawCtx.canvas.offsetLeft;
        var yCoor = event.clientY - drawCtx.canvas.offsetTop;
        drawCtx.beginPath();
        drawCtx.arc(xCoor, yCoor, 2, 0, 360*Math.PI/180);
        drawCtx.fill();
      }
    }

    drawCtx.canvas.addEventListener('mousemove', drawCanvas, false);

    drawCtx.canvas.addEventListener('mousedown', function () {
      mouseDown = true;
    }, false);

    drawCtx.canvas.addEventListener('mouseup', function () {
      link.href = drawCtx.canvas.toDataURL('image/png');
      mouseDown = false;
    }, false);

  }

}, false);

Что мы имеем в приведенном выше коде? Ну, во-первых, это конечно же canvas. На этом canvas’е при помощи мыши мы можем рисовать - за это отвечает функция

1
drawCanvas()
.

У этой функции работа проста, но есть одна фишка - это флаг

1
mouseDown
. Когда курсор мыши попадает в область canvas и начинает двигаться в пределах области этого canvas (событие
1
mousemove
), то запускается функция
1
drawCanvas (event)
.

Но результата работы этой функции нет, так условие внутри этой функции не срабатывает из-за значения флага

1
mouseDown == false
.

С помощью событий

1
mousedown
и
1
mouseup
в коде производится переключение состояний флага
1
mouseDown
из
1
false
в
1
true
и обратно.

Также при событии

1
mouseup
производится “снимок” текущего canvas и помещение его в атрибут
1
href
ссылки
1
a
:

link.href = drawCtx.canvas.toDataURL('image/png');

Обратите внимание на редкий HTML5-атрибут

1
download
, в котором задается имя скачиваемого изображения.

Если для ссылки указан атрибут

1
download
, то при клике по этой ссылке перехода никуда не происходит, а выполняется скачивание изображения с именем по-умолчанию (заданном в атрибуте
1
download
).

Заключение

Вот в принципе и все о манипуляцих с пикселями (raw pixel) в canvas. На самом деле это конечно же не все, что можно рассказать и сделать с помощью этой техники.

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

Этой статьи бы не было, если бы не существовали источники, которые и помогли ей родиться:

P.S.

“Живых” примеров кода из этого обзора я не привожу, ибо лень ) Адекватная критика и замечания приветствуются )


На этом все.

Небольшая заметка, посвященная вопросу настройки тем оформления (skins) в популярном и очень полезном консольном файловом менеджере Midnight Commander.

И попутно затрагивается вопрос с настройкой отображения кириллицы в Midnight Commander под управлением OSX.

Пару хвалебных слов

Midnight Commander - это консольный файловый менеджер. Консольный - потому что он работает в консоли, из эмулятора терминала. Внешне он очень похож на аналогичный Far Manager под операционной системой Windows.

Midnight Commander - очень легкий, потому что для своей работы он использует псевдографику.

Midnight Commander - обладает большими возможностями, больше половины которых обычный пользователь даже не применяет на практике.

Устанавливается Midnight Commander из пакетного менеджера, так как эта утилита имеется в репозиториях любого дистрибутива Linux. В Debian \ Ubuntu \ Mint установка производится такой командой:

sudo apt-get install mc

Оформление Midnight Commander

После установки Midnight Commander и его первоначального запуска внешний вид программы будет примерно таким:

Midnight Commander Default Skin

Прямо скажем, зрелище не очень привлекательное, особенно - зеленый шрифт на синем фоне. Это тема оформления (skin) по умолчанию для Midnight Commander и называется она также - default.

Но оформление Midnight Commander можно (и нужно) поменять и сделать это просто, так как эта программа идет с предустановленным набором тем оформления.

Готовые темы оформления (skins) после установки Midnight Commander располагаются по пути:

~|⇒ ll /usr/share/mc/skins

Туда можно заглянуть и выбрать, что понравиться:

~|⇒ ll /usr/share/mc/skins
total 212K
-rw-r--r-- 1 root root 3,0K Dec  5  2013 darkfar.ini
-rw-r--r-- 1 root root 3,0K Dec  5  2013 dark.ini
-rw-r--r-- 1 root root 2,7K Dec  5  2013 default.ini
-rw-r--r-- 1 root root 2,7K Dec  5  2013 double-lines.ini
-rw-r--r-- 1 root root 3,1K Dec  5  2013 featured.ini
-rw-r--r-- 1 root root 2,2K Dec  5  2013 gotar.ini
-rw-r--r-- 1 root root 2,3K Dec  5  2013 mc46.ini
-rw-r--r-- 1 root root 4,0K Dec  5  2013 modarcon16-defbg.ini
-rw-r--r-- 1 root root 4,0K Sep 24  2012 modarcon16-defbg-thin.ini
-rw-r--r-- 1 root root 4,0K Dec  5  2013 modarcon16.ini
-rw-r--r-- 1 root root 4,0K Dec  5  2013 modarcon16root-defbg.ini
-rw-r--r-- 1 root root 4,0K Sep 24  2012 modarcon16root-defbg-thin.ini
-rw-r--r-- 1 root root 4,0K Dec  5  2013 modarcon16root.ini
-rw-r--r-- 1 root root 4,0K Sep 24  2012 modarcon16root-thin.ini
-rw-r--r-- 1 root root 4,0K Sep 24  2012 modarcon16-thin.ini
-rw-r--r-- 1 root root 4,1K Dec  5  2013 modarin256-defbg.ini
-rw-r--r-- 1 root root 4,1K Sep 24  2012 modarin256-defbg-thin.ini
-rw-r--r-- 1 root root 4,1K Dec  5  2013 modarin256.ini
-rw-r--r-- 1 root root 4,1K Dec  5  2013 modarin256root-defbg.ini
-rw-r--r-- 1 root root 4,1K Sep 24  2012 modarin256root-defbg-thin.ini
-rw-r--r-- 1 root root 4,1K Dec  5  2013 modarin256root.ini
-rw-r--r-- 1 root root 4,1K Sep 24  2012 modarin256root-thin.ini
-rw-r--r-- 1 root root 4,1K Sep 24  2012 modarin256-thin.ini
-rw-r--r-- 1 root root 2,9K Dec  5  2013 nicedark.ini
-rw-r--r-- 1 root root 5,4K Dec  5  2013 sand256.ini
-rw-r--r-- 1 root root 3,9K Dec  5  2013 xoria256.ini

Выбрать тему оформления для Midnight Commander можно командой:

~|⇒ mc -S darkfar

Здесь ключ -S указывает, что при запуске Midnight Commander необходимо использовать тему оформления. Имя темы оформления (skin) указывается после ключа. Результат приведенной выше команды будет следующим:

Midnight Commander Darkfar Skin

Уже значительно лучше, не правда ли? Таким образом можно перебрать все имеющиеся в комплекте темы и выбрать понравившуюся.

Когда тема оформления выбрана, нужно прописать ее в конфигурационном файле Midnight Commander, чтобы последний запускался каждый раз именно с этим skin’ом.

Файл настроек Midnight Commander располагается по пути:

~|⇒ ll ~/.config/mc/ini

и запускается на редактирование таким образом:

~|⇒ nano ~/.config/mc/ini

В этом файле нужно найти строчку skin и изменить значение параметра на название файла темы (из /usr/share/mc/skins):

...
editor_filesize_threshold=64M
mcview_eof=
ignore_ftp_chattr_errors=true
skin=modarin256

[Layout]
message_visible=1
keybar_visible=1
...

Обратите внимание на название skin’а в данном случае - modarin256. Здесь 256 - это количество цветов отображения, которые используются в этой теме.

По умолчанию в Linux Mint консоль не поддерживает отображение такого количества цветов. Если запустить Midnight Commander с темой modarin256 (к примеру), то появится ошибка и предложении использовать тему по-умолчанию (default).

Включить поддержку отображения 256 цветов в консоли можно, добавив строку export TERM=xterm-256color в файле .bash_profile (если используется BASH), в файле .zshrc (если используется ZSH), в файле .profile (если используется macOS).

В моем случае используется ZSH и файл .zshrc будет выглядеть таким образом:

# User configuration
...
export TERM=xterm-256color
...

Если все сделано без ошибок, то запуск Midnight Commander выдаст такой результат (используется тема оформления modarin256):

Midnight Commander Modarin Skin

Можно попробовать тему xoria256 - хорошо проработанная тема, с которой также приятно работать. Об этой теме была статья на Хабрахабр - Цветовая схема Xoria256 для Midnight Commander:

Midnight Commander Xoria Skin

Midnight Commander и кириллица в macOS

Установка и настройка Midnight Commander в операционной системе OSX мало отличается от аналогичных действий в Linux.

Устанавливать Midnight Commander в macOS проще всего с помощью Homebrew:

$ brew update
$ brew install mc

Не забываем включить поддержку 256 цветов в консоли OSX, если хотим использовать богатые цветом темы оформления Midnight Commander, такие как modarin256 или xoria256.

Для этого редактируем файл .bash_profile или файл .zshrc (если используется ZSH):

...
export TERM=xterm-256color
...

Дополнительным шагом будет добавление в файл .bash_profile (или .zshrc) двух строчек:

...
export LC_CTYPE=en_US.UTF-8
export LC_ALL=en_US.UTF-8
...

… для того, чтобы в Midnight Commander правильно отображались русскоязычные имена файлов и директорий. Иначе вместо вразумительных имен файлов будут одни вопросительные знаки.

Вариант с добавлением строки:

export LANG=ru_RU.UTF-8

… в файле .bash_profile у меня не сработал.


На этом все.