Игра Маджонг | Mahjong game
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

670 lines
22KB

  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. const R = RR();
  279. // Создать текстуры.
  280. var обозначения = [];
  281. var текстуры = [];
  282. for (var номер in R)
  283. {
  284. var обозначение = R[номер][0];
  285. обозначения.push(обозначение);
  286. var имя = "tile.tex/" + обозначение;
  287. var содержимое = R[номер][1];
  288. var текстура = муром.ресурсы.создатьРесурс(имя, содержимое);
  289. текстуры.push(текстура);
  290. }
  291. // Создать материалы.
  292. var нейтраль = [];
  293. var выбор = [];
  294. var вер = муром.ресурсы.ресурс("ver").содержимое;
  295. var фра = муром.ресурсы.ресурс("fra").содержимое;
  296. // Нейтраль.
  297. for (var номер = 0; номер < 42; ++номер)
  298. {
  299. var имя = "tile.mat/" + обозначения[номер];
  300. var изо = текстуры[номер];
  301. var мат = муром.материалы.создатьМатериал(имя);
  302. мат.задатьТекстуру("image", изо);
  303. мат.задатьШейдеры(вер, фра);
  304. нейтраль.push(мат);
  305. }
  306. // Выбор.
  307. for (var номер = 42; номер < 83; ++номер)
  308. {
  309. var имя = "tile.mat/" + обозначения[номер];
  310. var изо = текстуры[номер];
  311. var мат = муром.материалы.создатьМатериал(имя);
  312. мат.задатьТекстуру("image", изо);
  313. мат.задатьШейдеры(вер, фра);
  314. выбор.push(мат);
  315. }
  316. // Применить тему.
  317. for (var номер in мж.фишки)
  318. {
  319. var ф = мж.фишки[номер];
  320. ф.нейтраль = нейтраль[ф.группа];
  321. ф.выбор = выбор[ф.группа];
  322. ф.показатьНейтраль();
  323. }
  324. };
  325. мж.маскаВыбора = 0x2;
  326. мж.сделатьФишкиВыбираемыми = function()
  327. {
  328. for (var номер in мж.фишки)
  329. {
  330. var ф = мж.фишки[номер];
  331. ф.узел.задатьМаску(мж.маскаВыбора);
  332. }
  333. }
  334. мж.индексПозиции = function(п)
  335. {
  336. return п[0] * 1000000 + п[1] * 1000 + п[2];
  337. };
  338. мж.индексПозиций = {};
  339. мж.проиндексироватьПозиции = function()
  340. {
  341. for (var номер in мж.фишки)
  342. {
  343. var ф = мж.фишки[номер];
  344. var и = мж.индексПозиции(ф.позиция);
  345. мж.индексПозиций[и] = true;
  346. }
  347. };
  348. мж.естьСоседи = function(позиция, смещениеПоля, смещениеСтолбца)
  349. {
  350. for (var смещениеРяда = -1; смещениеРяда <= 1; ++смещениеРяда)
  351. {
  352. var сосед = [
  353. Number(позиция[0]) + смещениеПоля,
  354. Number(позиция[1]) + смещениеРяда,
  355. Number(позиция[2]) + смещениеСтолбца,
  356. ];
  357. var и = мж.индексПозиции(сосед);
  358. if (и in мж.индексПозиций)
  359. {
  360. return true;
  361. }
  362. }
  363. // Соседей нет.
  364. return false;
  365. };
  366. мж.можноВыбратьФишку = function(ф)
  367. {
  368. // Проверяем наличие фишек с левой и правой сторон одновременно.
  369. var слева = this.естьСоседи(ф.позиция, 0, -2);
  370. var справа = this.естьСоседи(ф.позиция, 0, 2);
  371. if (слева && справа)
  372. {
  373. return false;
  374. }
  375. // Проверяем наличие фишек непосредственно сверху.
  376. for (var смещение = -1; смещение <= 1; ++смещение)
  377. {
  378. if (this.естьСоседи(ф.позиция, 1, смещение))
  379. {
  380. return false;
  381. }
  382. }
  383. // Фишка не заблокирована.
  384. return true;
  385. };
  386. мж.номерВыбраннойФишки = null;
  387. мж.выбраннаяФишка = null;
  388. мж.выбранаФишка = new Уведомитель();
  389. мж.выбратьФишку = function()
  390. {
  391. // Определить щелчок.
  392. if (муром.мышь.нажатыеКнопки.length == 1)
  393. {
  394. var узел =
  395. муром.камера.узелВПозиции(
  396. муром.мышь.позиция,
  397. мж.маскаВыбора
  398. );
  399. if (узел)
  400. {
  401. var номер = Number(узел.имя);
  402. var ф = мж.фишки[номер];
  403. if (мж.можноВыбратьФишку(ф))
  404. {
  405. мж.номерВыбраннойФишки = номер;
  406. мж.выбраннаяФишка = ф;
  407. мж.выбранаФишка.уведомить();
  408. }
  409. }
  410. }
  411. };
  412. мж.отладитьВыборФишки = function()
  413. {
  414. мж.выбранаФишка.подписать(function(){
  415. console.log(
  416. "Выбор. Номер: '" +
  417. мж.номерВыбраннойФишки +
  418. "' Группа: '" +
  419. мж.выбраннаяФишка.группа +
  420. "'"
  421. );
  422. });
  423. };
  424. мж.показатьВыбраннуюФишку = function()
  425. {
  426. мж.выбраннаяФишка.показатьВыбор();
  427. };
  428. мж.выбранныеФишки = [];
  429. мж.фишкиРазличаются = new Уведомитель();
  430. мж.фишкиСовпадают = new Уведомитель();
  431. мж.фишкиСравнили = new Уведомитель();
  432. мж.сравнитьВыбранныеФишки = function()
  433. {
  434. // Собираем.
  435. мж.выбранныеФишки.push(мж.выбраннаяФишка);
  436. // Удостоверяемся в наличии пары фишек.
  437. if (мж.выбранныеФишки.length != 2)
  438. {
  439. return;
  440. }
  441. var ф1 = мж.выбранныеФишки[0];
  442. var ф2 = мж.выбранныеФишки[1];
  443. // Убираем дубликат при двойном выборе одной фишки.
  444. if (ф1.имя == ф2.имя)
  445. {
  446. мж.выбранныеФишки.shift();
  447. return;
  448. }
  449. // Сравниваем.
  450. if (ф1.группа == ф2.группа)
  451. {
  452. мж.фишкиСовпадают.уведомить();
  453. }
  454. else
  455. {
  456. мж.фишкиРазличаются.уведомить();
  457. }
  458. мж.фишкиСравнили.уведомить();
  459. };
  460. мж.отладитьСравнениеФишек = function()
  461. {
  462. мж.фишкиСовпадают.подписать(function(){
  463. console.log("Фишки совпадают");
  464. });
  465. мж.фишкиРазличаются.подписать(function(){
  466. console.log("Фишки различаются");
  467. });
  468. };
  469. мж.скрытьСовпадающиеФишки = function()
  470. {
  471. мж.выбранныеФишки[0].скрыть();
  472. мж.выбранныеФишки[1].скрыть();
  473. };
  474. мж.убратьСовпадающиеФишки = function()
  475. {
  476. var ф1 = мж.выбранныеФишки[0];
  477. var ф2 = мж.выбранныеФишки[1];
  478. var и1 = мж.индексПозиции(ф1.позиция);
  479. var и2 = мж.индексПозиции(ф2.позиция);
  480. delete мж.индексПозиций[и1];
  481. delete мж.индексПозиций[и2];
  482. };
  483. мж.очиститьОтображениеВыбора = function()
  484. {
  485. мж.выбранныеФишки[0].показатьНейтраль();
  486. мж.выбранныеФишки[1].показатьНейтраль();
  487. };
  488. мж.очиститьВыбор = function()
  489. {
  490. мж.номерВыбраннойФишки = 0;
  491. мж.выбраннаяФишка = null;
  492. мж.выбранныеФишки = [];
  493. };
  494. мж.оставшиесяФишки = [];
  495. мж.задатьОставшиесяФишки = function()
  496. {
  497. мж.оставшиесяФишки = мж.фишки.slice();
  498. };
  499. мж.обновилиОставшиесяФишки = new Уведомитель();
  500. мж.обновитьОставшиесяФишки = function()
  501. {
  502. var и1 = мж.оставшиесяФишки.indexOf(мж.выбранныеФишки[0]);
  503. мж.оставшиесяФишки.splice(и1, 1);
  504. var и2 = мж.оставшиесяФишки.indexOf(мж.выбранныеФишки[1]);
  505. мж.оставшиесяФишки.splice(и2, 1);
  506. мж.обновилиОставшиесяФишки.уведомить();
  507. };
  508. мж.доступныеДляВыбораФишки = function()
  509. {
  510. var фишки = [];
  511. for (var номер in мж.оставшиесяФишки)
  512. {
  513. var ф = мж.оставшиесяФишки[номер];
  514. if (мж.можноВыбратьФишку(ф))
  515. {
  516. фишки.push(ф);
  517. }
  518. }
  519. return фишки;
  520. };
  521. мж.естьХод = function()
  522. {
  523. var фишки = мж.доступныеДляВыбораФишки();
  524. for (var н1 in фишки)
  525. {
  526. for (var н2 in фишки)
  527. {
  528. var ф1 = фишки[н1];
  529. var ф2 = фишки[н2];
  530. if (
  531. (ф1.имя != ф2.имя) &&
  532. (ф1.группа == ф2.группа)
  533. ) {
  534. return true;
  535. }
  536. }
  537. }
  538. return false;
  539. }
  540. мж.победа = new Уведомитель();
  541. мж.поражение = new Уведомитель();
  542. мж.проверитьЗавершение = function()
  543. {
  544. var кончилисьФишки = (мж.оставшиесяФишки.length == 0);
  545. if (кончилисьФишки)
  546. {
  547. мж.победа.уведомить();
  548. return;
  549. }
  550. if (!мж.естьХод())
  551. {
  552. мж.поражение.уведомить();
  553. }
  554. };
  555. мж.отладитьЗавершение = function()
  556. {
  557. мж.победа.подписать(function(){
  558. console.log("Игра завершена. ПОБЕДА!");
  559. });
  560. мж.поражение.подписать(function(){
  561. console.log("Игра завершена. ПОРАЖЕНИЕ :(");
  562. });
  563. };
  564. мж.задатьОтображениеЗавершения = function()
  565. {
  566. мж.победа.подписать(function(){
  567. муром.камера.цветОчистки = [0.2, 0.5, 0.2];
  568. });
  569. мж.поражение.подписать(function(){
  570. муром.камера.цветОчистки = [0.5, 0.2, 0.2];
  571. });
  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. function(){
  599. setTimeout(мж.скрытьСовпадающиеФишки, 200);
  600. },
  601. ]);
  602. мж.фишкиРазличаются.подписать(function(){
  603. setTimeout(мж.очиститьОтображениеВыбора, 200);
  604. });
  605. мж.фишкиСравнили.подписать(function(){
  606. setTimeout(мж.очиститьВыбор, 200);
  607. });
  608. мж.обновилиОставшиесяФишки.подписать(мж.проверитьЗавершение);
  609. // Начать после загрузки ресурсов.
  610. муром.ресурсы.получить(мж.ресурсы, мж.начать);