Игра Маджонг | Mahjong game
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

671 lines
23KB

  1. // Фишка.
  2. function Фишка()
  3. {
  4. this.позиция = null;
  5. this.узел = null;
  6. this.группа = null;
  7. this.нейтраль = null;
  8. this.выбор = null;
  9. }
  10. Фишка.prototype.показатьВыбор = function()
  11. {
  12. this.узел.задатьМатериал(this.выбор);
  13. }
  14. Фишка.prototype.показатьНейтраль = function()
  15. {
  16. this.узел.задатьМатериал(this.нейтраль);
  17. }
  18. Фишка.prototype.показать = function()
  19. {
  20. this.узел.задатьМаску(0x0);
  21. }
  22. Фишка.prototype.скрыть = function()
  23. {
  24. // Специальная маска OpenSceneGraph для скрытия узла от камеры.
  25. this.узел.задатьМаску(0xFFFFFFFF);
  26. }
  27. Object.defineProperty(Фишка.prototype, 'имя', {
  28. get: function()
  29. {
  30. return this.узел.имя;
  31. }
  32. });
  33. // Раскладка.
  34. function Раскладка()
  35. {
  36. this.версия = "";
  37. this.ширина = 0;
  38. this.высота = 0;
  39. this.глубина = 0;
  40. this.позиции = [];
  41. // Для внутреннего пользования.
  42. this.поля = [];
  43. };
  44. Раскладка.prototype.разобратьСлужебнуюИнформацию = function(содержимое)
  45. {
  46. const ключи = {
  47. "версия": "kmahjongg-layout-v",
  48. "комментарий": "#",
  49. "ширина": "w",
  50. "высота": "h",
  51. "глубина": "d"
  52. };
  53. // ВНИМАНИЕ Версия 1.0 предполагает заданные заранее ширину и высоту.
  54. this.версия = "";
  55. this.глубина = 0;
  56. this.ширина = 32;
  57. this.высота = 16;
  58. this.поля = [];
  59. var поле = [];
  60. var строки = содержимое.split("\n");
  61. for (var номер in строки)
  62. {
  63. var строка = строки[номер].trim();
  64. // Пропуск.
  65. if (строка.startsWith(ключи.комментарий))
  66. {
  67. continue;
  68. }
  69. // Служебная информация.
  70. if (строка.startsWith(ключи.версия))
  71. {
  72. this.версия = строка.split(ключи.версия)[1];
  73. }
  74. else if (строка.startsWith(ключи.ширина))
  75. {
  76. this.ширина = строка.split(ключи.ширина)[1];
  77. }
  78. else if (строка.startsWith(ключи.высота))
  79. {
  80. this.высота = строка.split(ключи.высота)[1];
  81. }
  82. else if (строка.startsWith(ключи.глубина))
  83. {
  84. this.глубина = строка.split(ключи.глубина)[1];
  85. }
  86. // Поле.
  87. else
  88. {
  89. поле.push(строка);
  90. if (поле.length >= this.высота)
  91. {
  92. this.поля.push(поле.slice());
  93. поле = [];
  94. }
  95. }
  96. }
  97. if (this.глубина == 0)
  98. {
  99. this.глубина = this.поля.length;
  100. }
  101. }
  102. Раскладка.prototype.разобратьПозиции = function()
  103. {
  104. this.позиции = [];
  105. for (var номер in this.поля)
  106. {
  107. var поле = this.поля[номер];
  108. for (var строка = 0; строка < this.высота - 1; ++строка)
  109. {
  110. for (var столбец = 0; столбец < this.ширина - 1; ++столбец)
  111. {
  112. if (
  113. поле[строка][столбец] == "1" &&
  114. поле[строка][столбец + 1] == "2" &&
  115. поле[строка + 1][столбец] == "4" &&
  116. поле[строка + 1][столбец + 1] == "3"
  117. ) {
  118. this.позиции.push([номер, строка, столбец]);
  119. }
  120. }
  121. }
  122. }
  123. }
  124. Раскладка.prototype.разобрать = function(содержимое)
  125. {
  126. this.разобратьСлужебнуюИнформацию(содержимое);
  127. this.разобратьПозиции();
  128. }
  129. Раскладка.prototype.отладка = function()
  130. {
  131. var о = "";
  132. о += "Отладочная информация о раскладке:\n";
  133. о += " версия: '" + this.версия + "'\n";
  134. о += " ширина: '" + this.ширина + "'\n";
  135. о += " высота: '" + this.высота + "'\n";
  136. о += " глубина: '" + this.глубина + "'\n";
  137. о += " позиции:\n";
  138. for (var номер in this.позиции)
  139. {
  140. var п = this.позиции[номер];
  141. о += " п(" + номер + "): '" + п[0] + ", " + п[1] + ", " + п[2] + "'\n";
  142. }
  143. return о;
  144. }
  145. // Игра.
  146. мж.ресурсы = [
  147. [Б + "модели/фишка/2019-09-08.osgt", "mod"],
  148. [Б + "текстуры/заглушка.png", "tex.stub"],
  149. [Б + "шейдеры/освещение-изображение.vert", "ver"],
  150. [Б + "шейдеры/освещение-изображение.frag", "fra"],
  151. [Б + "раскладки/X_shaped.layout", "lay"],
  152. ];
  153. мж.начали = new Уведомитель();
  154. мж.начать = function()
  155. {
  156. муром.камера.позиция = [0, -70, 0];
  157. муром.камера.вращение = [90, 0, 0];
  158. мж.сцена = муром.узлы.создатьСферу("sce", 0);
  159. мж.сцена.вращение = [60, 0, 0];
  160. var мир = муром.узлы.узел("mir");
  161. мир.добавитьДитя(мж.сцена);
  162. мж.начали.уведомить();
  163. };
  164. мж.задатьНейтральныйМатериал = function()
  165. {
  166. var мат = муром.материалы.создатьМатериал("N");
  167. var изо = муром.ресурсы.ресурс("tex.stub");
  168. мат.задатьТекстуру("image", изо);
  169. var вер = муром.ресурсы.ресурс("ver").содержимое;
  170. var фра = муром.ресурсы.ресурс("fra").содержимое;
  171. мат.задатьШейдеры(вер, фра);
  172. мж.сцена.задатьМатериал(мат);
  173. };
  174. мж.разобратьРаскладку = function()
  175. {
  176. var содержимое = муром.ресурсы.ресурс("lay").содержимое;
  177. мж.раскладка = new Раскладка();
  178. мж.раскладка.разобрать(содержимое);
  179. };
  180. мж.фишки = [];
  181. мж.создатьФишки = function()
  182. {
  183. for (var номер in мж.раскладка.позиции)
  184. {
  185. var ф = new Фишка();
  186. ф.позиция = мж.раскладка.позиции[номер];
  187. мж.фишки.push(ф);
  188. }
  189. };
  190. мж.размерФишки = {
  191. "ширина": 2.0,
  192. "высота": 3.0,
  193. "глубина": 1.0,
  194. };
  195. мж.создатьУзлыФишек = function()
  196. {
  197. const шагФишки = 2.0;
  198. const коэффициенты = {
  199. "x": мж.размерФишки.ширина / шагФишки,
  200. "y": -мж.размерФишки.высота / шагФишки,
  201. "z": мж.размерФишки.глубина,
  202. };
  203. var мод = муром.ресурсы.ресурс("mod");
  204. for (var номер in мж.фишки)
  205. {
  206. var имя = номер.toString();
  207. var узел = муром.узлы.создатьУзел(имя, мод);
  208. мж.сцена.добавитьДитя(узел);
  209. var ф = мж.фишки[номер];
  210. var п = ф.позиция;
  211. узел.позиция = [
  212. п[2] * коэффициенты.x,
  213. п[1] * коэффициенты.y,
  214. п[0] * коэффициенты.z,
  215. ];
  216. ф.узел = узел;
  217. }
  218. };
  219. мж.центрироватьСцену = function()
  220. {
  221. var границы = {
  222. "лево": 1000,
  223. "право": -1000,
  224. "верх": -1000,
  225. "низ": 1000,
  226. };
  227. for (var номер in мж.фишки)
  228. {
  229. const ф = мж.фишки[номер];
  230. const x = ф.узел.позиция[0];
  231. const y = ф.узел.позиция[1];
  232. if (x < границы.лево)
  233. {
  234. границы.лево = x;
  235. }
  236. if (x > границы.право)
  237. {
  238. границы.право = x;
  239. }
  240. if (y < границы.низ)
  241. {
  242. границы.низ = y;
  243. }
  244. if (y > границы.верх)
  245. {
  246. границы.верх = y;
  247. }
  248. }
  249. const ширина = границы.право - границы.лево + мж.размерФишки.ширина;
  250. const высота = границы.верх - границы.низ + мж.размерФишки.высота;
  251. мж.сцена.позиция = [-ширина / 2.0, 0, высота / 2.0];
  252. };
  253. // http://www.rubl.com/rules/mahjong-solitaire-rules.html
  254. мж.задатьФишкамГруппыПоследовательно = function()
  255. {
  256. var группы = [];
  257. // Генерируем группы.
  258. const группВсего = 42;
  259. const группПо4Дубля = 34;
  260. for (var г = 0; г < группВсего; ++г)
  261. {
  262. const четыреДубля = (г < группПо4Дубля);
  263. const колвоДублей = четыреДубля ? 4 : 1;
  264. for (var д = 0; д < колвоДублей; ++д)
  265. {
  266. группы.push(г);
  267. }
  268. }
  269. // Задаём.
  270. for (var номер in мж.фишки)
  271. {
  272. var ф = мж.фишки[номер];
  273. ф.группа = Number(группы[номер]);
  274. }
  275. };
  276. мж.применитьТемуФишек = function()
  277. {
  278. eval(dl["тема.заглушка"]);
  279. const R = RR();
  280. // Создать текстуры.
  281. var обозначения = [];
  282. var текстуры = [];
  283. for (var номер in R)
  284. {
  285. var обозначение = R[номер][0];
  286. обозначения.push(обозначение);
  287. var имя = "tile.tex/" + обозначение;
  288. var содержимое = R[номер][1];
  289. var текстура = муром.ресурсы.создатьРесурс(имя, содержимое);
  290. текстуры.push(текстура);
  291. }
  292. // Создать материалы.
  293. var нейтраль = [];
  294. var выбор = [];
  295. var вер = муром.ресурсы.ресурс("ver").содержимое;
  296. var фра = муром.ресурсы.ресурс("fra").содержимое;
  297. // Нейтраль.
  298. for (var номер = 0; номер < 42; ++номер)
  299. {
  300. var имя = "tile.mat/" + обозначения[номер];
  301. var изо = текстуры[номер];
  302. var мат = муром.материалы.создатьМатериал(имя);
  303. мат.задатьТекстуру("image", изо);
  304. мат.задатьШейдеры(вер, фра);
  305. нейтраль.push(мат);
  306. }
  307. // Выбор.
  308. for (var номер = 42; номер < 83; ++номер)
  309. {
  310. var имя = "tile.mat/" + обозначения[номер];
  311. var изо = текстуры[номер];
  312. var мат = муром.материалы.создатьМатериал(имя);
  313. мат.задатьТекстуру("image", изо);
  314. мат.задатьШейдеры(вер, фра);
  315. выбор.push(мат);
  316. }
  317. // Применить тему.
  318. for (var номер in мж.фишки)
  319. {
  320. var ф = мж.фишки[номер];
  321. ф.нейтраль = нейтраль[ф.группа];
  322. ф.выбор = выбор[ф.группа];
  323. ф.показатьНейтраль();
  324. }
  325. };
  326. мж.маскаВыбора = 0x2;
  327. мж.сделатьФишкиВыбираемыми = function()
  328. {
  329. for (var номер in мж.фишки)
  330. {
  331. var ф = мж.фишки[номер];
  332. ф.узел.задатьМаску(мж.маскаВыбора);
  333. }
  334. }
  335. мж.индексПозиции = function(п)
  336. {
  337. return п[0] * 1000000 + п[1] * 1000 + п[2];
  338. };
  339. мж.индексПозиций = {};
  340. мж.проиндексироватьПозиции = function()
  341. {
  342. for (var номер in мж.фишки)
  343. {
  344. var ф = мж.фишки[номер];
  345. var и = мж.индексПозиции(ф.позиция);
  346. мж.индексПозиций[и] = true;
  347. }
  348. };
  349. мж.естьСоседи = function(позиция, смещениеПоля, смещениеСтолбца)
  350. {
  351. for (var смещениеРяда = -1; смещениеРяда <= 1; ++смещениеРяда)
  352. {
  353. var сосед = [
  354. Number(позиция[0]) + смещениеПоля,
  355. Number(позиция[1]) + смещениеРяда,
  356. Number(позиция[2]) + смещениеСтолбца,
  357. ];
  358. var и = мж.индексПозиции(сосед);
  359. if (и in мж.индексПозиций)
  360. {
  361. return true;
  362. }
  363. }
  364. // Соседей нет.
  365. return false;
  366. };
  367. мж.можноВыбратьФишку = function(ф)
  368. {
  369. // Проверяем наличие фишек с левой и правой сторон одновременно.
  370. var слева = this.естьСоседи(ф.позиция, 0, -2);
  371. var справа = this.естьСоседи(ф.позиция, 0, 2);
  372. if (слева && справа)
  373. {
  374. return false;
  375. }
  376. // Проверяем наличие фишек непосредственно сверху.
  377. for (var смещение = -1; смещение <= 1; ++смещение)
  378. {
  379. if (this.естьСоседи(ф.позиция, 1, смещение))
  380. {
  381. return false;
  382. }
  383. }
  384. // Фишка не заблокирована.
  385. return true;
  386. };
  387. мж.номерВыбраннойФишки = null;
  388. мж.выбраннаяФишка = null;
  389. мж.выбранаФишка = new Уведомитель();
  390. мж.выбратьФишку = function()
  391. {
  392. // Определить щелчок.
  393. if (муром.мышь.нажатыеКнопки.length == 1)
  394. {
  395. var узел =
  396. муром.камера.узелВПозиции(
  397. муром.мышь.позиция,
  398. мж.маскаВыбора
  399. );
  400. if (узел)
  401. {
  402. var номер = Number(узел.имя);
  403. var ф = мж.фишки[номер];
  404. if (мж.можноВыбратьФишку(ф))
  405. {
  406. мж.номерВыбраннойФишки = номер;
  407. мж.выбраннаяФишка = ф;
  408. мж.выбранаФишка.уведомить();
  409. }
  410. }
  411. }
  412. };
  413. мж.отладитьВыборФишки = function()
  414. {
  415. мж.выбранаФишка.подписать(function(){
  416. console.log(
  417. "Выбор. Номер: '" +
  418. мж.номерВыбраннойФишки +
  419. "' Группа: '" +
  420. мж.выбраннаяФишка.группа +
  421. "'"
  422. );
  423. });
  424. };
  425. мж.показатьВыбраннуюФишку = function()
  426. {
  427. мж.выбраннаяФишка.показатьВыбор();
  428. };
  429. мж.выбранныеФишки = [];
  430. мж.фишкиРазличаются = new Уведомитель();
  431. мж.фишкиСовпадают = new Уведомитель();
  432. мж.фишкиСравнили = new Уведомитель();
  433. мж.сравнитьВыбранныеФишки = function()
  434. {
  435. // Собираем.
  436. мж.выбранныеФишки.push(мж.выбраннаяФишка);
  437. // Удостоверяемся в наличии пары фишек.
  438. if (мж.выбранныеФишки.length != 2)
  439. {
  440. return;
  441. }
  442. var ф1 = мж.выбранныеФишки[0];
  443. var ф2 = мж.выбранныеФишки[1];
  444. // Убираем дубликат при двойном выборе одной фишки.
  445. if (ф1.имя == ф2.имя)
  446. {
  447. мж.выбранныеФишки.shift();
  448. return;
  449. }
  450. // Сравниваем.
  451. if (ф1.группа == ф2.группа)
  452. {
  453. мж.фишкиСовпадают.уведомить();
  454. }
  455. else
  456. {
  457. мж.фишкиРазличаются.уведомить();
  458. }
  459. мж.фишкиСравнили.уведомить();
  460. };
  461. мж.отладитьСравнениеФишек = function()
  462. {
  463. мж.фишкиСовпадают.подписать(function(){
  464. console.log("Фишки совпадают");
  465. });
  466. мж.фишкиРазличаются.подписать(function(){
  467. console.log("Фишки различаются");
  468. });
  469. };
  470. мж.скрытьСовпадающиеФишки = function()
  471. {
  472. мж.выбранныеФишки[0].скрыть();
  473. мж.выбранныеФишки[1].скрыть();
  474. };
  475. мж.убратьСовпадающиеФишки = function()
  476. {
  477. var ф1 = мж.выбранныеФишки[0];
  478. var ф2 = мж.выбранныеФишки[1];
  479. var и1 = мж.индексПозиции(ф1.позиция);
  480. var и2 = мж.индексПозиции(ф2.позиция);
  481. delete мж.индексПозиций[и1];
  482. delete мж.индексПозиций[и2];
  483. };
  484. мж.очиститьОтображениеВыбора = function()
  485. {
  486. мж.выбранныеФишки[0].показатьНейтраль();
  487. мж.выбранныеФишки[1].показатьНейтраль();
  488. };
  489. мж.очиститьВыбор = function()
  490. {
  491. мж.номерВыбраннойФишки = 0;
  492. мж.выбраннаяФишка = null;
  493. мж.выбранныеФишки = [];
  494. };
  495. мж.оставшиесяФишки = [];
  496. мж.задатьОставшиесяФишки = function()
  497. {
  498. мж.оставшиесяФишки = мж.фишки.slice();
  499. };
  500. мж.обновилиОставшиесяФишки = new Уведомитель();
  501. мж.обновитьОставшиесяФишки = function()
  502. {
  503. var и1 = мж.оставшиесяФишки.indexOf(мж.выбранныеФишки[0]);
  504. мж.оставшиесяФишки.splice(и1, 1);
  505. var и2 = мж.оставшиесяФишки.indexOf(мж.выбранныеФишки[1]);
  506. мж.оставшиесяФишки.splice(и2, 1);
  507. мж.обновилиОставшиесяФишки.уведомить();
  508. };
  509. мж.доступныеДляВыбораФишки = function()
  510. {
  511. var фишки = [];
  512. for (var номер in мж.оставшиесяФишки)
  513. {
  514. var ф = мж.оставшиесяФишки[номер];
  515. if (мж.можноВыбратьФишку(ф))
  516. {
  517. фишки.push(ф);
  518. }
  519. }
  520. return фишки;
  521. };
  522. мж.естьХод = function()
  523. {
  524. var фишки = мж.доступныеДляВыбораФишки();
  525. for (var н1 in фишки)
  526. {
  527. for (var н2 in фишки)
  528. {
  529. var ф1 = фишки[н1];
  530. var ф2 = фишки[н2];
  531. if (
  532. (ф1.имя != ф2.имя) &&
  533. (ф1.группа == ф2.группа)
  534. ) {
  535. return true;
  536. }
  537. }
  538. }
  539. return false;
  540. }
  541. мж.победа = new Уведомитель();
  542. мж.поражение = new Уведомитель();
  543. мж.проверитьЗавершение = function()
  544. {
  545. var кончилисьФишки = (мж.оставшиесяФишки.length == 0);
  546. if (кончилисьФишки)
  547. {
  548. мж.победа.уведомить();
  549. return;
  550. }
  551. if (!мж.естьХод())
  552. {
  553. мж.поражение.уведомить();
  554. }
  555. };
  556. мж.отладитьЗавершение = function()
  557. {
  558. мж.победа.подписать(function(){
  559. console.log("Игра завершена. ПОБЕДА!");
  560. });
  561. мж.поражение.подписать(function(){
  562. console.log("Игра завершена. ПОРАЖЕНИЕ :(");
  563. });
  564. };
  565. мж.задатьОтображениеЗавершения = function()
  566. {
  567. мж.победа.подписать(function(){
  568. муром.камера.цветОчистки = [0.2, 0.5, 0.2];
  569. });
  570. мж.поражение.подписать(function(){
  571. муром.камера.цветОчистки = [0.5, 0.2, 0.2];
  572. });
  573. };
  574. // Игра.
  575. мж.начали.подписатьМного([
  576. мж.задатьНейтральныйМатериал,
  577. мж.разобратьРаскладку,
  578. мж.создатьФишки,
  579. мж.создатьУзлыФишек,
  580. мж.центрироватьСцену,
  581. мж.задатьФишкамГруппыПоследовательно,
  582. мж.применитьТемуФишек,
  583. мж.сделатьФишкиВыбираемыми,
  584. мж.отладитьВыборФишки,
  585. мж.проиндексироватьПозиции,
  586. мж.отладитьСравнениеФишек,
  587. мж.задатьОставшиесяФишки,
  588. мж.отладитьЗавершение,
  589. мж.задатьОтображениеЗавершения,
  590. ]);
  591. муром.мышь.нажатыеКнопкиИзменились.подписать(мж.выбратьФишку);
  592. мж.выбранаФишка.подписатьМного([
  593. мж.показатьВыбраннуюФишку,
  594. мж.сравнитьВыбранныеФишки,
  595. ]);
  596. мж.фишкиСовпадают.подписатьМного([
  597. мж.убратьСовпадающиеФишки,
  598. мж.обновитьОставшиесяФишки,
  599. function(){
  600. setTimeout(мж.скрытьСовпадающиеФишки, 200);
  601. },
  602. ]);
  603. мж.фишкиРазличаются.подписать(function(){
  604. setTimeout(мж.очиститьОтображениеВыбора, 200);
  605. });
  606. мж.фишкиСравнили.подписать(function(){
  607. setTimeout(мж.очиститьВыбор, 200);
  608. });
  609. мж.обновилиОставшиесяФишки.подписать(мж.проверитьЗавершение);
  610. // Начать после загрузки ресурсов.
  611. муром.ресурсы.получить(мж.ресурсы, мж.начать);