-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy path16_game.html
More file actions
796 lines (577 loc) · 138 KB
/
16_game.html
File metadata and controls
796 lines (577 loc) · 138 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
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
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>پروژه: یک بازی پرشی :: Eloquent JavaScript</title>
<link rel=stylesheet href="js/node_modules/codemirror/lib/codemirror.css">
<script src="js/acorn_codemirror.js"></script>
<link rel=stylesheet href="css/ejs.css">
<script src="js/sandbox.js"></script>
<script src="js/ejs.js"></script><script>var chapNum = 16;var sandboxLoadFiles = ["code/chapter/16_game.js","code/levels.js"];</script><script>var clicky_site_ids = clicky_site_ids || []; clicky_site_ids.push(101171577);</script>
<script async src="//static.getclicky.com/js"></script>
</head>
<article>
<nav><a href="15_event.html" title="previous chapter">◀</a> <a href="index.html" title="cover">◆</a> <a href="17_canvas.html" title="next chapter">▶</a></nav>
<h1><span class=chap_num>فصل 16</span>پروژه: یک بازی پرشی</h1>
<blockquote>
<p><a class="p_ident" id="p_2jmj7l5rSw" href="#p_2jmj7l5rSw" tabindex="-1" role="presentation"></a>تمام واقعیت یک بازی است.</p>
<footer>Iain Banks, <cite>The Player of Games</cite></footer>
</blockquote><figure class="chapter framed"><img src="img/chapter_picture_16.jpg" alt="Picture of a game character jumping over lava"></figure>
<p>بیشتر شیفتگی اولیهی من نسبت به کامپیوترها، مثل خیلی از بچههای دیگر، از بازیهای کامپیوتری شروع شد. من جذب دنیاهای شبیهسازی شدهی کوچکی شدم که میتوانستم آنها را اداره کنم که در آنها داستانهایی گشوده میشد – گمان میکنم بیشتر به خاطر گسترش تخیلاتم به درون بازیها بود تا امکانات و قابلیتهای خود بازی ها.</p>
<p>شخصا برای هیچ کس حرفهی برنامهنویسی بازیهای کامپیوتری را آرزو نمیکنم. بسیار شبیه به صنعت موسیقی، تفاوت فاحش بین تعداد زیاد افرادی که دوست دارند در آن کار کنند و تقاضای واقعی برای آنها، باعث ایجاد محیط نسبتا نامناسبی شده است. اما نوشتن بازیها برای تفریح، کاری دلچسب است.</p>
<p>در این فصل به سراغ پیادهسازی یک بازی پرشی (سکوبازی) کوچک میرویم. سکوبازیها (یا بازیهای حرکت و پرش)، بازیهایی هستند که بازیکن باید یک شخصیت را در جهان بازی حرکت دهد که معمولا این جهان دو بعدی است و از کنار نمایش داده میشود و شخصیت از روی (و درون) چیزها میتواند بپرد.</p>
<h2><a class="h_ident" id="h_kgCxP1w88K" href="#h_kgCxP1w88K" tabindex="-1" role="presentation"></a>بازی</h2>
<p><a class="p_ident" id="p_wJHyJzEw4V" href="#p_wJHyJzEw4V" tabindex="-1" role="presentation"></a>بازی ما به طور کلی بر پایهی بازی <a href="http://www.lessmilk.com/games/10">Dark Blue</a> که توسط توماس پالف ساخته شده خواهد بود. من این بازی را انتخاب کردم به دلیل اینکه هم سرگرمکننده و هم ساده است؛ و نیازی نیست برای نوشتن آن کدنویسی خیلی زیادی انجام شود. بازی به شکل زیر خواهد بود.</p><figure><img src="img/darkblue.png" alt="The game Dark Blue"></figure>
<p>مستطیل تیره رنگ نمایانگر شخصیت بازی است که وظیفهاش جمع آوری مستطیل های زرد (سکهها) بدون برخورد با چیزهای قرمز رنگ (گدازهها) است. یک مرحله بازی زمانی کامل میشود که تمامی سکهها جمع آوری شده باشند.</p>
<p>بازیکن میتواند به وسیلهی کلیدهای چپ و راست صفحهکلید جابجا شود و با فشردن کلید بالا، بپرد. پریدن توانایی خاص این کاراکتر است. میتواند چندین برابر قد خودش بپرد و در هوا جهتش را عوض کند. بازی به طور کامل واقعگرایانه نیست اما به بازیکن این حس را القا میکند که کاملا شخصیت بازی تحت کنترلش حرکت میکند.</p>
<p><a class="p_ident" id="p_cbjuaNmwBW" href="#p_cbjuaNmwBW" tabindex="-1" role="presentation"></a>این بازی از یک پسزمینهی ثابت تشکیل شده است که مثل یک grid طرح بندی شده به همراه عناصر متحرکی که روی پسزمینه قرار گرفته اند. هر فیلد از grid یا خالی، یا رنگشده یا یک گدازه است. عناصر متحرک شامل بازیکنان، سکهها و بعضی از گدازهها میباشند. موقعیت این عناصر محدود به grid نیست. – ممکن است مختصاتشان اعشاری باشد که باعث حرکت نرمتر آنها خواهد شد.</p>
<h2><a class="h_ident" id="h_zw8jHMjSeF" href="#h_zw8jHMjSeF" tabindex="-1" role="presentation"></a>تکنولوژی مورد استفاده</h2>
<p><a class="p_ident" id="p_Yfb0Ie8yZw" href="#p_Yfb0Ie8yZw" tabindex="-1" role="presentation"></a>ما از DOM مرورگر برای نمایش بازی استفاده میکنیم و ورودی کاربر را با مدیریت رخدادهای کلیدها، خواهیم خواند.</p>
<p>کدهای مربوط به صفحهی نمایش و صفحهکلید فقط بخشی کوچکی از کاری که برای ساخت این بازی لازم است را شامل میشوند. به دلیل این که همه چیز شبیه به مستطیلهای رنگی است، مشکل طراحی نداریم: عناصر DOM را ایجاد کرده و با استفاده از سبکدهی به آنها رنگ پسزمینه ،اندازه و موقعیت میدهیم.</p>
<p><a class="p_ident" id="p_88VDrV/p1p" href="#p_88VDrV/p1p" tabindex="-1" role="presentation"></a>میتوانیم پسزمینه را به شکل یک جدول نمایش دهیم چرا که یک grid ثابت از چهارگوشها است. عناصری که آزادانه حرکت میکنند را میتوان با موقعیتدهی مطلق روی طرح قرار داد.</p>
<p>در بازیها و دیگر برنامههایی که در آنها باید تصاویر به حرکت درآیند و به ورودی کاربر بدون تاخیر پاسخ دهند، کارایی خیلی مهم است. اگرچه DOM اساسا برای انجام کارهای گرافیکی سطح بالا طراحی نشده است، اما در عمل از چیزی که انتظارش را دارید بهتر کار می کند. در <a href="14_dom.html#animation">فصل 14</a> چند نمونه متحرکسازی مشاهده نمودید. در یک کامپیوتر مدرن، یک بازی ساده مثل این بازی به خوبی اجرا میشود، حتی اگر به بهینهسازی آن زیاد فکر نکنیم.</p>
<p>در <a href="17_canvas.html">فصل بعدی</a> به سراغ تکنولوژی دیگری در مرورگر خواهیم رفت، برچسب <bdo><code><canvas></code></bdo> که روش ریشهدارتری برای کشیدن تصاویر فراهم می سازد؛ کار با اشکال و پیکسلها به جای استفاده از عناصر DOM.</p>
<h2><a class="h_ident" id="h_/+PLvgEsJy" href="#h_/+PLvgEsJy" tabindex="-1" role="presentation"></a>مراحل بازی</h2>
<p>ما به روشی خوانا و قابل ویرایش برای انسان نیاز داریم تا به وسیلهی آن مراحل بازی را مشخص کنیم. چون میتوان همه چیز را روی grid شروع کرد، میتوانیم از رشتههای بلند که در آن هر کاراکتر نمایندهی یک عنصر است استفاده کنیم –هم برای یک بخش پسزمینه و هم یک عنصر در حال حرکت.</p>
<p>طرح مورد نظر برای یک مرحلهی کوچک ممکن است شبیه به زیر باشد:</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_txvY7tsNJp" href="#c_txvY7tsNJp" tabindex="-1" role="presentation"></a><span class="cm-keyword">let</span> <span class="cm-def">simpleLevelPlan</span> <span class="cm-operator">=</span> <span class="cm-string-2">`</span>
<span class="cm-string-2">......................</span>
<span class="cm-string-2">..#................#..</span>
<span class="cm-string-2">..#..............=.#..</span>
<span class="cm-string-2">..#.........o.o....#..</span>
<span class="cm-string-2">..#.@......#####...#..</span>
<span class="cm-string-2">..#####............#..</span>
<span class="cm-string-2">......#++++++++++++#..</span>
<span class="cm-string-2">......##############..</span>
<span class="cm-string-2">......................`</span>;</pre>
<p><a class="p_ident" id="p_CKkUzeBQOW" href="#p_CKkUzeBQOW" tabindex="-1" role="presentation"></a>نقطهها نمایندهی فضاهای خالی، کاراکترهای هش (<code>#</code>) معرف دیوارها و علامتهای مثبت نمایندهی گدازهها میباشند. نقطهی شروع بازیکن با علامت <code>@</code> مشخص شده است. هر کاراکتر O نمایندهی یک سکه است و علامت مساوی (<code>=</code>) در بالا یک بلاک از گدازه است که به صورت افقی جلوعقب میرود.</p>
<p>دو نوع دیگر از گدازههای متحرک را پشتیبانی خواهیم کرد: یک کاراکتر پایپ (<code>|</code>) گلولههای متحرک عمودی ایجاد میکند و <code>v</code> نشاندهنده گدازههایی است که چکیده میشوند – گدازههای متحرک عمودی که بالا پایین نمیروند بلکه فقط به سمت پایین حرکت میکنند و وقتی به زمین رسیدند به نقطهی اولشان بر می گردند.</p>
<p>کل بازی شامل چندین مرحله میشود که بازیکن باید به اتمام برساند. وقتی همهی سکهها جمعآوری شد یک مرحله به اتمام میرسد. اگر بازیکن با گدازه برخورد کند ، مرحلهی کنونی به ابتدا برخواهد گشت و بازیکن میتواند دوباره تلاش کند.</p>
<h2 id="level"><a class="h_ident" id="h_ZEM+UZSmxy" href="#h_ZEM+UZSmxy" tabindex="-1" role="presentation"></a>خواندن یک مرحله</h2>
<p>کلاس پیش رو یک شیء مرحله را ذخیره میکند. آرگومان آن باید رشتهای باشد که مرحله را تعریف میکند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_ObYKMNTKci" href="#c_ObYKMNTKci" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">Level</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">plan</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">rows</span> <span class="cm-operator">=</span> <span class="cm-variable-2">plan</span>.<span class="cm-property">trim</span>().<span class="cm-property">split</span>(<span class="cm-string">"\n"</span>).<span class="cm-property">map</span>(<span class="cm-def">l</span> <span class="cm-operator">=></span> [<span class="cm-meta">...</span><span class="cm-variable-2">l</span>]);
<span class="cm-keyword">this</span>.<span class="cm-property">height</span> <span class="cm-operator">=</span> <span class="cm-variable-2">rows</span>.<span class="cm-property">length</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">width</span> <span class="cm-operator">=</span> <span class="cm-variable-2">rows</span>[<span class="cm-number">0</span>].<span class="cm-property">length</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">startActors</span> <span class="cm-operator">=</span> [];
<span class="cm-keyword">this</span>.<span class="cm-property">rows</span> <span class="cm-operator">=</span> <span class="cm-variable-2">rows</span>.<span class="cm-property">map</span>((<span class="cm-def">row</span>, <span class="cm-def">y</span>) <span class="cm-operator">=></span> {
<span class="cm-keyword">return</span> <span class="cm-variable-2">row</span>.<span class="cm-property">map</span>((<span class="cm-def">ch</span>, <span class="cm-def">x</span>) <span class="cm-operator">=></span> {
<span class="cm-keyword">let</span> <span class="cm-def">type</span> <span class="cm-operator">=</span> <span class="cm-variable">levelChars</span>[<span class="cm-variable-2">ch</span>];
<span class="cm-keyword">if</span> (<span class="cm-keyword">typeof</span> <span class="cm-variable-2">type</span> <span class="cm-operator">==</span> <span class="cm-string">"string"</span>) <span class="cm-keyword">return</span> <span class="cm-variable-2">type</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">startActors</span>.<span class="cm-property">push</span>(
<span class="cm-variable-2">type</span>.<span class="cm-property">create</span>(<span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-variable-2">x</span>, <span class="cm-variable-2">y</span>), <span class="cm-variable-2">ch</span>));
<span class="cm-keyword">return</span> <span class="cm-string">"empty"</span>;
});
});
}
}</pre>
<p>متد <code>trim</code> در اینجا برای حذف فضاهای خالی شروع و پایان رشتهی طرح استفاده میشود. این به طرح مثال ما این امکان را میدهد که در یک خط جدید شروع شود تا همهی خطوط مستقیما زیر یکدیگر قرار بگیرند. رشتهی باقیمانده براساس کاراکترهای خط جدید تقسیم میشود و هر خط درون یک آرایه پخش میشود و آرایهای از کاراکترها تولید میشود.</p>
<p>بنابراین <code>rows</code>، آرایهای از آرایههای کاراکترها را نگهداری میکند، همان ردیفهای طرح. میتوانیم طول و عرض هر مرحله را از این ها بدست بیاوریم. اما هنوز لازم است که عناصر متحرک را از grid پسزمینه جدا کنیم. عناصر متحرک را بازیگران مینامیم. آن را در آرایهای از اشیاء ذخیره میکنیم. پسزمینه، آرایهای از آرایههای رشتهها خواهد بود که نوع فیلدهایی مثل <code>"empty"</code>، <code>"wall"</code>، یا <code>"lava"</code> را نگهداری میکند.</p>
<p><a class="p_ident" id="p_DXF16Sx+Gq" href="#p_DXF16Sx+Gq" tabindex="-1" role="presentation"></a>برای ایجاد این آرایهها به سراغ تک تک ردیف ها و بعد محتوای آنها میرویم. به خاطر داشته باشید که متد <code>map</code> اندیس آرایه را به عنوان آرگومان دوم به تابع ارسال میکند که به ما مختصات x و y کاراکتر دادهشده را میدهد. موقعیت ها در این بازی به صورت جفتهایی از مختصات با مبدا بالا و چپ <bdo>0,0</bdo> ذخیره میشوند و هر مربع پسزمینه دارای 1 واحد طول و عرض میباشد.</p>
<p><a class="p_ident" id="p_N3Re16DwBf" href="#p_N3Re16DwBf" tabindex="-1" role="presentation"></a>برای تفسیر کاراکترهای موجود در طرح، تابع سازندهی <code>Level</code> از شیء <code>levelChars</code> استفاده می کند که عناصر پسزمینه را به رشتهها و بازیگران را به کلاسها نگاشت میکند. زمانی که <code>type</code> یک کلاس بازیگر است، متد استاتیک <code>create</code> آن برای ایجاد یک شیء استفاده می شود که به <code>startActors</code> افزوده میشود و تابع map مقدار <code>"empty"</code> را برای این مربع پسزمینه برمی گرداند.</p>
<p>موقعیت بازیگر به عنوان یک شیء <code>Vec</code> ذخیره میشود که یک بردار دوبعدی است، شیءای با خاصیتهای <code>x</code> و <code>y</code>، همانطور که در قسمت تمرینها <a href="06_object.html#exercise_vector">فصل 6</a> مشاهده شد.</p>
<p>با اجرای بازی، بازیگران در مکانهای متفاوتی قرار می گیرند یا حتی به طور کامل ناپدید میشوند (همانطور که سکهها در صورت جمعآوری ناپدید میشوند). ما از یک کلاس <code>State</code> برای رصد وضعیت بازی در حال اجرا استفاده میکنیم.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_8mXPZZkFTr" href="#c_8mXPZZkFTr" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">State</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">level</span>, <span class="cm-def">actors</span>, <span class="cm-def">status</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">level</span> <span class="cm-operator">=</span> <span class="cm-variable-2">level</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">actors</span> <span class="cm-operator">=</span> <span class="cm-variable-2">actors</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">status</span> <span class="cm-operator">=</span> <span class="cm-variable-2">status</span>;
}
<span class="cm-keyword">static</span> <span class="cm-property">start</span>(<span class="cm-def">level</span>) {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">State</span>(<span class="cm-variable-2">level</span>, <span class="cm-variable-2">level</span>.<span class="cm-property">startActors</span>, <span class="cm-string">"playing"</span>);
}
<span class="cm-keyword">get</span> <span class="cm-property">player</span>() {
<span class="cm-keyword">return</span> <span class="cm-keyword">this</span>.<span class="cm-property">actors</span>.<span class="cm-property">find</span>(<span class="cm-def">a</span> <span class="cm-operator">=></span> <span class="cm-variable-2">a</span>.<span class="cm-property">type</span> <span class="cm-operator">==</span> <span class="cm-string">"player"</span>);
}
}</pre>
<p>زمانی که بازی به اتمام میرسد، خاصیت <code>status</code> به <code>"lost"</code> یا <code>"won"</code> تغییر میکند.</p>
<p>این دوباره یک ساختار دادهی مانا محسوب میشود – به روز رسانی وضعیت بازی باعث ایجاد وضعیت جدیدی میشود و وضعیت قبلی را دستنخورده باقی می گذارد.</p>
<h2><a class="h_ident" id="h_/6w3TfyWe1" href="#h_/6w3TfyWe1" tabindex="-1" role="presentation"></a>بازیگران</h2>
<p>اشیاء بازیگر نمایانگر موقعیت و وضعیت یک عنصر متحرک در بازی ما میباشند. تمامی اشیاء بازیگر از رابط یکسانی پیروی میکنند. خاصیت <code>pos</code> آنها، مختصات گوشهی بالا-چپ عنصر را نگهداری کرده و خاصیت <code>size</code> آنها اندازهشان را نگهداری میکند.</p>
<p>آنها نیز دارای یک متد <code>update</code> میباشند که برای محاسبهی وضعیت و موقعیت جدیدشان بعد از یک گام زمانی داده شده است. این متد، کاری که یک بازیگر انجام میدهد را شبیهسازی میکند- حرکت در پاسخ به کلیدهای جهت دار برای بازیکن، حرکت جلو و عقب برای گدازهها – و یک شیء بازیگر جدید و بهروز بر می گرداند.</p>
<p>یک خاصیت <code>type</code> حاوی رشتهای است که نوع بازیگر را مشخص میکند – <bdo>,<code>“player”</code></bdo> <bdo><code>“coin”</code></bdo> یا <bdo><code>“lava”</code></bdo>. در هنگام کشیدن طرح بازی این خاصیت مفید خواهد بود. – شکل مستطیلی که برای یک بازیگر کشیده میشود بر اساس نوعش میباشد.</p>
<p>کلاسهای بازیگر دارای یک متد استاتیک به نام <code>create</code> هستند که به وسیلهی سازندهی <code>Level</code> برای ایجاد یک بازیگر از یک کاراکتر موجود در طرح مرحله استفاده میشود. به آن، مختصات کاراکتر و خود کاراکتر داده میشود، که ضروری است زیرا کلاس <code>Lava</code> کاراکترهای متعددی را رسیدگی میکند.</p>
<p id="vector">برای مقادیر دوبعدی از کلاس <code>Vec</code> استفاده میکنیم مثل موقعیت و اندازهی بازیگران.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_Hb9lakixOM" href="#c_Hb9lakixOM" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">Vec</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">x</span>, <span class="cm-def">y</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">x</span> <span class="cm-operator">=</span> <span class="cm-variable-2">x</span>; <span class="cm-keyword">this</span>.<span class="cm-property">y</span> <span class="cm-operator">=</span> <span class="cm-variable-2">y</span>;
}
<span class="cm-property">plus</span>(<span class="cm-def">other</span>) {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-keyword">this</span>.<span class="cm-property">x</span> <span class="cm-operator">+</span> <span class="cm-variable-2">other</span>.<span class="cm-property">x</span>, <span class="cm-keyword">this</span>.<span class="cm-property">y</span> <span class="cm-operator">+</span> <span class="cm-variable-2">other</span>.<span class="cm-property">y</span>);
}
<span class="cm-property">times</span>(<span class="cm-def">factor</span>) {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-keyword">this</span>.<span class="cm-property">x</span> <span class="cm-operator">*</span> <span class="cm-variable-2">factor</span>, <span class="cm-keyword">this</span>.<span class="cm-property">y</span> <span class="cm-operator">*</span> <span class="cm-variable-2">factor</span>);
}
}</pre>
<p><a class="p_ident" id="p_AnwRRrG4+S" href="#p_AnwRRrG4+S" tabindex="-1" role="presentation"></a>متد <code>times</code> با توجه به عدد دریافتی، اندازهی یک بردار (vector) را تغییر میدهد. زمانی که لازم است تا یک بردار سرعت را در یک وقفهی زمان ضرب کنیم تا فاصلهی پیموده شده را در طول آن زمان به دست بیاوریم، به کار خواهد آمد.</p>
<p>انواع مختلف بازیگران دارای کلاسهای خودشان میباشند، به دلیل اینکه رفتارهایشان خیلی متفاوت است. اجازه بدهید این کلاس ها را تعریف کنیم. بعدا به متدهای <code>update</code> شان خواهیم پرداخت.</p>
<p><a class="p_ident" id="p_UDxK95A8q+" href="#p_UDxK95A8q+" tabindex="-1" role="presentation"></a>کلاس <code>Player</code> دارای خاصیتی به نام <code>speed</code> است که سرعت فعلی اش را ذخیره میکند تا جاذبه و تکانه (momentum) را شبیهسازی کند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_+Zda+gD/W/" href="#c_+Zda+gD/W/" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">Player</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">pos</span>, <span class="cm-def">speed</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">pos</span> <span class="cm-operator">=</span> <span class="cm-variable-2">pos</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">speed</span> <span class="cm-operator">=</span> <span class="cm-variable-2">speed</span>;
}
<span class="cm-keyword">get</span> <span class="cm-property">type</span>() { <span class="cm-keyword">return</span> <span class="cm-string">"player"</span>; }
<span class="cm-keyword">static</span> <span class="cm-property">create</span>(<span class="cm-def">pos</span>) {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Player</span>(<span class="cm-variable-2">pos</span>.<span class="cm-property">plus</span>(<span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-number">0</span>, <span class="cm-operator">-</span><span class="cm-number">0.5</span>)),
<span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-number">0</span>, <span class="cm-number">0</span>));
}
}
<span class="cm-variable">Player</span>.<span class="cm-property">prototype</span>.<span class="cm-property">size</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-number">0.8</span>, <span class="cm-number">1.5</span>);</pre>
<p>چون یک بازیکن یک و نیم برابر یک مربع ارتفاع دارد، موقعیت اولیهی آن برابر با نصف مربع بالای موقعیتی که کاراکتر <code>@</code> ظاهر میشود تنظیم میشود. با این کار، قسمت پایین آن با قسمت پایین مربعی که در آن ظاهر میشود تراز خواهد شد.</p>
<p><a class="p_ident" id="p_68YDtd2GEB" href="#p_68YDtd2GEB" tabindex="-1" role="presentation"></a>خاصیت <code>size</code> برای همهی نمونههای گرفته شده از <code>Player</code> یکسان است پس میتوان آن را به جای ذخیره در نمونهها در prototype ذخیره کرد. میتوانستیم از یک getter مثل <code>type</code> استفاده کنیم اما در این صورت یک شیء <code>Vec</code> جدید هر بار که خاصیت خوانده میشد ایجاد و برگردانده میشد که کاری بیهوده است. (رشتهها با توجه به غیرقابل تغییر بودن، نیازی ندارند با هر بار ارزیابی از نو ایجاد شوند).</p>
<p><a class="p_ident" id="p_NPCV7d07RH" href="#p_NPCV7d07RH" tabindex="-1" role="presentation"></a>زمانی که یک بازیگر <code>Lava</code> را می سازیم، لازم است که شیء با توجه با کاراکتری که بر پایهی آن است مقداردهی متفاوتی شود. گدازهی پویا با سرعت فعلیاش حرکت میکند تا زمانی که به یک مانع برخورد کند. در این نقطه، اگر دارای خاصیت <code>reset</code> باشد، به موقعیت اولیهاش برمیگردد (dripping). اگر نداشت، سرعتش معکوس شده و درجهت مخالف به حرکت ادامه میدهد (bouncing).</p>
<p>متد <code>create</code> به کاراکترهایی که سازندهی <code>Level</code> ارسال میکند نگاه کرده و بازیگران گدازهی مناسب را ایجاد میکند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_OquWedN4L5" href="#c_OquWedN4L5" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">Lava</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">pos</span>, <span class="cm-def">speed</span>, <span class="cm-def">reset</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">pos</span> <span class="cm-operator">=</span> <span class="cm-variable-2">pos</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">speed</span> <span class="cm-operator">=</span> <span class="cm-variable-2">speed</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">reset</span> <span class="cm-operator">=</span> <span class="cm-variable-2">reset</span>;
}
<span class="cm-keyword">get</span> <span class="cm-property">type</span>() { <span class="cm-keyword">return</span> <span class="cm-string">"lava"</span>; }
<span class="cm-keyword">static</span> <span class="cm-property">create</span>(<span class="cm-def">pos</span>, <span class="cm-def">ch</span>) {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">ch</span> <span class="cm-operator">==</span> <span class="cm-string">"="</span>) {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Lava</span>(<span class="cm-variable-2">pos</span>, <span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-number">2</span>, <span class="cm-number">0</span>));
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">ch</span> <span class="cm-operator">==</span> <span class="cm-string">"|"</span>) {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Lava</span>(<span class="cm-variable-2">pos</span>, <span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-number">0</span>, <span class="cm-number">2</span>));
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">ch</span> <span class="cm-operator">==</span> <span class="cm-string">"v"</span>) {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Lava</span>(<span class="cm-variable-2">pos</span>, <span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-number">0</span>, <span class="cm-number">3</span>), <span class="cm-variable-2">pos</span>);
}
}
}
<span class="cm-variable">Lava</span>.<span class="cm-property">prototype</span>.<span class="cm-property">size</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-number">1</span>, <span class="cm-number">1</span>);</pre>
<p>بازیگران <code>Coin</code> نسبتا ساده میباشند. بیشترشان فقط در جای خود ثابت میمانند. اما برای اینکه کمی به بازی پویایی اضافه کنیم، آنها را در جا حرکت میدهیم. برای انجام این کار، یک شیء سکه موقعیت پایهای را به همراه یک خاصیت <code>wobble</code> که حرکت درجا را رصد می کند ذخیره میکند. این دو با هم موقعیت واقعی سکه را مشخص میکنند (که در خاصیت <code>pos</code> حفظ میشوند).</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_f2L1vFl5w5" href="#c_f2L1vFl5w5" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">Coin</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">pos</span>, <span class="cm-def">basePos</span>, <span class="cm-def">wobble</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">pos</span> <span class="cm-operator">=</span> <span class="cm-variable-2">pos</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">basePos</span> <span class="cm-operator">=</span> <span class="cm-variable-2">basePos</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">wobble</span> <span class="cm-operator">=</span> <span class="cm-variable-2">wobble</span>;
}
<span class="cm-keyword">get</span> <span class="cm-property">type</span>() { <span class="cm-keyword">return</span> <span class="cm-string">"coin"</span>; }
<span class="cm-keyword">static</span> <span class="cm-property">create</span>(<span class="cm-def">pos</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">basePos</span> <span class="cm-operator">=</span> <span class="cm-variable-2">pos</span>.<span class="cm-property">plus</span>(<span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-number">0.2</span>, <span class="cm-number">0.1</span>));
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Coin</span>(<span class="cm-variable-2">basePos</span>, <span class="cm-variable-2">basePos</span>,
<span class="cm-variable">Math</span>.<span class="cm-property">random</span>() <span class="cm-operator">*</span> <span class="cm-variable">Math</span>.<span class="cm-property">PI</span> <span class="cm-operator">*</span> <span class="cm-number">2</span>);
}
}
<span class="cm-variable">Coin</span>.<span class="cm-property">prototype</span>.<span class="cm-property">size</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-number">0.6</span>, <span class="cm-number">0.6</span>);</pre>
<p>در <a href="14_dom.html#sin_cos">فصل 14</a>، دیدیم که متد <bdo><code>Math.sin</code></bdo> مختصات عرضی نقطهای روی دایره را برای ما فراهم میسازد. مقدار این مختصات با حرکت در محیط دایره به صورت موجی، در یک بازه بالا و پایین میرود که موجب میشود تابع سینوس گزینهی خوبی برای مدلسازی حرکت موجی برای ما باشد.</p>
<p><a class="p_ident" id="p_2kuSN7rMzf" href="#p_2kuSN7rMzf" tabindex="-1" role="presentation"></a>برای جلوگیری از حالتی که همهی سکهها همزمان بالا و پایین بروند، فاز شروع هر سکه به صورت تصادفی تعیین میشود. فاز موج <bdo><code>Math.sin</code></bdo> همان عرض موجی است که تولید میکند و برابر با 2π میباشد. مقدار بازگشتی از <bdo><code>Math.random</code></bdo> را در آن عدد ضرب کرده تا موقعیت شروع تصادفی ای به سکه روی موج بدهیم.</p>
<p>اکنون میتوانیم شیء <code>levelChars</code> را تعریف کنیم که کاراکترهای طرح را روی انواع grid پسزمینه یا کلاسهای بازیگر نگاشت کند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_VxaicldIYi" href="#c_VxaicldIYi" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> <span class="cm-def">levelChars</span> <span class="cm-operator">=</span> {
<span class="cm-string cm-property">"."</span>: <span class="cm-string">"empty"</span>, <span class="cm-string cm-property">"#"</span>: <span class="cm-string">"wall"</span>, <span class="cm-string cm-property">"+"</span>: <span class="cm-string">"lava"</span>,
<span class="cm-string cm-property">"@"</span>: <span class="cm-variable">Player</span>, <span class="cm-string cm-property">"o"</span>: <span class="cm-variable">Coin</span>,
<span class="cm-string cm-property">"="</span>: <span class="cm-variable">Lava</span>, <span class="cm-string cm-property">"|"</span>: <span class="cm-variable">Lava</span>, <span class="cm-string cm-property">"v"</span>: <span class="cm-variable">Lava</span>
};</pre>
<p>این به ما تمامی بخشهای لازم برای ایجاد نمونهی <code>Level</code> را میدهد.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_CDJvcZL+0x" href="#c_CDJvcZL+0x" tabindex="-1" role="presentation"></a><span class="cm-keyword">let</span> <span class="cm-def">simpleLevel</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">Level</span>(<span class="cm-variable">simpleLevelPlan</span>);
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-string-2">`${</span><span class="cm-variable">simpleLevel</span>.<span class="cm-property">width</span><span class="cm-string-2">}</span> <span class="cm-string-2">by ${</span><span class="cm-variable">simpleLevel</span>.<span class="cm-property">height</span><span class="cm-string-2">}</span><span class="cm-string-2">`</span>);
<span class="cm-comment">// → 22 by 9</span></pre>
<p>کار باقی مانده این است که این مرحلهها را روی صفحهی نمایش نشان دهیم و زمان و حرکت را درون آن مدلسازی کنیم.</p>
<h2><a class="h_ident" id="h_WXKm2heBeA" href="#h_WXKm2heBeA" tabindex="-1" role="presentation"></a>کپسولهسازی به عنوان یک بار</h2>
<p>بیشتر کدهای این فصل بدون در نظر گرفته کپسوله سازی نوشته شده اند و این کار دو دلیل دارد. اول اینکه کپسولهسازی کار بیشتری از ما میگیرد. باعث بزرگتر شدن برنامه میشود و نیاز به طرح مفاهیم و رابط های بیشتری دارد. به دلیل این که نمیتوان در اینجا کد زیادی به نمایش گذاشت و برای خواننده کسل کننده خواهد شد، من تلاش کردم که که برنامه را کوچک نگه دارم.</p>
<p>دوما، عناصر متنوع درون این بازی با هم ارتباط تنگاتنگی دارند و اگر رفتار یکی از آنها تغییر کند، بعید است که دیگر عناصر بتوانند به همان صورت قبلی بمانند. رابطهای بین این عناصر ممکن است به اینجا ختم شود که فرضهای زیادی دربارهی نحوهی عملکرد بازی در نظر بگیرند. این باعث میشود که اثرگذاری این رابطها بسیار کاهش یابد- هربار که بخشی از سیستم را تغییر می دهید، همچنان باید نگران نحوهی اثر آن روی دیگر قسمتها باشید چراکه رابطهای آنها شرایط جدید را پوشش نداده اند.</p>
<p><a class="p_ident" id="p_bOHMEF1lEF" href="#p_bOHMEF1lEF" tabindex="-1" role="presentation"></a>بعضی نقاط قابل برش در سیستم (<em>cutting points</em>)، خودشان مناسب قرارگرفتن به عنوان قسمتهای مجزا توسط رابطهای دقیق میباشند، اما دیگر قسمتها این طور نیستند. تلاش در جهت کپسولهسازی چیزی که کرانهی مناسبی محسوب نمیشود، روش مطمئنی برای تلف کردن زیاد انرژی است. زمانی که مرتکب این اشتباه میشوید معمولا متوجه میشوید که رابط شما به شکل نامناسبی بزرگ و دارای جزئیات میشود و اغلب با تکامل برنامه، لازم است تغییر کند.</p>
<p>یک چیز هست که قصد داریم تا کپسولهسازی کنیم و آن طراحی زیرسیستم است. دلیل این کار این است که ما بازی را به روش متفاوتی در فصل آینده قرار است نمایش دهیم. با قرار دادن عمل طراحی پشت یک رابط، میتوانیم همین برنامهی بازی را آنجا بارگیری کرده و ماژول نمایش جدیدی را به خدمت بگیریم.</p>
<h2 id="domdisplay"><a class="h_ident" id="h_oIC/o2KMUA" href="#h_oIC/o2KMUA" tabindex="-1" role="presentation"></a>رسم</h2>
<p><a class="p_ident" id="p_x/xsZs25go" href="#p_x/xsZs25go" tabindex="-1" role="presentation"></a>عمل کپسوله کردن کد رسم اشکال، با تعریف یک شیء <em>display</em> انجام میشود که وضعیت و مرحلهی داده شده را نمایش میدهد. نوع displayای که در این فصل تعریف میکنیم <code>DOMDisplay</code> خوانده میشود به دلیل این که از عناصر DOM برای نمایش مرحله استفاده می شود.</p>
<p><a class="p_ident" id="p_L4RBep5zzq" href="#p_L4RBep5zzq" tabindex="-1" role="presentation"></a>ما از یک برگهی سبکدهی (css) برای تنظیم رنگهای واقعی و دیگر خاصیتهای ثابت عناصر سازندهی بازی استفاده میکنیم. همچنین میتوان مستقیما خاصیت <code>style</code> عناصر را بعد از ایجادشان مقداردهی کرد اما این کار برنامهها را بینظم و شلوغ میکند.</p>
<p>تابع کمکی زیر روشی مختصر برای ایجاد یک عنصر و اختصاص چند خصیصه و گرهی فرزند فراهم میکند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_IslrNCPEgI" href="#c_IslrNCPEgI" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">elt</span>(<span class="cm-def">name</span>, <span class="cm-def">attrs</span>, <span class="cm-meta">...</span><span class="cm-def">children</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">dom</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">createElement</span>(<span class="cm-variable-2">name</span>);
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">attr</span> <span class="cm-keyword">of</span> <span class="cm-variable">Object</span>.<span class="cm-property">keys</span>(<span class="cm-variable-2">attrs</span>)) {
<span class="cm-variable-2">dom</span>.<span class="cm-property">setAttribute</span>(<span class="cm-variable-2">attr</span>, <span class="cm-variable-2">attrs</span>[<span class="cm-variable-2">attr</span>]);
}
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">child</span> <span class="cm-keyword">of</span> <span class="cm-variable-2">children</span>) {
<span class="cm-variable-2">dom</span>.<span class="cm-property">appendChild</span>(<span class="cm-variable-2">child</span>);
}
<span class="cm-keyword">return</span> <span class="cm-variable-2">dom</span>;
}</pre>
<p><a class="p_ident" id="p_WbGJBwr3Ud" href="#p_WbGJBwr3Ud" tabindex="-1" role="presentation"></a>یک display به این صورت ایجاد میشود که به آن عنصر والدی اختصاص داده میشود که باید خودش و یک شیء مرحله را به آن اضافه کند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_YPdTKEt761" href="#c_YPdTKEt761" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">DOMDisplay</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">parent</span>, <span class="cm-def">level</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"div"</span>, {<span class="cm-property">class</span>: <span class="cm-string">"game"</span>}, <span class="cm-variable">drawGrid</span>(<span class="cm-variable-2">level</span>));
<span class="cm-keyword">this</span>.<span class="cm-property">actorLayer</span> <span class="cm-operator">=</span> <span class="cm-atom">null</span>;
<span class="cm-variable-2">parent</span>.<span class="cm-property">appendChild</span>(<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>);
}
<span class="cm-property">clear</span>() { <span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">remove</span>(); }
}</pre>
<p>grid پسزمینهی مرحله، که همیشه ثابت است، فقط یک بار رسم میشود. بازیگران اما با هر بار تغییر صفحه نمایش توسط یک وضعیت جدید از نو رسم میشوند. خاصیت <code>actorLayer</code> برای رصد عنصری که بازیگران را نگهداری میکند استفاده میشود تا آنها بتوانند به آسانی تغییر و حذف شوند.</p>
<p><a class="p_ident" id="p_PmKGGVH0OG" href="#p_PmKGGVH0OG" tabindex="-1" role="presentation"></a>مختصات و اندازههای ما در واحدهای grid اندازهگیری میشوند، برای اندازه یا فاصله، 1 به معنای یک بلاک grid است. زمانی که اندازههای پیکسلی را تنظیم میکنیم، می بایست مقیاس این مختصات را افزایش دهیم – اگر برای هر مربع یک پیکسل در نظر بگیریم همهی عناصر بازی به شدت کوچک میشوند. ثابت <code>scale</code> تعداد پیکسل معادل یک واحد در صفحهی نمایش را تعیین میکند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_LrmszCVXMZ" href="#c_LrmszCVXMZ" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> <span class="cm-def">scale</span> <span class="cm-operator">=</span> <span class="cm-number">20</span>;
<span class="cm-keyword">function</span> <span class="cm-def">drawGrid</span>(<span class="cm-def">level</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable">elt</span>(<span class="cm-string">"table"</span>, {
<span class="cm-property">class</span>: <span class="cm-string">"background"</span>,
<span class="cm-property">style</span>: <span class="cm-string-2">`width: ${</span><span class="cm-variable-2">level</span>.<span class="cm-property">width</span> <span class="cm-operator">*</span> <span class="cm-variable">scale</span><span class="cm-string-2">}</span><span class="cm-string-2">px`</span>
}, <span class="cm-meta">...</span><span class="cm-variable-2">level</span>.<span class="cm-property">rows</span>.<span class="cm-property">map</span>(<span class="cm-def">row</span> <span class="cm-operator">=></span>
<span class="cm-variable">elt</span>(<span class="cm-string">"tr"</span>, {<span class="cm-property">style</span>: <span class="cm-string-2">`height: ${</span><span class="cm-variable">scale</span><span class="cm-string-2">}</span><span class="cm-string-2">px`</span>},
<span class="cm-meta">...</span><span class="cm-variable-2">row</span>.<span class="cm-property">map</span>(<span class="cm-def">type</span> <span class="cm-operator">=></span> <span class="cm-variable">elt</span>(<span class="cm-string">"td"</span>, {<span class="cm-property">class</span>: <span class="cm-variable-2">type</span>})))
));
}</pre>
<p><a class="p_ident" id="p_I5MwxFq9eA" href="#p_I5MwxFq9eA" tabindex="-1" role="presentation"></a>همانطور که قبلا ذکر شد، پسزمینه به عنوان یک عنصر <bdo><code><table></code></bdo> رسم میشود. این عنصر با ساختار خاصیت <code>rows</code> مربوط به مرحله به خوبی هماهنگی دارد – هر ردیف از grid به یک ردیف جدول (<bdo><code><tr></code></bdo>) تبدیل میشود. رشتههای موجود در grid به عنوان نامهای کلاس برای سلولهای جدول (<bdo><code><td></code></bdo>) استفاده میشوند. عملگر توزیع (سهنقطه) برای ارسال آرایهی گرههای فرزند به <code>elt</code> به عنوان آرگومانهای مجزا استفاده میشود.</p>
<p id="game_css"><a class="p_ident" id="p_GU4T2nIKHw" href="#p_GU4T2nIKHw" tabindex="-1" role="presentation"></a>کد CSS زیر موجب میشود که جدول شبیه پسزمینهای که دوست داریم بشود:</p>
<pre class="snippet cm-s-default" data-language="text/css" ><a class="c_ident" id="c_wOP5LzF6Sp" href="#c_wOP5LzF6Sp" tabindex="-1" role="presentation"></a><span class="cm-qualifier">.background</span> { <span class="cm-property">background</span>: <span class="cm-atom">rgb</span>(<span class="cm-number">52</span>, <span class="cm-number">166</span>, <span class="cm-number">251</span>);
<span class="cm-property">table-layout</span>: <span class="cm-atom">fixed</span>;
<span class="cm-property">border-spacing</span>: <span class="cm-number">0</span>; }
<span class="cm-qualifier">.background</span> <span class="cm-tag">td</span> { <span class="cm-property">padding</span>: <span class="cm-number">0</span>; }
<span class="cm-qualifier">.lava</span> { <span class="cm-property">background</span>: <span class="cm-atom">rgb</span>(<span class="cm-number">255</span>, <span class="cm-number">100</span>, <span class="cm-number">100</span>); }
<span class="cm-qualifier">.wall</span> { <span class="cm-property">background</span>: <span class="cm-keyword">white</span>; }</pre>
<p><a class="p_ident" id="p_3UNV2daimV" href="#p_3UNV2daimV" tabindex="-1" role="presentation"></a>بعضی از این خاصیتها (<bdo><code>table-layout</code></bdo>،<bdo><code>border-spacing</code></bdo> و <code>padding</code>) برای تغییر رفتارهای پیشفرض ناخواسته است. ما نمی خواهیم که قالب جدول وابسته به محتوای خانههایش باشد و دوست نداریم بین خانههای جدول فاصله باشد یا درونشان padding داشته باشند.</p>
<p><a class="p_ident" id="p_F+bpiTczTx" href="#p_F+bpiTczTx" tabindex="-1" role="presentation"></a>دستور <code>background</code> رنگ پسزمینه را تنظیم میکند. در CSS میتوان رنگ را هم با نامشان (white) و هم با فرمتهایی مثل <bdo><code>rgb(R, G, B)</code></bdo> مشخص نمود؛ که سه رنگ اصلی قرمز، سبز و آبی آن را تشکیل میدهند و با اعدادی بین 0 تا 255 مشخص میشوند. براین اساس، در <bdo><code>rgb(52, 166, 251)</code></bdo> قرمز برابر 52، سبز 166 و آبی 251 میباشد. چون قسمت آبی بیشترین عدد را دارد نتیجه رنگی متمایل به آبی خواهد بود. میتوانید آن را در دستور <bdo><code>.lava</code></bdo> مشاهده کنید، که در آنجا اولین عدد (قرمز) بزرگترین عدد است.</p>
<p>ما هر بازیگر را با ایجاد یک عنصر DOM برای آن رسم کردیم و موقعیت و اندازهی آن عنصر را بر اساس خاصیتهای بازیگر مورد نظر تنظیم کردیم. مقادیر باید در <code>scale</code> ضرب شوند تا از واحدهای بازی به پیکسل تبدیل شوند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_SJNWL3kOZh" href="#c_SJNWL3kOZh" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">drawActors</span>(<span class="cm-def">actors</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable">elt</span>(<span class="cm-string">"div"</span>, {}, <span class="cm-meta">...</span><span class="cm-variable-2">actors</span>.<span class="cm-property">map</span>(<span class="cm-def">actor</span> <span class="cm-operator">=></span> {
<span class="cm-keyword">let</span> <span class="cm-def">rect</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"div"</span>, {<span class="cm-property">class</span>: <span class="cm-string-2">`actor ${</span><span class="cm-variable-2">actor</span>.<span class="cm-property">type</span><span class="cm-string-2">}</span><span class="cm-string-2">`</span>});
<span class="cm-variable-2">rect</span>.<span class="cm-property">style</span>.<span class="cm-property">width</span> <span class="cm-operator">=</span> <span class="cm-string-2">`${</span><span class="cm-variable-2">actor</span>.<span class="cm-property">size</span>.<span class="cm-property">x</span> <span class="cm-operator">*</span> <span class="cm-variable">scale</span><span class="cm-string-2">}</span><span class="cm-string-2">px`</span>;
<span class="cm-variable-2">rect</span>.<span class="cm-property">style</span>.<span class="cm-property">height</span> <span class="cm-operator">=</span> <span class="cm-string-2">`${</span><span class="cm-variable-2">actor</span>.<span class="cm-property">size</span>.<span class="cm-property">y</span> <span class="cm-operator">*</span> <span class="cm-variable">scale</span><span class="cm-string-2">}</span><span class="cm-string-2">px`</span>;
<span class="cm-variable-2">rect</span>.<span class="cm-property">style</span>.<span class="cm-property">left</span> <span class="cm-operator">=</span> <span class="cm-string-2">`${</span><span class="cm-variable-2">actor</span>.<span class="cm-property">pos</span>.<span class="cm-property">x</span> <span class="cm-operator">*</span> <span class="cm-variable">scale</span><span class="cm-string-2">}</span><span class="cm-string-2">px`</span>;
<span class="cm-variable-2">rect</span>.<span class="cm-property">style</span>.<span class="cm-property">top</span> <span class="cm-operator">=</span> <span class="cm-string-2">`${</span><span class="cm-variable-2">actor</span>.<span class="cm-property">pos</span>.<span class="cm-property">y</span> <span class="cm-operator">*</span> <span class="cm-variable">scale</span><span class="cm-string-2">}</span><span class="cm-string-2">px`</span>;
<span class="cm-keyword">return</span> <span class="cm-variable-2">rect</span>;
}));
}</pre>
<p><a class="p_ident" id="p_F/4rlB4nhn" href="#p_F/4rlB4nhn" tabindex="-1" role="presentation"></a>برای اینکه به یک عنصر بیش از یک کلاس اختصاص بدهیم، نام کلاسها را با فضای خالی از هم جدا میکنیم. در کد CSSای که در ادامه نمایش داده میشود، کلاس <code>actor</code> به همهی عناصر بازیگر موقعیتی مطلق را تخصیص میدهد. نام نوع بازیگران به عنوان کلاسی اضافی استفاده میشود تا به هر کدام یک رنگ اختصاص یابد. نیازی نیست که کلاس <code>lava</code> را دوباره تعریف کنیم چون از همان کلاس <code>lava</code> که برای مربعهای grid تعریف کرده بودیم در قبل استفاده خواهیم کرد.</p>
<pre class="snippet cm-s-default" data-language="text/css" ><a class="c_ident" id="c_ksr13Gc65g" href="#c_ksr13Gc65g" tabindex="-1" role="presentation"></a><span class="cm-qualifier">.actor</span> { <span class="cm-property">position</span>: <span class="cm-atom">absolute</span>; }
<span class="cm-qualifier">.coin</span> { <span class="cm-property">background</span>: <span class="cm-atom">rgb</span>(<span class="cm-number">241</span>, <span class="cm-number">229</span>, <span class="cm-number">89</span>); }
<span class="cm-qualifier">.player</span> { <span class="cm-property">background</span>: <span class="cm-atom">rgb</span>(<span class="cm-number">64</span>, <span class="cm-number">64</span>, <span class="cm-number">64</span>); }</pre>
<p><a class="p_ident" id="p_AEaGKg4haX" href="#p_AEaGKg4haX" tabindex="-1" role="presentation"></a>متد <code>syncState</code> برای نمایش دادن یک وضعیت داده شده استفاده میشود. ابتدا تصاویر گرافیکی قدیمی بازیگران را حذف میکند، در صورت وجود، و سپس بازیگران را در موقعیت جدیدشان از نو ترسیم میکند. ممکن است وسوسهانگیز باشد که از عناصر DOM برای بازیگران دوباره استفاده کنیم، اما برای این کار، لازم است تا کلی حساب و کتاب اضافی برای انتساب بازیگران به عناصر DOM انجام دهیم و باز مطمئن شویم با ناپدید شدن هر بازیگر آن عناصر مرتبط را نیز حذف کنیم. به دلیل اینکه تعداد انگشتشماری بازیگر در این بازی وجود دارد، از نو ترسیم کردن همهی آنها کار هزینهبرداری محسوب نمیشود.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_/bAFVECbGl" href="#c_/bAFVECbGl" tabindex="-1" role="presentation"></a><span class="cm-variable">DOMDisplay</span>.<span class="cm-property">prototype</span>.<span class="cm-property">syncState</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">state</span>) {
<span class="cm-keyword">if</span> (<span class="cm-keyword">this</span>.<span class="cm-property">actorLayer</span>) <span class="cm-keyword">this</span>.<span class="cm-property">actorLayer</span>.<span class="cm-property">remove</span>();
<span class="cm-keyword">this</span>.<span class="cm-property">actorLayer</span> <span class="cm-operator">=</span> <span class="cm-variable">drawActors</span>(<span class="cm-variable-2">state</span>.<span class="cm-property">actors</span>);
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">appendChild</span>(<span class="cm-keyword">this</span>.<span class="cm-property">actorLayer</span>);
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">className</span> <span class="cm-operator">=</span> <span class="cm-string-2">`game ${</span><span class="cm-variable-2">state</span>.<span class="cm-property">status</span><span class="cm-string-2">}</span><span class="cm-string-2">`</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">scrollPlayerIntoView</span>(<span class="cm-variable-2">state</span>);
};</pre>
<p><a class="p_ident" id="p_iTOE9haLUy" href="#p_iTOE9haLUy" tabindex="-1" role="presentation"></a>با افزودن وضعیت فعلی مرحله به عنوان یک نام کلاس به wrapper، میتوانیم شخصیت بازی را در زمان برنده شدن یا باختن بازی سبکدهی متفاوتی بکنیم و این کار با افزودن یک دستور CSS که زمانی اعمال میشود که بازیکن عنصر والدش دارای کلاس داده شده باشد.</p>
<pre class="snippet cm-s-default" data-language="text/css" ><a class="c_ident" id="c_6QpUiIcdtL" href="#c_6QpUiIcdtL" tabindex="-1" role="presentation"></a><span class="cm-qualifier">.lost</span> <span class="cm-qualifier">.player</span> {
<span class="cm-property">background</span>: <span class="cm-atom">rgb</span>(<span class="cm-number">160</span>, <span class="cm-number">64</span>, <span class="cm-number">64</span>);
}
<span class="cm-qualifier">.won</span> <span class="cm-qualifier">.player</span> {
<span class="cm-property">box-shadow</span>: <span class="cm-number">-4px</span> <span class="cm-number">-7px</span> <span class="cm-number">8px</span> <span class="cm-keyword">white</span>, <span class="cm-number">4px</span> <span class="cm-number">-7px</span> <span class="cm-number">8px</span> <span class="cm-keyword">white</span>;
}</pre>
<p>بعد از برخورد با گدازه، رنگ بازیکن به قرمز تیره تغییر میکند که سوختن را نمایش دهد. زمانی که آخرین سکه هم جمع شد دو سایهی سفید تار- یکی به بالا-چپ و دیگری به بالا-راست اضافه میکنیم- تا جلوهی هالهی روشن را ایجاد کنیم.</p>
<p id="viewport"><a class="p_ident" id="p_ZgqzD/QamT" href="#p_ZgqzD/QamT" tabindex="-1" role="presentation"></a>نمیتوانیم فرض بگیریم که مرحلهی بازی همیشه در میدان دید (viewport) باشد – منظور عنصری است که در آن بازی را ترسیم میکنیم. به همین دلیل است که فراخوانی <code>scrollPlayerIntoView</code> لازم است – این تابع باعث میشود تا در صورتی که مرحلهی بازی از اندازهی میدان دید فراتر رفت، آن عنصر میدان دید اسکرول شود و شخصیت بازی نزدیک وسط تصویر آن قرار گیرد. دستورات CSS پیش رو به عنصر wrapper بازی بیشینهی اندازه را اختصاص داده و اطمینان حاصل میکند که هر چیزی که بیرون از محدودهی این عنصر قرار بگیرد قابل مشاهده نخواهد بود. همچنین به عنصر بیرونی یک موقعیت نسبی تخصیص دادیم که باعث می شود بازیگران درون آن نسبت به گوشهی چپ-بالای مرحله موقعیت دهی شوند.</p>
<pre class="snippet cm-s-default" data-language="text/css" ><a class="c_ident" id="c_cxq+gtsZuW" href="#c_cxq+gtsZuW" tabindex="-1" role="presentation"></a><span class="cm-qualifier">.game</span> {
<span class="cm-property">overflow</span>: <span class="cm-atom">hidden</span>;
<span class="cm-property">max-width</span>: <span class="cm-number">600px</span>;
<span class="cm-property">max-height</span>: <span class="cm-number">450px</span>;
<span class="cm-property">position</span>: <span class="cm-atom">relative</span>;
}</pre>
<p>در متد <code>scrollPlayerIntoView</code> ما موقعیت بازیکن را پیدا میکنیم و موقعیت اسکرول عنصر پوشانندهی آن را بهروز میکنیم. موقعیت اسکرول را با دستکاری خاصیتهای <code>scollLeft</code> و <code>scrollTop</code> وقتی که بازیکن خیلی به کنارهها نزیک میشود تغییر میدهیم.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_Of96qEfT96" href="#c_Of96qEfT96" tabindex="-1" role="presentation"></a><span class="cm-variable">DOMDisplay</span>.<span class="cm-property">prototype</span>.<span class="cm-property">scrollPlayerIntoView</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">state</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">width</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">clientWidth</span>;
<span class="cm-keyword">let</span> <span class="cm-def">height</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">clientHeight</span>;
<span class="cm-keyword">let</span> <span class="cm-def">margin</span> <span class="cm-operator">=</span> <span class="cm-variable-2">width</span> <span class="cm-operator">/</span> <span class="cm-number">3</span>;
<span class="cm-comment">// The viewport</span>
<span class="cm-keyword">let</span> <span class="cm-def">left</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">scrollLeft</span>, <span class="cm-def">right</span> <span class="cm-operator">=</span> <span class="cm-variable-2">left</span> <span class="cm-operator">+</span> <span class="cm-variable-2">width</span>;
<span class="cm-keyword">let</span> <span class="cm-def">top</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">scrollTop</span>, <span class="cm-def">bottom</span> <span class="cm-operator">=</span> <span class="cm-variable-2">top</span> <span class="cm-operator">+</span> <span class="cm-variable-2">height</span>;
<span class="cm-keyword">let</span> <span class="cm-def">player</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>.<span class="cm-property">player</span>;
<span class="cm-keyword">let</span> <span class="cm-def">center</span> <span class="cm-operator">=</span> <span class="cm-variable-2">player</span>.<span class="cm-property">pos</span>.<span class="cm-property">plus</span>(<span class="cm-variable-2">player</span>.<span class="cm-property">size</span>.<span class="cm-property">times</span>(<span class="cm-number">0.5</span>))
.<span class="cm-property">times</span>(<span class="cm-variable">scale</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">center</span>.<span class="cm-property">x</span> <span class="cm-operator"><</span> <span class="cm-variable-2">left</span> <span class="cm-operator">+</span> <span class="cm-variable-2">margin</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">scrollLeft</span> <span class="cm-operator">=</span> <span class="cm-variable-2">center</span>.<span class="cm-property">x</span> <span class="cm-operator">-</span> <span class="cm-variable-2">margin</span>;
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">center</span>.<span class="cm-property">x</span> <span class="cm-operator">></span> <span class="cm-variable-2">right</span> <span class="cm-operator">-</span> <span class="cm-variable-2">margin</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">scrollLeft</span> <span class="cm-operator">=</span> <span class="cm-variable-2">center</span>.<span class="cm-property">x</span> <span class="cm-operator">+</span> <span class="cm-variable-2">margin</span> <span class="cm-operator">-</span> <span class="cm-variable-2">width</span>;
}
<span class="cm-keyword">if</span> (<span class="cm-variable-2">center</span>.<span class="cm-property">y</span> <span class="cm-operator"><</span> <span class="cm-variable-2">top</span> <span class="cm-operator">+</span> <span class="cm-variable-2">margin</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">scrollTop</span> <span class="cm-operator">=</span> <span class="cm-variable-2">center</span>.<span class="cm-property">y</span> <span class="cm-operator">-</span> <span class="cm-variable-2">margin</span>;
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">center</span>.<span class="cm-property">y</span> <span class="cm-operator">></span> <span class="cm-variable-2">bottom</span> <span class="cm-operator">-</span> <span class="cm-variable-2">margin</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">scrollTop</span> <span class="cm-operator">=</span> <span class="cm-variable-2">center</span>.<span class="cm-property">y</span> <span class="cm-operator">+</span> <span class="cm-variable-2">margin</span> <span class="cm-operator">-</span> <span class="cm-variable-2">height</span>;
}
};</pre>
<p>روشی که در آن مرکز بازیکن را پیدا کردیم نشان میدهد چگونه متدهای موجود در نوع <code>Vec</code> امکان محاسبات روی اشیاء را به شکلی نسبتا خوانا فراهم میکنند. برای پیدا کردن مرکز بازیگر، موقعیت آن را (گوشهی بالا-چپ) به نیمی از اندازهاش اضافه میکنیم. در مختصات مرحله آن مرکز محسوب میشود اما ما نیاز به مختصات در واحد پیکسل داریم که بتوانیم بردار نتیجه را در مقیاس نمایشمان ضرب کنیم.</p>
<p><a class="p_ident" id="p_hiaAA8CMxS" href="#p_hiaAA8CMxS" tabindex="-1" role="presentation"></a>در ادامه مجموعهای از بررسیها را داریم که اطمینان حاصل شود که موقعیت بازیکن بیرون از بازهی مجاز قرار نگیرد. توجه داشته باشید که گاهی اوقات مختصات اسکرول تولیدی نادرست میشود، عددی منفی یا بیشتر از محدودهی قابل اسکرول. این مشکلی پیش نخواهد آورد – DOM آنها را به مقدارهای قابل قبول محدود میکند. اگر مقدار <code>scrollLeft</code> را برابر <bdo>-10</bdo> تنظیم کنید به صورت خودکار 0 خواهد شد.</p>
<p><a class="p_ident" id="p_wp2sWlqMYv" href="#p_wp2sWlqMYv" tabindex="-1" role="presentation"></a>کمی کار راحتتر میشد اگر همیشه بازیکن در مرکز میدان دید scroll میشد. اما این کار باعث میشود تا یک حالت لرزش ایجاد شود. در هنگام پرش، تصویر مداوم به سمت بالا و پایین حرکت میکند. بهتر است که یک ناحیهی بیطرف در مرکز صفحهی نمایش داشته باشیم که بتوان در آن بدون ایجاد اسکرول به حرکت پرداخت.</p>
<p>اکنون میتوانیم مرحلهی کوچکمان را نمایش دهیم.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_LDPexlnWt1" href="#c_LDPexlnWt1" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">link</span> <span class="cm-attribute">rel</span>=<span class="cm-string">"stylesheet"</span> <span class="cm-attribute">href</span>=<span class="cm-string">"css/game.css"</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">simpleLevel</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">Level</span>(<span class="cm-variable">simpleLevelPlan</span>);
<span class="cm-keyword">let</span> <span class="cm-def">display</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">DOMDisplay</span>(<span class="cm-variable">document</span>.<span class="cm-property">body</span>, <span class="cm-variable">simpleLevel</span>);
<span class="cm-variable">display</span>.<span class="cm-property">syncState</span>(<span class="cm-variable">State</span>.<span class="cm-property">start</span>(<span class="cm-variable">simpleLevel</span>));
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p>برچسب <bdo><code><link></code></bdo> زمانی که با <bdo><code>rel="stylesheet"</code></bdo> استفاده می شود ، باعث بارگیری یک فایل CSS درون صفحه میشود. فایل <bdo><code>game.css</code></bdo> سبکهای مورد نیاز بازی را در بر دارد.</p>
<h2><a class="h_ident" id="h_p1c0/oaShN" href="#h_p1c0/oaShN" tabindex="-1" role="presentation"></a>حرکت و برخورد</h2>
<p><a class="p_ident" id="p_DfnuoLrVpV" href="#p_DfnuoLrVpV" tabindex="-1" role="presentation"></a>اکنون در نقطهای قرار داریم که میتوانیم حرکت را به بازی اضافه کنیم – جالب ترین قسمت بازی. روش اصلی که اکثر بازیهای مشابه استفاده میکنند این است که زمان را به گامهای کوچک تقسیم کنیم و هر گام، بازیگران را مسافتی معادل ضرب سرعتشان در اندازه گام زمانی، جابجا کنیم. ما زمان را با ثانیه اندازهگیری میکنیم؛ بنابراین سرعتها به صورت واحد (unit) بر ثانیه بیان میشوند.</p>
<p>حرکت دادن عناصر ساده است. قسمت مشکل مدیریت تعاملات بین عنصرها میباشد. زمانی که بازیکن به دیوار یا زمین برخورد میکند، نباید وارد آن بشود. بازی باید متوجه برخورد یک شیء با شیء دیگر بشود و واکنش مناسبی نشان دهد. برای دیوارها، حرکت باید متوقف شود. زمانی که به سکهها برخورد میشود، باید جمعآوری شوند. زمانی که گدازهها لمس میشوند، بازی باید با شکست پایان یابد.</p>
<p><a class="p_ident" id="p_tRptEEG0ga" href="#p_tRptEEG0ga" tabindex="-1" role="presentation"></a>به طور کلی حل این مشکل کار زیادی میطلبد. میتوانید از کتابخانههایی که معمولا موتورهای فیزیک (<em>physics engines</em>) نامیده میشوند استفاده کنید که این تعاملات فیزیکی بین اشیاء را در دو یا سه بعد شبیهسازی میکنند. ما از روش سادهتری در این فصل استفاده خواهیم کرد و فقط برخورد بین مستطیلها را به شکلی خیلی ساده و ابتدایی پوشش میدهیم.</p>
<p>قبل از حرکت دادن یک بازیکن یا یک گدازه، بررسی میکنیم که آیا این حرکت باعث میشود که شیء به درون دیوار برود. اگر این طور بود، حرکت را لغو میکنیم. پاسخ به این گونه برخورد بستگی به نوع بازیگر دارد – اگر بازیکن بود که از حرکت می ایستد درحالیکه بلاک گدازه هم در جهت عکس برخواهد گشت.</p>
<p>در این روش لازم است که گامهای زمانی ما کوتاه باشند تا بتوان قبل از لمس شیء از حرکت ایستاد. اگر گامهای زمانی (و در نتیجه گامهای حرکتی) خیلی بلند باشند، منجر میشود که بازیکن با فاصلهی محسوسی روی زمین شناور بماند. روشی دیگر، که احتمالا بهتر اما پیچیدهتر است این است که دقیقا نقطهی برخورد را پیدا کنیم و به سمت آن حرکت انجام شود. ما از روش ساده تر استفاده خواهیم کرد و مشکلاتش را با کوتاهتر کردن گامهای زمانی حل میکنیم.</p>
<p id="touches">متد پیش رو به ما نشان میدهد که یک مستطیل (که با موقعیت و اندازه مشخص میشود) با یک عنصر grid داده شده تماس دارد یا خیر.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_EY7I5wl4Zy" href="#c_EY7I5wl4Zy" tabindex="-1" role="presentation"></a><span class="cm-variable">Level</span>.<span class="cm-property">prototype</span>.<span class="cm-property">touches</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">pos</span>, <span class="cm-def">size</span>, <span class="cm-def">type</span>) {
<span class="cm-keyword">var</span> <span class="cm-def">xStart</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">floor</span>(<span class="cm-variable-2">pos</span>.<span class="cm-property">x</span>);
<span class="cm-keyword">var</span> <span class="cm-def">xEnd</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">ceil</span>(<span class="cm-variable-2">pos</span>.<span class="cm-property">x</span> <span class="cm-operator">+</span> <span class="cm-variable-2">size</span>.<span class="cm-property">x</span>);
<span class="cm-keyword">var</span> <span class="cm-def">yStart</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">floor</span>(<span class="cm-variable-2">pos</span>.<span class="cm-property">y</span>);
<span class="cm-keyword">var</span> <span class="cm-def">yEnd</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">ceil</span>(<span class="cm-variable-2">pos</span>.<span class="cm-property">y</span> <span class="cm-operator">+</span> <span class="cm-variable-2">size</span>.<span class="cm-property">y</span>);
<span class="cm-keyword">for</span> (<span class="cm-keyword">var</span> <span class="cm-def">y</span> <span class="cm-operator">=</span> <span class="cm-variable-2">yStart</span>; <span class="cm-variable-2">y</span> <span class="cm-operator"><</span> <span class="cm-variable-2">yEnd</span>; <span class="cm-variable-2">y</span><span class="cm-operator">++</span>) {
<span class="cm-keyword">for</span> (<span class="cm-keyword">var</span> <span class="cm-def">x</span> <span class="cm-operator">=</span> <span class="cm-variable-2">xStart</span>; <span class="cm-variable-2">x</span> <span class="cm-operator"><</span> <span class="cm-variable-2">xEnd</span>; <span class="cm-variable-2">x</span><span class="cm-operator">++</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">isOutside</span> <span class="cm-operator">=</span> <span class="cm-variable-2">x</span> <span class="cm-operator"><</span> <span class="cm-number">0</span> <span class="cm-operator">|</span><span class="cm-operator">|</span> <span class="cm-variable-2">x</span> <span class="cm-operator">>=</span> <span class="cm-keyword">this</span>.<span class="cm-property">width</span> <span class="cm-operator">|</span><span class="cm-operator">|</span>
<span class="cm-variable-2">y</span> <span class="cm-operator"><</span> <span class="cm-number">0</span> <span class="cm-operator">|</span><span class="cm-operator">|</span> <span class="cm-variable-2">y</span> <span class="cm-operator">>=</span> <span class="cm-keyword">this</span>.<span class="cm-property">height</span>;
<span class="cm-keyword">let</span> <span class="cm-def">here</span> <span class="cm-operator">=</span> <span class="cm-variable-2">isOutside</span> <span class="cm-operator">?</span> <span class="cm-string">"wall"</span> : <span class="cm-keyword">this</span>.<span class="cm-property">rows</span>[<span class="cm-variable-2">y</span>][<span class="cm-variable-2">x</span>];
<span class="cm-keyword">if</span> (<span class="cm-variable-2">here</span> <span class="cm-operator">==</span> <span class="cm-variable-2">type</span>) <span class="cm-keyword">return</span> <span class="cm-atom">true</span>;
}
}
<span class="cm-keyword">return</span> <span class="cm-atom">false</span>;
};</pre>
<p><a class="p_ident" id="p_Jba2DLTEbb" href="#p_Jba2DLTEbb" tabindex="-1" role="presentation"></a>متد بالا مجموعهای از مربعهای grid که body با آنها همپوشانی دارد را با استفاده از <bdo><code>Math.floor</code></bdo> و <bdo><code>Math.ceil</code></bdo> روی مختصاتش محاسبه میکند. به خاطر داشته باشید که مربعهای grid دارای اندازهی 1 در 1 واحد میباشند. با رند کردن کنارههای مستطیل به بالا و پایین، بازهای از مربعهای پسزمینه را در اختیار خواهیم داشت که مستطیل آنها را لمس میکند.</p><figure><img src="img/game-grid.svg" alt="Finding collisions on a grid"></figure>
<p>ما مربعهای grid به دست آمده از رندسازی مختصات را یک به یک پیمایش میکنیم و زمانی که یک مربع مورد نظر پیدا شود مقدار <code>true</code> را بر میگردانیم. مربعهای بیرون از مرحله معمولا به عنوان <code>"wall"</code> (دیوار) در نظر گرفته میشوند تا اطمینان حاصل شود که بازیکن نتواند از جهان تعریف شده بیرون برود و ما هم به صورت تصادفی ورای مرزهای آرایهی <code>rows</code> را در نظر نگیریم.</p>
<p>متد <code>update</code> وضعیت، از <code>touches</code> برای تشخیص برخورد بازیکن با گدازه استفاده میکند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_af6Xo1AsIn" href="#c_af6Xo1AsIn" tabindex="-1" role="presentation"></a><span class="cm-variable">State</span>.<span class="cm-property">prototype</span>.<span class="cm-property">update</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">time</span>, <span class="cm-def">keys</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">actors</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">actors</span>
.<span class="cm-property">map</span>(<span class="cm-def">actor</span> <span class="cm-operator">=></span> <span class="cm-variable-2">actor</span>.<span class="cm-property">update</span>(<span class="cm-variable-2">time</span>, <span class="cm-keyword">this</span>, <span class="cm-variable-2">keys</span>));
<span class="cm-keyword">let</span> <span class="cm-def">newState</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">State</span>(<span class="cm-keyword">this</span>.<span class="cm-property">level</span>, <span class="cm-variable-2">actors</span>, <span class="cm-keyword">this</span>.<span class="cm-property">status</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">newState</span>.<span class="cm-property">status</span> <span class="cm-operator">!=</span> <span class="cm-string">"playing"</span>) <span class="cm-keyword">return</span> <span class="cm-variable-2">newState</span>;
<span class="cm-keyword">let</span> <span class="cm-def">player</span> <span class="cm-operator">=</span> <span class="cm-variable-2">newState</span>.<span class="cm-property">player</span>;
<span class="cm-keyword">if</span> (<span class="cm-keyword">this</span>.<span class="cm-property">level</span>.<span class="cm-property">touches</span>(<span class="cm-variable-2">player</span>.<span class="cm-property">pos</span>, <span class="cm-variable-2">player</span>.<span class="cm-property">size</span>, <span class="cm-string">"lava"</span>)) {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">State</span>(<span class="cm-keyword">this</span>.<span class="cm-property">level</span>, <span class="cm-variable-2">actors</span>, <span class="cm-string">"lost"</span>);
}
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">actor</span> <span class="cm-keyword">of</span> <span class="cm-variable-2">actors</span>) {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">actor</span> <span class="cm-operator">!=</span> <span class="cm-variable-2">player</span> <span class="cm-operator">&</span><span class="cm-operator">&</span> <span class="cm-variable">overlap</span>(<span class="cm-variable-2">actor</span>, <span class="cm-variable-2">player</span>)) {
<span class="cm-variable-2">newState</span> <span class="cm-operator">=</span> <span class="cm-variable-2">actor</span>.<span class="cm-property">collide</span>(<span class="cm-variable-2">newState</span>);
}
}
<span class="cm-keyword">return</span> <span class="cm-variable-2">newState</span>;
};</pre>
<p>به این متد یک گام زمانی و یک ساختار داده که مشخص میکند کدام کلیدها نگهداشته می شوند، ارسال میشود. اولین کاری که انجام میدهد این است که متد <code>update</code> را روی همهی بازیگران فراخوانی میکند که منجر به تولید آرایهای از بازیگران بهروز میشود. بازیگران نیز گام زمانی، کلیدها و وضعیت را دریافت میکنند که بتوانند بهروز رسانیشان را بر اساس آنها انجام دهند. فقط بازیکن است که در واقع کلیدها را می خواند. به دلیل این که تنها بازیگری است که توسط صفحهکلید کنترل میشود.</p>
<p>اگر بازی به اتمام رسیده شده باشد، دیگر نباید پردازشی انجام شود (بعد از باختن دیگر نمیتوان بازی را برد یا برعکس). در غیر این صورت، متد بررسی میکند که بازیکن با گدازهی پسزمینه برخورد دارد یا خیر. در صورت برخورد، بازیکن می بازد و کار تمام است. سرانجام، اگر بازی هنوز در حال اجرا است، همپوشانی دیگر بازیگران را با بازیکن بررسی مینماید.</p>
<p><a class="p_ident" id="p_TX7+4hfhb8" href="#p_TX7+4hfhb8" tabindex="-1" role="presentation"></a>همپوشانی بین بازیگران توسط تابع <code>overlap</code> تشخیص داده میشود. این تابع دو بازیگر را دریافت کرده و در صورت تماس آنها، مقدار true را تولید میکند- که در این جا زمانی رخ میدهد که همپوشانی در جهت محور x و محور y رخ داده باشد.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_Z19icVgfA7" href="#c_Z19icVgfA7" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">overlap</span>(<span class="cm-def">actor1</span>, <span class="cm-def">actor2</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable-2">actor1</span>.<span class="cm-property">pos</span>.<span class="cm-property">x</span> <span class="cm-operator">+</span> <span class="cm-variable-2">actor1</span>.<span class="cm-property">size</span>.<span class="cm-property">x</span> <span class="cm-operator">></span> <span class="cm-variable-2">actor2</span>.<span class="cm-property">pos</span>.<span class="cm-property">x</span> <span class="cm-operator">&</span><span class="cm-operator">&</span>
<span class="cm-variable-2">actor1</span>.<span class="cm-property">pos</span>.<span class="cm-property">x</span> <span class="cm-operator"><</span> <span class="cm-variable-2">actor2</span>.<span class="cm-property">pos</span>.<span class="cm-property">x</span> <span class="cm-operator">+</span> <span class="cm-variable-2">actor2</span>.<span class="cm-property">size</span>.<span class="cm-property">x</span> <span class="cm-operator">&</span><span class="cm-operator">&</span>
<span class="cm-variable-2">actor1</span>.<span class="cm-property">pos</span>.<span class="cm-property">y</span> <span class="cm-operator">+</span> <span class="cm-variable-2">actor1</span>.<span class="cm-property">size</span>.<span class="cm-property">y</span> <span class="cm-operator">></span> <span class="cm-variable-2">actor2</span>.<span class="cm-property">pos</span>.<span class="cm-property">y</span> <span class="cm-operator">&</span><span class="cm-operator">&</span>
<span class="cm-variable-2">actor1</span>.<span class="cm-property">pos</span>.<span class="cm-property">y</span> <span class="cm-operator"><</span> <span class="cm-variable-2">actor2</span>.<span class="cm-property">pos</span>.<span class="cm-property">y</span> <span class="cm-operator">+</span> <span class="cm-variable-2">actor2</span>.<span class="cm-property">size</span>.<span class="cm-property">y</span>;
}</pre>
<p>اگر هرکدام از بازیگران همپوشانی داشته باشند، متد <code>collide</code> این شانس را دارد که وضعیت را بهروز رسانی کند. تماس با یک بازیگر گدازه موجب باختن در بازی و تغییر وضعیت به <code>"lost"</code> میشود. سکهها در صورت تماس با آنها ناپدید میشوند و اگر آن تماس با آخرین سکه رخ داده باشد وضعیت برابر با <code>"won"</code> قرار میگیرد.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_jNqQLSOJRn" href="#c_jNqQLSOJRn" tabindex="-1" role="presentation"></a><span class="cm-variable">Lava</span>.<span class="cm-property">prototype</span>.<span class="cm-property">collide</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">state</span>) {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">State</span>(<span class="cm-variable-2">state</span>.<span class="cm-property">level</span>, <span class="cm-variable-2">state</span>.<span class="cm-property">actors</span>, <span class="cm-string">"lost"</span>);
};
<span class="cm-variable">Coin</span>.<span class="cm-property">prototype</span>.<span class="cm-property">collide</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">state</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">filtered</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>.<span class="cm-property">actors</span>.<span class="cm-property">filter</span>(<span class="cm-def">a</span> <span class="cm-operator">=></span> <span class="cm-variable-2">a</span> <span class="cm-operator">!=</span> <span class="cm-keyword">this</span>);
<span class="cm-keyword">let</span> <span class="cm-def">status</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>.<span class="cm-property">status</span>;
<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable-2">filtered</span>.<span class="cm-property">some</span>(<span class="cm-def">a</span> <span class="cm-operator">=></span> <span class="cm-variable-2">a</span>.<span class="cm-property">type</span> <span class="cm-operator">==</span> <span class="cm-string">"coin"</span>)) <span class="cm-variable-2">status</span> <span class="cm-operator">=</span> <span class="cm-string">"won"</span>;
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">State</span>(<span class="cm-variable-2">state</span>.<span class="cm-property">level</span>, <span class="cm-variable-2">filtered</span>, <span class="cm-variable-2">status</span>);
};</pre>
<h2 id="actors"><a class="h_ident" id="h_iPxcecKbn5" href="#h_iPxcecKbn5" tabindex="-1" role="presentation"></a>بهروزرسانیهای بازیگر</h2>
<p>اشیاء بازیگر دارای متدی به نام <code>update</code> میباشند که به عنوان ورودی، گام زمان، شیء وضعیت و یک شیء <code>keys</code> دریافت میکند. متد <code>update</code> مربوط به نوع <code>Lava</code> شیء <code>keys</code> را در نظر نمیگیرد.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_vuIaAGYDTl" href="#c_vuIaAGYDTl" tabindex="-1" role="presentation"></a><span class="cm-variable">Lava</span>.<span class="cm-property">prototype</span>.<span class="cm-property">update</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">time</span>, <span class="cm-def">state</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">newPos</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">pos</span>.<span class="cm-property">plus</span>(<span class="cm-keyword">this</span>.<span class="cm-property">speed</span>.<span class="cm-property">times</span>(<span class="cm-variable-2">time</span>));
<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable-2">state</span>.<span class="cm-property">level</span>.<span class="cm-property">touches</span>(<span class="cm-variable-2">newPos</span>, <span class="cm-keyword">this</span>.<span class="cm-property">size</span>, <span class="cm-string">"wall"</span>)) {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Lava</span>(<span class="cm-variable-2">newPos</span>, <span class="cm-keyword">this</span>.<span class="cm-property">speed</span>, <span class="cm-keyword">this</span>.<span class="cm-property">reset</span>);
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-keyword">this</span>.<span class="cm-property">reset</span>) {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Lava</span>(<span class="cm-keyword">this</span>.<span class="cm-property">reset</span>, <span class="cm-keyword">this</span>.<span class="cm-property">speed</span>, <span class="cm-keyword">this</span>.<span class="cm-property">reset</span>);
} <span class="cm-keyword">else</span> {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Lava</span>(<span class="cm-keyword">this</span>.<span class="cm-property">pos</span>, <span class="cm-keyword">this</span>.<span class="cm-property">speed</span>.<span class="cm-property">times</span>(<span class="cm-operator">-</span><span class="cm-number">1</span>));
}
};</pre>
<p><a class="p_ident" id="p_UoWb/jdcGq" href="#p_UoWb/jdcGq" tabindex="-1" role="presentation"></a>این متد یک موقعیت جدید را با افزودن نتیجهی گام زمانی و سرعت فعلی به موقعیت قبلی اش، محاسبه میکند. اگر مانعی برای موقعیت جدید وجود نداشته باشد، به آنجا حرکت می کند. اگر مانعی موجود باشد ، رفتار متناسب با نوع بلاک گدازه خواهد بود – گدازهی dripping دارای یک موقعیت <code>reset</code> میباشد که وقتی به شیءای برخود میکند به آن بپرد. گدازهای که بالاپایین میرود، سرعتش را با ضرب در <bdo>-1</bdo> منفی میکند در نتیجه با رسیدن به مانع، جهت حرکت معکوس میشود.</p>
<p>سکهها از متد <code>update</code> شان استفاده میکنند تا جنب و جوش داشته باشند. سکهها برخورد با grid را در نظر نمی گیرند چرا که آنها فقط درون مربع خودشان جنب و جوش دارند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_+DC3G3xD19" href="#c_+DC3G3xD19" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> <span class="cm-def">wobbleSpeed</span> <span class="cm-operator">=</span> <span class="cm-number">8</span>, <span class="cm-def">wobbleDist</span> <span class="cm-operator">=</span> <span class="cm-number">0.07</span>;
<span class="cm-variable">Coin</span>.<span class="cm-property">prototype</span>.<span class="cm-property">update</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">time</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">wobble</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">wobble</span> <span class="cm-operator">+</span> <span class="cm-variable-2">time</span> <span class="cm-operator">*</span> <span class="cm-variable">wobbleSpeed</span>;
<span class="cm-keyword">let</span> <span class="cm-def">wobblePos</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">sin</span>(<span class="cm-variable-2">wobble</span>) <span class="cm-operator">*</span> <span class="cm-variable">wobbleDist</span>;
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Coin</span>(<span class="cm-keyword">this</span>.<span class="cm-property">basePos</span>.<span class="cm-property">plus</span>(<span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-number">0</span>, <span class="cm-variable-2">wobblePos</span>)),
<span class="cm-keyword">this</span>.<span class="cm-property">basePos</span>, <span class="cm-variable-2">wobble</span>);
};</pre>
<p>خاصیت <code>wobble</code> برای رصد زمان افزایش داده میشود و بعد به عنوان آرگومان <bdo><code>Math.sin</code></bdo> برای پیدا کردن موقعیت جدیدی روی موج استفاده میشود. موقعیت فعلی سکه، سپس به وسیلهی موقعیت پایه آن و جایگاهی که روی این موج دارد محاسبه میشود.</p>
<p>این یعنی نیازی به درنظر گرفتن بازیکن نیست. حرکت بازیکن به صورت جداگانه با توجه به محور حرکت مدیریت میشود؛ به این دلیل که برخورد با زمین نباید مانع از حرکت افقی بشود. و برخورد با دیوار نباید مانع از پریدن یا افتادن بشود.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_cBJRAPnr2+" href="#c_cBJRAPnr2+" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> <span class="cm-def">playerXSpeed</span> <span class="cm-operator">=</span> <span class="cm-number">7</span>;
<span class="cm-keyword">const</span> <span class="cm-def">gravity</span> <span class="cm-operator">=</span> <span class="cm-number">30</span>;
<span class="cm-keyword">const</span> <span class="cm-def">jumpSpeed</span> <span class="cm-operator">=</span> <span class="cm-number">17</span>;
<span class="cm-variable">Player</span>.<span class="cm-property">prototype</span>.<span class="cm-property">update</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">time</span>, <span class="cm-def">state</span>, <span class="cm-def">keys</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">xSpeed</span> <span class="cm-operator">=</span> <span class="cm-number">0</span>;
<span class="cm-keyword">if</span> (<span class="cm-variable-2">keys</span>.<span class="cm-property">ArrowLeft</span>) <span class="cm-variable-2">xSpeed</span> <span class="cm-operator">-=</span> <span class="cm-variable">playerXSpeed</span>;
<span class="cm-keyword">if</span> (<span class="cm-variable-2">keys</span>.<span class="cm-property">ArrowRight</span>) <span class="cm-variable-2">xSpeed</span> <span class="cm-operator">+=</span> <span class="cm-variable">playerXSpeed</span>;
<span class="cm-keyword">let</span> <span class="cm-def">pos</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">pos</span>;
<span class="cm-keyword">let</span> <span class="cm-def">movedX</span> <span class="cm-operator">=</span> <span class="cm-variable-2">pos</span>.<span class="cm-property">plus</span>(<span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-variable-2">xSpeed</span> <span class="cm-operator">*</span> <span class="cm-variable-2">time</span>, <span class="cm-number">0</span>));
<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable-2">state</span>.<span class="cm-property">level</span>.<span class="cm-property">touches</span>(<span class="cm-variable-2">movedX</span>, <span class="cm-keyword">this</span>.<span class="cm-property">size</span>, <span class="cm-string">"wall"</span>)) {
<span class="cm-variable-2">pos</span> <span class="cm-operator">=</span> <span class="cm-variable-2">movedX</span>;
}
<span class="cm-keyword">let</span> <span class="cm-def">ySpeed</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">speed</span>.<span class="cm-property">y</span> <span class="cm-operator">+</span> <span class="cm-variable-2">time</span> <span class="cm-operator">*</span> <span class="cm-variable">gravity</span>;
<span class="cm-keyword">let</span> <span class="cm-def">movedY</span> <span class="cm-operator">=</span> <span class="cm-variable-2">pos</span>.<span class="cm-property">plus</span>(<span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-number">0</span>, <span class="cm-variable-2">ySpeed</span> <span class="cm-operator">*</span> <span class="cm-variable-2">time</span>));
<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable-2">state</span>.<span class="cm-property">level</span>.<span class="cm-property">touches</span>(<span class="cm-variable-2">movedY</span>, <span class="cm-keyword">this</span>.<span class="cm-property">size</span>, <span class="cm-string">"wall"</span>)) {
<span class="cm-variable-2">pos</span> <span class="cm-operator">=</span> <span class="cm-variable-2">movedY</span>;
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">keys</span>.<span class="cm-property">ArrowUp</span> <span class="cm-operator">&</span><span class="cm-operator">&</span> <span class="cm-variable-2">ySpeed</span> <span class="cm-operator">></span> <span class="cm-number">0</span>) {
<span class="cm-variable-2">ySpeed</span> <span class="cm-operator">=</span> <span class="cm-operator">-</span><span class="cm-variable">jumpSpeed</span>;
} <span class="cm-keyword">else</span> {
<span class="cm-variable-2">ySpeed</span> <span class="cm-operator">=</span> <span class="cm-number">0</span>;
}
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Player</span>(<span class="cm-variable-2">pos</span>, <span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-variable-2">xSpeed</span>, <span class="cm-variable-2">ySpeed</span>));
};</pre>
<p>حرکت افقی بر اساس وضعیت چپ و راست کلیدهای جهتدار محاسبه میشوند. وقتی دیواری وجود ندارد که مانع از ایجاد موقعیت جدید توسط این حرکت بشود، از آن استفاده میشود در غیر این صورت موقعیت قبلی حفظ می گردد.</p>
<p>حرکت عمودی به همان صورت کار میکند اما باید پریدن و گرانش زمین را شبیه سازی کند. به خاطر وجود گرانش زمین، سرعت عمودی بازیکن (<code>ySpeed</code>) در ابتدا شتاب میگیرد.</p>
<p>دوباره وجود دیوارها را بررسی میکنیم. اگر به هیچ دیواری برخورد نکردیم، موقعیت جدید استفاده میشود. اگر دیواری وجود داشت دو حالت ممکن است پیش بیاید. زمانی که کلید بالا فشار داده شده است و در حال حرکت به پایین هستیم ( به این معنا که چیزی که به آن برخورد میکنیم پایین ما قرار دارد) سرعت با یک مقدار نسبتا بزرگ منفی تنظیم میشود. این باعث پرش بازیکن میشود.اگر این حالت به وجود نیامد، بازیکن به چیزی برخورد کرده و سرعت صفر میشود.</p>
<p>میزان گرانش ، سرعت پرش، و ثابتهای دیگر در بازی با تست و خطا تنظیم میشوند. من مقدارهای متفاوتی را آزمایش کردم تا به ترکیبی که دوست داشتم رسیدم.</p>
<h2><a class="h_ident" id="h_AIg8nkyNcm" href="#h_AIg8nkyNcm" tabindex="-1" role="presentation"></a>رصد کلیدها</h2>
<p>برای یک بازی شبیه به این، قصد نداریم تا اثر فشردن کلید با هر بار فشار دادن آن ظاهر شود. بلکه میخواهیم اثر کلیدها (حرکت دادن شخصیت بازی) تا زمانی که کلید فشرده نگه داشته میشود باقی بماند.</p>
<p>باید یک گردانندهی کلید تعریف کنیم که وضعیت فعلی کلیدهای جهتدار چپ، راست و بالا را نگهداری کند. همچنین لازم است که از فراخوانی <code>preventDefault</code> برای این کلیدها استفاده کنیم تا از اسکرول صفحه جلوگیری کنیم.</p>
<p>تابع پیش رو، اگر آرایهای از نام کلیدها دریافت کند، شیءای را برمی گرداند که موقعیت فعلی آن کلیدها را رصد میکند. این تابع گردانندهی رخدادی برای <code>"keydown"</code> و <code>"keyup"</code> ثبت میکند و زمانی که کد کلید در رخداد در مجموعهی کدهای کلیدی که رصد میشود وجود داشت، شیء را به روز میکند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_HHYPd26+il" href="#c_HHYPd26+il" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">trackKeys</span>(<span class="cm-def">keys</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">down</span> <span class="cm-operator">=</span> <span class="cm-variable">Object</span>.<span class="cm-property">create</span>(<span class="cm-atom">null</span>);
<span class="cm-keyword">function</span> <span class="cm-def">track</span>(<span class="cm-def">event</span>) {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">keys</span>.<span class="cm-property">includes</span>(<span class="cm-variable-2">event</span>.<span class="cm-property">key</span>)) {
<span class="cm-variable-2">down</span>[<span class="cm-variable-2">event</span>.<span class="cm-property">key</span>] <span class="cm-operator">=</span> <span class="cm-variable-2">event</span>.<span class="cm-property">type</span> <span class="cm-operator">==</span> <span class="cm-string">"keydown"</span>;
<span class="cm-variable-2">event</span>.<span class="cm-property">preventDefault</span>();
}
}
<span class="cm-variable">window</span>.<span class="cm-property">addEventListener</span>(<span class="cm-string">"keydown"</span>, <span class="cm-variable-2">track</span>);
<span class="cm-variable">window</span>.<span class="cm-property">addEventListener</span>(<span class="cm-string">"keyup"</span>, <span class="cm-variable-2">track</span>);
<span class="cm-keyword">return</span> <span class="cm-variable-2">down</span>;
}
<span class="cm-keyword">const</span> <span class="cm-def">arrowKeys</span> <span class="cm-operator">=</span>
<span class="cm-variable">trackKeys</span>([<span class="cm-string">"ArrowLeft"</span>, <span class="cm-string">"ArrowRight"</span>, <span class="cm-string">"ArrowUp"</span>]);</pre>
<p><a class="p_ident" id="p_k/l4Io+JM0" href="#p_k/l4Io+JM0" tabindex="-1" role="presentation"></a>تابع گردانندهی مشابهی، برای هر دو نوع رخداد استفاده میشود. خاصیت <code>type</code> شیء رخداد بررسی شده تا مشخص شود که آیا وضعیت کلید باید به true (معادل <code>"keydown"</code>) یا false (معادل <code>"keyup"</code>) بهروز شود.</p>
<h2 id="runAnimation"><a class="h_ident" id="h_gNXPVkXRfH" href="#h_gNXPVkXRfH" tabindex="-1" role="presentation"></a>اجرای بازی</h2>
<p>تابع <code>requestAnimationFrame</code>، که در <a href="14_dom.html#animationFrame">فصل 14</a> با آن آشنا شدیم، راه خوبی برای متحرکسازی بازی فراهم مینماید. اما رابط آن بسیار ابتدایی است- برای استفاده از آن باید زمانی که در آن تابع ما، آخرین بار فراخوانی شده را رصد کنیم و تابع <code>requestAnimationFrame</code> را بعد از هر فریم دوباره فراخوانی کنیم.</p>
<p>اجازه بدهید تا یک تابع کمکی تعریف کنیم که آن قسمتهای کسلکننده را توسط رابطی مناسب پوشش دهد و این امکان را فراهم کند که فقط <code>runAnimation</code> را با ارسال تابعی که یک تفاوت زمان را به عنوان آرگومان میگیرد و یک فریم واحد را ترسیم می کند، فراخوانی کنیم. زمانی که تابع فریم مقدار <code>false</code> را برگرداند، انیمیشن متوقف می شود.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_AVT0noPnDW" href="#c_AVT0noPnDW" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">runAnimation</span>(<span class="cm-def">frameFunc</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">lastTime</span> <span class="cm-operator">=</span> <span class="cm-atom">null</span>;
<span class="cm-keyword">function</span> <span class="cm-def">frame</span>(<span class="cm-def">time</span>) {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">lastTime</span> <span class="cm-operator">!=</span> <span class="cm-atom">null</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">timeStep</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">min</span>(<span class="cm-variable-2">time</span> <span class="cm-operator">-</span> <span class="cm-variable-2">lastTime</span>, <span class="cm-number">100</span>) <span class="cm-operator">/</span> <span class="cm-number">1000</span>;
<span class="cm-keyword">if</span> (<span class="cm-variable-2">frameFunc</span>(<span class="cm-variable-2">timeStep</span>) <span class="cm-operator">===</span> <span class="cm-atom">false</span>) <span class="cm-keyword">return</span>;
}
<span class="cm-variable-2">lastTime</span> <span class="cm-operator">=</span> <span class="cm-variable-2">time</span>;
<span class="cm-variable">requestAnimationFrame</span>(<span class="cm-variable-2">frame</span>);
}
<span class="cm-variable">requestAnimationFrame</span>(<span class="cm-variable-2">frame</span>);
}</pre>
<p><a class="p_ident" id="p_MQuG4LYrgo" href="#p_MQuG4LYrgo" tabindex="-1" role="presentation"></a>من بیشینهی گام هر فریم را معادل 100 هزارم ثانیه قرار دادم (یک دهم یک ثانیه). زمانی که برگه یا پنجرهی مرورگر حاوی صفحهی ما فعال نیست، فراخوانیهای <code>requestAnimationFrame</code> تا زمان فعال شدن دوبارهی برگه مرورگر به تعلیق در میآیند. در این مثال، تفاوت بین <code>lastTime</code> و <code>time</code> برابر با کل زمانی میشود که صفحه مخفی (غیرفعال) بوده است. این همه پیشروی با هرگام در بازی احمقانه به نظر میرسد و ممکن است اثرات جانبی عجیب غریبی داشته باشد، مثلا بازیکن در زمین فرو برود.</p>
<p>تابع همچنین گامهای زمانی را به ثانیه تبدیل میکند که کمیت سادهتری نسبت به هزارم ثانیه محسوب میشود.</p>
<p><a class="p_ident" id="p_FykHZel6vX" href="#p_FykHZel6vX" tabindex="-1" role="presentation"></a>تابع <code>runLevel</code> یک شیء <code>Level</code> را گرفته و یک سازنده نمایش میدهد و یک خروجی از نوع promise تولید میکند. این تابع مرحله (در <bdo><code>document.body</code></bdo>) را نمایش میدهد و امکان بازی را برای بازیکن فراهم میکند. زمانی که مرحله به پایان رسید (برنده یا بازنده)، <code>runLevel</code> یک ثانیهی دیگر منتظر می ماند (برای اینکه به کاربر نشان دهد چه اتفاقی می افتد) و بعد صفحه را پاک کرده، انیمیشن را متوقف نموده و promise را برای وضعیت نهایی بازی رسیدگی میکند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_HTrHnVaIWA" href="#c_HTrHnVaIWA" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">runLevel</span>(<span class="cm-def">level</span>, <span class="cm-def">Display</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">display</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable-2">Display</span>(<span class="cm-variable">document</span>.<span class="cm-property">body</span>, <span class="cm-variable-2">level</span>);
<span class="cm-keyword">let</span> <span class="cm-def">state</span> <span class="cm-operator">=</span> <span class="cm-variable">State</span>.<span class="cm-property">start</span>(<span class="cm-variable-2">level</span>);
<span class="cm-keyword">let</span> <span class="cm-def">ending</span> <span class="cm-operator">=</span> <span class="cm-number">1</span>;
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Promise</span>(<span class="cm-def">resolve</span> <span class="cm-operator">=></span> {
<span class="cm-variable">runAnimation</span>(<span class="cm-def">time</span> <span class="cm-operator">=></span> {
<span class="cm-variable-2">state</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>.<span class="cm-property">update</span>(<span class="cm-variable-2">time</span>, <span class="cm-variable">arrowKeys</span>);
<span class="cm-variable-2">display</span>.<span class="cm-property">syncState</span>(<span class="cm-variable-2">state</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">state</span>.<span class="cm-property">status</span> <span class="cm-operator">==</span> <span class="cm-string">"playing"</span>) {
<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">ending</span> <span class="cm-operator">></span> <span class="cm-number">0</span>) {
<span class="cm-variable-2">ending</span> <span class="cm-operator">-=</span> <span class="cm-variable-2">time</span>;
<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;
} <span class="cm-keyword">else</span> {
<span class="cm-variable-2">display</span>.<span class="cm-property">clear</span>();
<span class="cm-variable-2">resolve</span>(<span class="cm-variable-2">state</span>.<span class="cm-property">status</span>);
<span class="cm-keyword">return</span> <span class="cm-atom">false</span>;
}
});
});
}</pre>
<p><a class="p_ident" id="p_V0/5sMSc3r" href="#p_V0/5sMSc3r" tabindex="-1" role="presentation"></a>یک بازی شامل چندین مرحله میشود. زمانی که بازیکن در بازی میمیرد مرحلهی فعلی از نو شروع میشود. زمانی که یک مرحله به اتمام میرسد، به مرحلهی بعدی منتقل میشویم. این کار را میتوان با تابع پیش رو نمایش داد که آرایهای از طرحهای مراحل (رشتهای) و یک سازندهی Display را دریافت میکند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_SyT3weqmk4" href="#c_SyT3weqmk4" tabindex="-1" role="presentation"></a><span class="cm-keyword">async</span> <span class="cm-keyword">function</span> <span class="cm-def">runGame</span>(<span class="cm-def">plans</span>, <span class="cm-def">Display</span>) {
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">level</span> <span class="cm-operator">=</span> <span class="cm-number">0</span>; <span class="cm-variable-2">level</span> <span class="cm-operator"><</span> <span class="cm-variable-2">plans</span>.<span class="cm-property">length</span>;) {
<span class="cm-keyword">let</span> <span class="cm-def">status</span> <span class="cm-operator">=</span> <span class="cm-keyword">await</span> <span class="cm-variable">runLevel</span>(<span class="cm-keyword">new</span> <span class="cm-variable">Level</span>(<span class="cm-variable-2">plans</span>[<span class="cm-variable-2">level</span>]),
<span class="cm-variable-2">Display</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">status</span> <span class="cm-operator">==</span> <span class="cm-string">"won"</span>) <span class="cm-variable-2">level</span><span class="cm-operator">++</span>;
}
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-string">"You've won!"</span>);
}</pre>
<p><a class="p_ident" id="p_jC/YRC/GWd" href="#p_jC/YRC/GWd" tabindex="-1" role="presentation"></a>به دلیل اینکه تابع <code>runLevel</code> یک promise بر می گرداند، <code>runGame</code> را میتوان با یک تابع <code>async</code> نوشت، همانطور که در <a href="11_async.html">فصل 11</a> شرح داده شد. این تابع یک <code>promise</code> دیگر برمی گرداند که وقتی بازیکن بازی را تمام میکند رسیدگی میشود.</p>
<p><a class="p_ident" id="p_FeTYo+vIeI" href="#p_FeTYo+vIeI" tabindex="-1" role="presentation"></a>مجموعهای از طرحهای مراحل در متغیر <code>GAME_LEVELS</code> در قسمت کدهای مربوط به این فصل <a href="https://eloquentjavascript.net/code#16">قسمت کدهای مربوط به این فصل موجود است</a>. این صفحه این مراحل را به تابع <code>runGame</code> ارسال میکند تا بازی شروع شود.</p>
<pre class="snippet cm-s-default" data-language="text/html" data-focus="true" data-sandbox="null"><a class="c_ident" id="c_ftVm34P6My" href="#c_ftVm34P6My" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">link</span> <span class="cm-attribute">rel</span>=<span class="cm-string">"stylesheet"</span> <span class="cm-attribute">href</span>=<span class="cm-string">"css/game.css"</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">body</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-variable">runGame</span>(<span class="cm-variable">GAME_LEVELS</span>, <span class="cm-variable">DOMDisplay</span>);
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">body</span><span class="cm-tag cm-bracket">></span></pre>
<p>ببینید میتوانید آنها را شکست دهید. من از ساختنشان لذت زیادی بردم.</p>
<h2><a class="h_ident" id="h_ggOFdVwDCk" href="#h_ggOFdVwDCk" tabindex="-1" role="presentation"></a>تمرینها</h2>
<h3><a class="i_ident" id="i_H3Mx+/rC/i" href="#i_H3Mx+/rC/i" tabindex="-1" role="presentation"></a>پایان بازی</h3>
<p>یکی از سنتهای سکوبازیها این است که بازیکن با تعداد محدودی “جان” شروع میکند و با هر بار مردن در بازی یک واحد از آنها کاسته میشود. زمانیکه این تعداد تمام شود، بازی از ابتدا شروع میشود.</p>
<p><code>runGame</code> را بهبود ببخشید و “جانها” را هم به آن اضافه کنید. هر بازیکن با سه جان شروع کند. با هر بار شروع یک مرحله، تعداد جان باقی مانده را توسط <bdo><code>console.log</code></bdo> چاپ کنید.</p>
<pre class="snippet cm-s-default" data-language="text/html" data-focus="true"><a class="c_ident" id="c_/XVg6hHOl5" href="#c_/XVg6hHOl5" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">link</span> <span class="cm-attribute">rel</span>=<span class="cm-string">"stylesheet"</span> <span class="cm-attribute">href</span>=<span class="cm-string">"css/game.css"</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">body</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-comment">// The old runGame function. Modify it...</span>
<span class="cm-keyword">async</span> <span class="cm-keyword">function</span> <span class="cm-def">runGame</span>(<span class="cm-def">plans</span>, <span class="cm-def">Display</span>) {
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">level</span> <span class="cm-operator">=</span> <span class="cm-number">0</span>; <span class="cm-variable-2">level</span> <span class="cm-operator"><</span> <span class="cm-variable-2">plans</span>.<span class="cm-property">length</span>;) {
<span class="cm-keyword">let</span> <span class="cm-def">status</span> <span class="cm-operator">=</span> <span class="cm-keyword">await</span> <span class="cm-variable">runLevel</span>(<span class="cm-keyword">new</span> <span class="cm-variable">Level</span>(<span class="cm-variable-2">plans</span>[<span class="cm-variable-2">level</span>]),
<span class="cm-variable-2">Display</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">status</span> <span class="cm-operator">==</span> <span class="cm-string">"won"</span>) <span class="cm-variable-2">level</span><span class="cm-operator">++</span>;
}
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-string">"You've won!"</span>);
}
<span class="cm-variable">runGame</span>(<span class="cm-variable">GAME_LEVELS</span>, <span class="cm-variable">DOMDisplay</span>);
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">body</span><span class="cm-tag cm-bracket">></span></pre>
<h3><a class="i_ident" id="i_XO65bb5+SE" href="#i_XO65bb5+SE" tabindex="-1" role="presentation"></a>متوقف کردن بازی</h3>
<p><a class="p_ident" id="p_H3pPni9w0L" href="#p_H3pPni9w0L" tabindex="-1" role="presentation"></a>کاری کنید که در بازی بتوان با فشردن کلید Esc روی صفحهکلید بازی را متوقف کرده یا از حالت توقف خارج کرد.</p>
<p>این کار را میتوان با تغییر تابع <code>runLevel</code> انجام داد که از یک گردانندهی رخداد کلید دیگر استفاده کند و انیمیشن را فشردن Esc متوقف یا به حرکت در بیاورد.</p>
<p>رابط <code>runAnimation</code> ممکن است در ابتدا مناسب این تغییر به نظر نرسد اما اگر ترتیبی که <code>runLevel</code> آن را فراخوانی میکند را تغییر دهید، مناسب خواهد بود.</p>
<p>بعد از انجام قسمت بالا، چیزی دیگری هست که میتوانید انجام دهید. روشی که برای ثبت گردانندههای کلید استفاده میکردیم کمی مشکلزا است. شیء <code>arrows</code> در فضای سراسری در دسترس میباشد و گردانندهی رخدادش نیز حتی زمانی که بازی اجرا نمیشود در دسترس است. میتوان گفت که این دو از سیستم نشت کردهاند. <code>trackKeys</code> را توسعه داده تا راهی برای لغو ثبت گردانندههایش فراهم شود و بعد <code>runLevel</code> را تغییر دهید تا گردانندههایش را در زمانی که شروع میشود ثبت کند و با پایان کارش آنها را لغو ثبت نمایند.</p>
<pre class="snippet cm-s-default" data-language="text/html" data-focus="true"><a class="c_ident" id="c_ybbf+T2p9b" href="#c_ybbf+T2p9b" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">link</span> <span class="cm-attribute">rel</span>=<span class="cm-string">"stylesheet"</span> <span class="cm-attribute">href</span>=<span class="cm-string">"css/game.css"</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">body</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-comment">// The old runLevel function. Modify this...</span>
<span class="cm-keyword">function</span> <span class="cm-def">runLevel</span>(<span class="cm-def">level</span>, <span class="cm-def">Display</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">display</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable-2">Display</span>(<span class="cm-variable">document</span>.<span class="cm-property">body</span>, <span class="cm-variable-2">level</span>);
<span class="cm-keyword">let</span> <span class="cm-def">state</span> <span class="cm-operator">=</span> <span class="cm-variable">State</span>.<span class="cm-property">start</span>(<span class="cm-variable-2">level</span>);
<span class="cm-keyword">let</span> <span class="cm-def">ending</span> <span class="cm-operator">=</span> <span class="cm-number">1</span>;
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Promise</span>(<span class="cm-def">resolve</span> <span class="cm-operator">=></span> {
<span class="cm-variable">runAnimation</span>(<span class="cm-def">time</span> <span class="cm-operator">=></span> {
<span class="cm-variable-2">state</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>.<span class="cm-property">update</span>(<span class="cm-variable-2">time</span>, <span class="cm-variable">arrowKeys</span>);
<span class="cm-variable-2">display</span>.<span class="cm-property">syncState</span>(<span class="cm-variable-2">state</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">state</span>.<span class="cm-property">status</span> <span class="cm-operator">==</span> <span class="cm-string">"playing"</span>) {
<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">ending</span> <span class="cm-operator">></span> <span class="cm-number">0</span>) {
<span class="cm-variable-2">ending</span> <span class="cm-operator">-=</span> <span class="cm-variable-2">time</span>;
<span class="cm-keyword">return</span> <span class="cm-atom">true</span>;
} <span class="cm-keyword">else</span> {
<span class="cm-variable-2">display</span>.<span class="cm-property">clear</span>();
<span class="cm-variable-2">resolve</span>(<span class="cm-variable-2">state</span>.<span class="cm-property">status</span>);
<span class="cm-keyword">return</span> <span class="cm-atom">false</span>;
}
});
});
}
<span class="cm-variable">runGame</span>(<span class="cm-variable">GAME_LEVELS</span>, <span class="cm-variable">DOMDisplay</span>);
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">body</span><span class="cm-tag cm-bracket">></span></pre>
<div class="solution"><div class="solution-text">
<p>برای توقف یک تصویر متحرک میتوان مقدار <code>false</code> را از تابعی که به <code>runAnimation</code> داده میشود برگرداند میتوان دوباره آن را با فراخوانی دوبارهی <code>runAnimation</code> به حرکت درآورد.</p>
<p>خوب ما باید توسط تابعی که به <code>runAnimation</code> داده میشود، توقف بازی را اعلام کنیم. برای اینکار، میتوانید از متغیری که هر دوی گردانندهی رخداد و آن تابع به آن دسترسی دارند استفاده کنید.</p>
<p>زمانی که به دنبال راهی برای لغو ثبت گردانندههایی که توسط <code>trackKeys</code> ثبت شده اند هستید،به خاطر داشته باشید که دقیقا باید همان مقدار تابعی که به <code>addEventListener</code> ارسال شده است، به <code>removeEventListenre</code> ارسال شود تا آن گرداننده حذف شود. بنابراین، مقدار تابع <code>handler</code> که در <code>trackKeys</code> ایجاد شده، باید در دسترس کدی که عمل لغو ثبت را انجام میدهد باشد.</p>
</div></div>
<h3><a class="i_ident" id="i_0ZNPZSYsZ+" href="#i_0ZNPZSYsZ+" tabindex="-1" role="presentation"></a>یک هیولا</h3>
<p>یکی از چیزهای رایج در سکوبازیها داشتن دشمنانی است که بتوان روی آنها پرید. این تمرین از شما می خواهد که این شخصیت بازیگر را به بازی اضافه کنید.</p>
<p>ما آن را هیولا مینامیم. هیولاها به صورت افقی حرکت میکنند. میتوانید طوری آنها را بسازید که به طرف بازیکن حرکت کنند یا مثل گدازههای متحرک حرکت عقب و جلو داشته باشند یا هر الگوی حرکتی که شما دوست دارید. نیازی نیست افتادن را پیاده سازی کنید اما باید مطمئن شود که هیولا درون دیوار ها نرود.</p>
<p>زمانی که یک هیولا با یک بازیکن برخورد میکند بسته به اینکه بازیکن روی آن پریده باشد یا خیر واکنش متفاوت خواهد بود. میتوانیم این را با بررسی تطابق پایین بازیکن با بالای هیولا متوجه شویم. در این صورت هیولا باید ناپدید شود. در غیر این صورت بازیکن می بازد.</p>
<pre class="snippet cm-s-default" data-language="text/html" data-focus="true"><a class="c_ident" id="c_rthUoERAau" href="#c_rthUoERAau" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">link</span> <span class="cm-attribute">rel</span>=<span class="cm-string">"stylesheet"</span> <span class="cm-attribute">href</span>=<span class="cm-string">"css/game.css"</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">style</span><span class="cm-tag cm-bracket">></span><span class="cm-qualifier">.monster</span> { <span class="cm-property">background</span>: <span class="cm-keyword">purple</span> }<span class="cm-tag cm-bracket"></</span><span class="cm-tag">style</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">body</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-comment">// Complete the constructor, update, and collide methods</span>
<span class="cm-keyword">class</span> <span class="cm-def">Monster</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">pos</span>, <span class="cm-comment">/* ... */</span>) {}
<span class="cm-keyword">get</span> <span class="cm-property">type</span>() { <span class="cm-keyword">return</span> <span class="cm-string">"monster"</span>; }
<span class="cm-keyword">static</span> <span class="cm-property">create</span>(<span class="cm-def">pos</span>) {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Monster</span>(<span class="cm-variable-2">pos</span>.<span class="cm-property">plus</span>(<span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-number">0</span>, <span class="cm-operator">-</span><span class="cm-number">1</span>)));
}
<span class="cm-property">update</span>(<span class="cm-def">time</span>, <span class="cm-def">state</span>) {}
<span class="cm-property">collide</span>(<span class="cm-def">state</span>) {}
}
<span class="cm-variable">Monster</span>.<span class="cm-property">prototype</span>.<span class="cm-property">size</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">Vec</span>(<span class="cm-number">1.2</span>, <span class="cm-number">2</span>);
<span class="cm-variable">levelChars</span>[<span class="cm-string">"M"</span>] <span class="cm-operator">=</span> <span class="cm-variable">Monster</span>;
<span class="cm-variable">runLevel</span>(<span class="cm-keyword">new</span> <span class="cm-variable">Level</span>(<span class="cm-string-2">`</span>
<span class="cm-string-2">..................................</span>
<span class="cm-string-2">.################################.</span>
<span class="cm-string-2">.#..............................#.</span>
<span class="cm-string-2">.#..............................#.</span>
<span class="cm-string-2">.#..............................#.</span>
<span class="cm-string-2">.#...........................o..#.</span>
<span class="cm-string-2">.#..@...........................#.</span>
<span class="cm-string-2">.##########..............########.</span>
<span class="cm-string-2">..........#..o..o..o..o..#........</span>
<span class="cm-string-2">..........#...........M..#........</span>
<span class="cm-string-2">..........################........</span>
<span class="cm-string-2">..................................</span>
<span class="cm-string-2">`</span>), <span class="cm-variable">DOMDisplay</span>);
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">body</span><span class="cm-tag cm-bracket">></span></pre>
<div class="solution"><div class="solution-text">
<p>اگر قصد پیادهسازی حرکتی را دارید که دارای وضعیت داخلی میباشد، مانند حرکت رفت و برگشت به یک نقطه، اطمینان حاصل کنید که وضعیت مورد نیاز را در شیء بازیگر ذخیره کنید - به عنوان ورودی سازنده و یک خاصیت.</p>
<p>به یاد داشته باشید که <code>update</code> یک شیء جدید را برمیگرداند و شیء قبلی را تغییر نمیدهد.</p>
<p>زمانی که قسمت برخورد کردن اشیاء را پیادهسازی میکنید، بازیکن موجود در <bdo><code>state.actors</code></bdo> را پیدا کنید و موقعیت آن را با موقعیت هیولا مقایسه نمایید. برای بدست آوردن مختصات پایین بازیکن، باید اندازهی عمودی آن را به موقعیت عمودیش اضافه نمایید. بسته به موقعیت مکانی بازیکن، ایجاد یک وضعیت بهروز، موجب بروز برخورد مربوط به سکه (حذف آن) یا گدازه (تغییر وضعیت به <code>"lost"</code>) میشود.</p>
</div></div><nav><a href="15_event.html" title="previous chapter">◀</a> <a href="index.html" title="cover">◆</a> <a href="17_canvas.html" title="next chapter">▶</a></nav>
</article>