// Фишка. function Фишка() { this.позиция = null; this.узел = null; this.группа = null; this.нейтраль = null; this.выбор = null; } Фишка.prototype.показатьВыбор = function() { this.узел.задатьМатериал(this.выбор); } Фишка.prototype.показатьНейтраль = function() { this.узел.задатьМатериал(this.нейтраль); } Фишка.prototype.показать = function() { this.узел.задатьМаску(0x0); } Фишка.prototype.скрыть = function() { // Специальная маска OpenSceneGraph для скрытия узла от камеры. this.узел.задатьМаску(0xFFFFFFFF); } Object.defineProperty(Фишка.prototype, 'имя', { get: function() { return this.узел.имя; } }); // Раскладка. function Раскладка() { this.версия = ""; this.ширина = 0; this.высота = 0; this.глубина = 0; this.позиции = []; // Для внутреннего пользования. this.поля = []; }; Раскладка.prototype.разобратьСлужебнуюИнформацию = function(содержимое) { const ключи = { "версия": "kmahjongg-layout-v", "комментарий": "#", "ширина": "w", "высота": "h", "глубина": "d" }; // ВНИМАНИЕ Версия 1.0 предполагает заданные заранее ширину и высоту. this.версия = ""; this.глубина = 0; this.ширина = 32; this.высота = 16; this.поля = []; var поле = []; var строки = содержимое.split("\n"); for (var номер in строки) { var строка = строки[номер].trim(); // Пропуск. if (строка.startsWith(ключи.комментарий)) { continue; } // Служебная информация. if (строка.startsWith(ключи.версия)) { this.версия = строка.split(ключи.версия)[1]; } else if (строка.startsWith(ключи.ширина)) { this.ширина = строка.split(ключи.ширина)[1]; } else if (строка.startsWith(ключи.высота)) { this.высота = строка.split(ключи.высота)[1]; } else if (строка.startsWith(ключи.глубина)) { this.глубина = строка.split(ключи.глубина)[1]; } // Поле. else { поле.push(строка); if (поле.length >= this.высота) { this.поля.push(поле.slice()); поле = []; } } } if (this.глубина == 0) { this.глубина = this.поля.length; } } Раскладка.prototype.разобратьПозиции = function() { this.позиции = []; for (var номер in this.поля) { var поле = this.поля[номер]; for (var строка = 0; строка < this.высота - 1; ++строка) { for (var столбец = 0; столбец < this.ширина - 1; ++столбец) { if ( поле[строка][столбец] == "1" && поле[строка][столбец + 1] == "2" && поле[строка + 1][столбец] == "4" && поле[строка + 1][столбец + 1] == "3" ) { this.позиции.push([номер, строка, столбец]); } } } } } Раскладка.prototype.разобрать = function(содержимое) { this.разобратьСлужебнуюИнформацию(содержимое); this.разобратьПозиции(); } Раскладка.prototype.отладка = function() { var о = ""; о += "Отладочная информация о раскладке:\n"; о += " версия: '" + this.версия + "'\n"; о += " ширина: '" + this.ширина + "'\n"; о += " высота: '" + this.высота + "'\n"; о += " глубина: '" + this.глубина + "'\n"; о += " позиции:\n"; for (var номер in this.позиции) { var п = this.позиции[номер]; о += " п(" + номер + "): '" + п[0] + ", " + п[1] + ", " + п[2] + "'\n"; } return о; } // Игра. мж.ресурсы = [ [Б + "модели/фишка/2019-09-08.osgt", "mod"], [Б + "текстуры/заглушка.png", "tex.stub"], [Б + "шейдеры/освещение-изображение.vert", "ver"], [Б + "шейдеры/освещение-изображение.frag", "fra"], [Б + "раскладки/X_shaped.layout", "lay"], ]; мж.начали = new Уведомитель(); мж.начать = function() { муром.камера.позиция = [0, -70, 0]; муром.камера.вращение = [90, 0, 0]; мж.сцена = муром.узлы.создатьСферу("sce", 0); мж.сцена.вращение = [60, 0, 0]; var мир = муром.узлы.узел("mir"); мир.добавитьДитя(мж.сцена); мж.начали.уведомить(); }; мж.задатьНейтральныйМатериал = function() { var мат = муром.материалы.создатьМатериал("N"); var изо = муром.ресурсы.ресурс("tex.stub"); мат.задатьТекстуру("image", изо); var вер = муром.ресурсы.ресурс("ver").содержимое; var фра = муром.ресурсы.ресурс("fra").содержимое; мат.задатьШейдеры(вер, фра); мж.сцена.задатьМатериал(мат); }; мж.разобратьРаскладку = function() { var содержимое = муром.ресурсы.ресурс("lay").содержимое; мж.раскладка = new Раскладка(); мж.раскладка.разобрать(содержимое); }; мж.фишки = []; мж.создатьФишки = function() { for (var номер in мж.раскладка.позиции) { var ф = new Фишка(); ф.позиция = мж.раскладка.позиции[номер]; мж.фишки.push(ф); } }; мж.размерФишки = { "ширина": 2.0, "высота": 3.0, "глубина": 1.0, }; мж.создатьУзлыФишек = function() { const шагФишки = 2.0; const коэффициенты = { "x": мж.размерФишки.ширина / шагФишки, "y": -мж.размерФишки.высота / шагФишки, "z": мж.размерФишки.глубина, }; var мод = муром.ресурсы.ресурс("mod"); for (var номер in мж.фишки) { var имя = номер.toString(); var узел = муром.узлы.создатьУзел(имя, мод); мж.сцена.добавитьДитя(узел); var ф = мж.фишки[номер]; var п = ф.позиция; узел.позиция = [ п[2] * коэффициенты.x, п[1] * коэффициенты.y, п[0] * коэффициенты.z, ]; ф.узел = узел; } }; мж.центрироватьСцену = function() { var границы = { "лево": 1000, "право": -1000, "верх": -1000, "низ": 1000, }; for (var номер in мж.фишки) { const ф = мж.фишки[номер]; const x = ф.узел.позиция[0]; const y = ф.узел.позиция[1]; if (x < границы.лево) { границы.лево = x; } if (x > границы.право) { границы.право = x; } if (y < границы.низ) { границы.низ = y; } if (y > границы.верх) { границы.верх = y; } } const ширина = границы.право - границы.лево + мж.размерФишки.ширина; const высота = границы.верх - границы.низ + мж.размерФишки.высота; мж.сцена.позиция = [-ширина / 2.0, 0, высота / 2.0]; }; // http://www.rubl.com/rules/mahjong-solitaire-rules.html мж.задатьФишкамГруппыПоследовательно = function() { var группы = []; // Генерируем группы. const группВсего = 42; const группПо4Дубля = 34; for (var г = 0; г < группВсего; ++г) { const четыреДубля = (г < группПо4Дубля); const колвоДублей = четыреДубля ? 4 : 1; for (var д = 0; д < колвоДублей; ++д) { группы.push(г); } } // Задаём. for (var номер in мж.фишки) { var ф = мж.фишки[номер]; ф.группа = Number(группы[номер]); } }; мж.применитьТемуФишек = function() { eval(dl["тема.заглушка"]); const R = RR(); // Создать текстуры. var обозначения = []; var текстуры = []; for (var номер in R) { var обозначение = R[номер][0]; обозначения.push(обозначение); var имя = "tile.tex/" + обозначение; var содержимое = R[номер][1]; var текстура = муром.ресурсы.создатьРесурс(имя, содержимое); текстуры.push(текстура); } // Создать материалы. var нейтраль = []; var выбор = []; var вер = муром.ресурсы.ресурс("ver").содержимое; var фра = муром.ресурсы.ресурс("fra").содержимое; // Нейтраль. for (var номер = 0; номер < 42; ++номер) { var имя = "tile.mat/" + обозначения[номер]; var изо = текстуры[номер]; var мат = муром.материалы.создатьМатериал(имя); мат.задатьТекстуру("image", изо); мат.задатьШейдеры(вер, фра); нейтраль.push(мат); } // Выбор. for (var номер = 42; номер < 83; ++номер) { var имя = "tile.mat/" + обозначения[номер]; var изо = текстуры[номер]; var мат = муром.материалы.создатьМатериал(имя); мат.задатьТекстуру("image", изо); мат.задатьШейдеры(вер, фра); выбор.push(мат); } // Применить тему. for (var номер in мж.фишки) { var ф = мж.фишки[номер]; ф.нейтраль = нейтраль[ф.группа]; ф.выбор = выбор[ф.группа]; ф.показатьНейтраль(); } }; мж.маскаВыбора = 0x2; мж.сделатьФишкиВыбираемыми = function() { for (var номер in мж.фишки) { var ф = мж.фишки[номер]; ф.узел.задатьМаску(мж.маскаВыбора); } } мж.индексПозиции = function(п) { return п[0] * 1000000 + п[1] * 1000 + п[2]; }; мж.индексПозиций = {}; мж.проиндексироватьПозиции = function() { for (var номер in мж.фишки) { var ф = мж.фишки[номер]; var и = мж.индексПозиции(ф.позиция); мж.индексПозиций[и] = true; } }; мж.естьСоседи = function(позиция, смещениеПоля, смещениеСтолбца) { for (var смещениеРяда = -1; смещениеРяда <= 1; ++смещениеРяда) { var сосед = [ Number(позиция[0]) + смещениеПоля, Number(позиция[1]) + смещениеРяда, Number(позиция[2]) + смещениеСтолбца, ]; var и = мж.индексПозиции(сосед); if (и in мж.индексПозиций) { return true; } } // Соседей нет. return false; }; мж.можноВыбратьФишку = function(ф) { // Проверяем наличие фишек с левой и правой сторон одновременно. var слева = this.естьСоседи(ф.позиция, 0, -2); var справа = this.естьСоседи(ф.позиция, 0, 2); if (слева && справа) { return false; } // Проверяем наличие фишек непосредственно сверху. for (var смещение = -1; смещение <= 1; ++смещение) { if (this.естьСоседи(ф.позиция, 1, смещение)) { return false; } } // Фишка не заблокирована. return true; }; мж.номерВыбраннойФишки = null; мж.выбраннаяФишка = null; мж.выбранаФишка = new Уведомитель(); мж.выбратьФишку = function() { // Определить щелчок. if (муром.мышь.нажатыеКнопки.length == 1) { var узел = муром.камера.узелВПозиции( муром.мышь.позиция, мж.маскаВыбора ); if (узел) { var номер = Number(узел.имя); var ф = мж.фишки[номер]; if (мж.можноВыбратьФишку(ф)) { мж.номерВыбраннойФишки = номер; мж.выбраннаяФишка = ф; мж.выбранаФишка.уведомить(); } } } }; мж.отладитьВыборФишки = function() { мж.выбранаФишка.подписать(function(){ console.log( "Выбор. Номер: '" + мж.номерВыбраннойФишки + "' Группа: '" + мж.выбраннаяФишка.группа + "'" ); }); }; мж.показатьВыбраннуюФишку = function() { мж.выбраннаяФишка.показатьВыбор(); }; мж.выбранныеФишки = []; мж.фишкиРазличаются = new Уведомитель(); мж.фишкиСовпадают = new Уведомитель(); мж.фишкиСравнили = new Уведомитель(); мж.сравнитьВыбранныеФишки = function() { // Собираем. мж.выбранныеФишки.push(мж.выбраннаяФишка); // Удостоверяемся в наличии пары фишек. if (мж.выбранныеФишки.length != 2) { return; } var ф1 = мж.выбранныеФишки[0]; var ф2 = мж.выбранныеФишки[1]; // Убираем дубликат при двойном выборе одной фишки. if (ф1.имя == ф2.имя) { мж.выбранныеФишки.shift(); return; } // Сравниваем. if (ф1.группа == ф2.группа) { мж.фишкиСовпадают.уведомить(); } else { мж.фишкиРазличаются.уведомить(); } мж.фишкиСравнили.уведомить(); }; мж.отладитьСравнениеФишек = function() { мж.фишкиСовпадают.подписать(function(){ console.log("Фишки совпадают"); }); мж.фишкиРазличаются.подписать(function(){ console.log("Фишки различаются"); }); }; мж.скрытьСовпадающиеФишки = function() { мж.выбранныеФишки[0].скрыть(); мж.выбранныеФишки[1].скрыть(); }; мж.убратьСовпадающиеФишки = function() { var ф1 = мж.выбранныеФишки[0]; var ф2 = мж.выбранныеФишки[1]; var и1 = мж.индексПозиции(ф1.позиция); var и2 = мж.индексПозиции(ф2.позиция); delete мж.индексПозиций[и1]; delete мж.индексПозиций[и2]; }; мж.очиститьОтображениеВыбора = function() { мж.выбранныеФишки[0].показатьНейтраль(); мж.выбранныеФишки[1].показатьНейтраль(); }; мж.очиститьВыбор = function() { мж.номерВыбраннойФишки = 0; мж.выбраннаяФишка = null; мж.выбранныеФишки = []; }; мж.оставшиесяФишки = []; мж.задатьОставшиесяФишки = function() { мж.оставшиесяФишки = мж.фишки.slice(); }; мж.обновилиОставшиесяФишки = new Уведомитель(); мж.обновитьОставшиесяФишки = function() { var и1 = мж.оставшиесяФишки.indexOf(мж.выбранныеФишки[0]); мж.оставшиесяФишки.splice(и1, 1); var и2 = мж.оставшиесяФишки.indexOf(мж.выбранныеФишки[1]); мж.оставшиесяФишки.splice(и2, 1); мж.обновилиОставшиесяФишки.уведомить(); }; мж.доступныеДляВыбораФишки = function() { var фишки = []; for (var номер in мж.оставшиесяФишки) { var ф = мж.оставшиесяФишки[номер]; if (мж.можноВыбратьФишку(ф)) { фишки.push(ф); } } return фишки; }; мж.естьХод = function() { var фишки = мж.доступныеДляВыбораФишки(); for (var н1 in фишки) { for (var н2 in фишки) { var ф1 = фишки[н1]; var ф2 = фишки[н2]; if ( (ф1.имя != ф2.имя) && (ф1.группа == ф2.группа) ) { return true; } } } return false; } мж.победа = new Уведомитель(); мж.поражение = new Уведомитель(); мж.проверитьЗавершение = function() { var кончилисьФишки = (мж.оставшиесяФишки.length == 0); if (кончилисьФишки) { мж.победа.уведомить(); return; } if (!мж.естьХод()) { мж.поражение.уведомить(); } }; мж.отладитьЗавершение = function() { мж.победа.подписать(function(){ console.log("Игра завершена. ПОБЕДА!"); }); мж.поражение.подписать(function(){ console.log("Игра завершена. ПОРАЖЕНИЕ :("); }); }; мж.задатьОтображениеЗавершения = function() { мж.победа.подписать(function(){ муром.камера.цветОчистки = [0.2, 0.5, 0.2]; }); мж.поражение.подписать(function(){ муром.камера.цветОчистки = [0.5, 0.2, 0.2]; }); }; // Игра. мж.начали.подписатьМного([ мж.задатьНейтральныйМатериал, мж.разобратьРаскладку, мж.создатьФишки, мж.создатьУзлыФишек, мж.центрироватьСцену, мж.задатьФишкамГруппыПоследовательно, мж.применитьТемуФишек, мж.сделатьФишкиВыбираемыми, мж.отладитьВыборФишки, мж.проиндексироватьПозиции, мж.отладитьСравнениеФишек, мж.задатьОставшиесяФишки, мж.отладитьЗавершение, мж.задатьОтображениеЗавершения, ]); муром.мышь.нажатыеКнопкиИзменились.подписать(мж.выбратьФишку); мж.выбранаФишка.подписатьМного([ мж.показатьВыбраннуюФишку, мж.сравнитьВыбранныеФишки, ]); мж.фишкиСовпадают.подписатьМного([ мж.убратьСовпадающиеФишки, мж.обновитьОставшиесяФишки, function(){ setTimeout(мж.скрытьСовпадающиеФишки, 200); }, ]); мж.фишкиРазличаются.подписать(function(){ setTimeout(мж.очиститьОтображениеВыбора, 200); }); мж.фишкиСравнили.подписать(function(){ setTimeout(мж.очиститьВыбор, 200); }); мж.обновилиОставшиесяФишки.подписать(мж.проверитьЗавершение); // Начать после загрузки ресурсов. муром.ресурсы.получить(мж.ресурсы, мж.начать);