-
Notifications
You must be signed in to change notification settings - Fork 19
/
Chapter_II-9.tex
1219 lines (1022 loc) · 53.1 KB
/
Chapter_II-9.tex
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
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\chapter{برمجة لعبة الـ\textenglish{Pendu}}
أكرر دائما: التطبيق شيء ضروريّ. هو ضروريّ لك لأنك اكتشفت كثيرا من المفاهيم النظرية و، أيّا كان ما تقول، لن تفهمها حقّا بدون تطبيق.
في هذا العمل التطبيقي، أقترح عليك إنشاء لعبة الـ\textenglish{Pendu}.
و هي لعبة حروف تقليديّة يتمّ فيها تخمين كلمة سريّة حرفا بحرف. الـ\textenglish{Pendu}
سيكون إذن لعبة في الكونسول بلغة
\textenglish{C}.
الهدف هو جعلك تستخدم كلّ ما تعلّمته حتّى الآن: المؤشرات، السلاسل المحرفيّة، الملفات، الجداول\dots باختصار، الأشياء الجيّدة فقط!
\section{التعليمات}
سأقوم بشرح قواعد الـ\textenglish{Pendu}
الواجب إنشاءه. سأعطيك هنا التعليمات، أي سأشرح لك بدقّة كيف يجب أن تعمل اللعبة التي ستُنشئها.
أعتقد أن الجميع يعرف
الـ\textenglish{Pendu}،
أليس كذلك؟ هيّا، تذكير صغير لا يمكن أن يحدث ضررا: هدف الـ\textenglish{Pendu}
هو إيجاد الكلمة المخبّأة في أقلّ من عشر محاولات (يمكنك تغيير العدد الأقصى لتغيير صعوبة اللعبة، بالطبع!).
\subsection{سريان الجولة}
فلنفترض أن الكلمة المخبّأة هي \textenglish{RED}.\\
ستقوم باقتراح حرف على الحاسوب، مثلا الحرف
\textenglish{A}.
سيتأكّد الحاسوب ما إن كان هذا الحرف موجودًا في الكلمة المخفيّة.
\begin{information}
تذكّر: هناك دالة جاهزة في
\InlineCode{string.h}
تقوم بالبحث عن حرف في كلمة! وبالطبع أنت لست مجبرًا على استخدامها (شخصيّا، أنا لم أفعل).
\end{information}
انطلاقًا من هنا، يوجد احتمالان:
\begin{itemize}
\item الحرف موجود بالفعل في الكلمة: سنكشف مكان الحرف في الكلمة.
\item الحرف غير موجود في الكلمة (هذا هو الحال هنا، لأن
\textenglish{A}
ليس موجودًا في الكلمة
\textenglish{RED}):
سنخبر اللاعب بأن الحرف هذا غير موجود في الكلمة، وسننقص عدد المحاولات المتبقّية. عندما لا تتبق أية محاولة (0 محاولة)، ستنتهي اللعبة وسيخسر.
\end{itemize}
\begin{information}
في لعبة
\textenglish{Pendu}
"حقيقة"، يفترض وجود شخص يتأسّف في كلّ مرّه نخطئ فيها. في الكونسول، سيكون من الصعب كثيرا رسم شخص يتأسّف بواسطة لاشيء غير النص، لذا سنكتفي بعرض جملة بسيطة مثل "بقي لك
\textenglish{X}
محاولات قبل الموت الأكيد".
\end{information}
فلنفرض الآن أن اللاعب أدخل الحرف
\textenglish{D}.
هذا الحرف موجود في الكلمة المخفيّة، لهذا لن نقوم بإنقاص عدد المحاولات المتبقّية للاعب. سنقوم بإظهار الكلمة مع الحروف الّتي تم إيجادها، أي شيء كهذا:
\begin{Console}
Secret word : **D
\end{Console}
إذا أدخل اللاعب فيما بعد الحرف
\textenglish{R}،
و بما أنّه موجود في الكلمة، سنضيف الحرف إلى قائمة الحروف التي تم إيجادها ويتم إظهار الكلمة مع الحروف الّتي تمّ اكتشافها:
\begin{Console}
Secret word : R*D
\end{Console}
\subsubsection{حالة وجود حرف مكرر}
في بعض الكلمات، يمكن أن نجد حرفًا مكررًا مرتين أو ثلاث، أو ربّما أكثر!\\
مثلا: يوجد اثنان من
\textenglish{Z}
في كلمة
\textenglish{PUZZLE}،
و كذلك يوجد ثلاثة
\textenglish{E}
في كلمة
\textenglish{ELEMENT}.
ماذا علينا أن نفعل في حالة كهذه؟ قواعد
\textenglish{Pendu}
واضحة: إذا أدخل اللاعب الحرف
\textenglish{E}،
كلّ حروف
\textenglish{E}
في كلمة
\textenglish{ELEMENT}
يجب أن تظهر دفعة واحدة:
\begin{Console}
Secret word : E*E*E**
\end{Console}
يعني أنه ليس على اللاعب أن يدخل 3 مرات الحرف
\textenglish{E}
ليتم اكتشاف كل تكرار له في الكلمة.
\subsubsection{مثال عن جولة كاملة}
هذا ما ستبدو عليه جولة كاملة في الكونسول عند انتهاء البرنامج:
\begin{Console}
Welcome !
You have 10 remaining tries
What's the secret word ? ****
Suggest a letter : B
You have 9 remaining tries
What's the secret word ? ****
Suggest a letter : F
You have 9 remaining tries
What's the secret word ? F***
Suggest a letter : D
You have 9 remaining tries
What's the secret word ? F**D
Suggest a letter : O
You win ! The secret word is : FOOD
\end{Console}
\subsubsection{قراءة حرف من الكونسول}
قراءة حرف من الكونسول هي أكثر تعقيدًا ممّا تبدو.\\
بديهيّا، لاسترجاع محرف، يفترض أنّك تفكّر في:
\begin{Csource}
scanf("%c", &myLetter);
\end{Csource}
و تماما، هذا جيّد.
\InlineCode{\%c}
تعني أننا ننتظر محرفًا، والذي سنقوم بتخزينه في
\InlineCode{myLetter}
(متغيّر من نوع
\InlineCode{char}).
كل شيء يعمل جيدًا\dots ما دمنا لم نقم بـ\InlineCode{scanf}
مرّة أخرى. يمكنك تجريب الشفرة التالية:
\begin{Csource}
int main(int argc, char* argv[])
{
char myLetter = 0;
scanf("%c", &myLetter);
printf("%c", myLetter);
scanf("%c", &myLetter);
printf("%c", myLetter);
return 0;
}
\end{Csource}
يفترض بهذه الشفرة أن تطلب حرفًا وتظهره، وذلك لمرّتين.\\
جرّب. ما الذي يحصل؟ تدخل حرفا، نعم، ولكن\dots البرنامج يتوقّف مباشرة بعدها، فهو لا يطلب منك المحرف الثاني! وكأنه تم تجاهل
\InlineCode{scanf}
الثانية.
\begin{question}
ما الذي حصل؟
\end{question}
في الواقع، حينما تدخل نصًا في الكونسول، فإن كل ما قمت بإدخاله يتمّ تخزينه في الذاكرة، بما في ذلك الزر
\texttt{Enter}
(\InlineCode{\textbackslash n}).
لذلك، في أوّل مرّة تدخل فيها حرفا
(\textenglish{A}
مثلًا) ثمّ تضغط على
\textit{\textenglish{Enter}}
فإن الحرف
\textenglish{A}
هو من يتم إعادته من طرف
\InlineCode{scanf}.
بينما في المرّة الثانية،
\InlineCode{scanf}
سيعيد
\InlineCode{\textbackslash n}
الموافق لـ\textit{\textenglish{Enter}}
الّذي أدخلته سابقا!
لتجنب هذا، من الأحسن أن نكتب بأنفسنا دالتنا الخاصّة الصغيرة
\InlineCode{readCharacter()}:
\begin{Csource}
char readCharacter()
{
char character = 0;
character = getchar(); // Read the first character
character = toupper(character); // Convert the character to uppercase
// Read other characters until reaching \n (to erase them)
while (getchar() != '\n') ;
return character; // Return the first character that have been read
}
\end{Csource}
هذه الدالة تستخدم
\InlineCode{getchar()}
الّتي هي دالة من
\InlineCode{stdio.h}
و هذا يعود تمامًا إلى كتابة\\
\InlineCode{scanf("\%c", \&letter);}.
الدالة
\InlineCode{getchar()}
تقوم بإرجاع المحرف الذي قام اللاعب بإدخاله.
بعد ذلك، أستعمل أيضًا الدالة القياسيّة التي لم تسنح لنا فرصة تعلّمها في كتابنا:
\InlineCode{toupper()}.
هذه الدالّة تحوّل الحرف المعطى إلى كبير
(\textenglish{Uppercase}).
هكّذا، اللعبة ستعمل حتى إن أدخل اللاعب حروفًا صغيرة. يجب تضمين
\InlineCode{ctype.h}
لتستطيع استخدام هذه الدالة (لا تنس ذلك!).
تأتي بعد ذلك المرحلة الأكثر أهمية: وهي أن نقوم بمسح المحارف التي يمكن أن نكون قد أدخلناها. في الواقع، بإعادة استدعاء
\InlineCode{getchar}
نحصل على المحرف الثاني الّذي تمّ إدخاله (مثلا
\InlineCode{\textbackslash n}).\\
ما أقوم به بسيط ويأخذ سطرا واحدا: أستدعي الدالة
\InlineCode{getchar}
في حلقة تكرارية حتى الوصول إلى
\InlineCode{\textbackslash n}.
تتوقف الحلقة إذن، وهذا يعني أننا "قرأنا" كلّ المحارف الأخرى، سيتمّ إذن إفراغها من الذاكرة. نقول أنّنا
\textbf{نفرغ المتغير المؤقت
(\textenglish{Buffer})}.
\begin{question}
لماذا توجد فاصلة منقوطة في نهاية الـ\InlineCode{while}
و لماذا لا نرى أية حاضنة؟
\end{question}
في الواقع، استعملت حلقة تكرارية لا تحتوي على تعليمات (التعليمة الوحيدة، هي
\InlineCode{getchar}
داخل القوسين). الحاضنتان ليستا ضروريّتين نظرا لأنه ليس لدينا ما نفعله غير
\InlineCode{getchar}.
لهذا أضع فاصلة منقوطة لتعويض الحاضنتين. هذه الفاصلة المنقوطة تعني "لا تفعل شيئًا في كلّ دورة للحلقة". هذا أمر غريب قليلا، لكنها تقنيّة يجب معرفتها، تقنيّة يستعملها المبرمجون لإنشاء حلقات بسيطة وقصيرة.
اعلم أنّ الـ\InlineCode{while}
كان بالإمكان كتابتها هكذا:
\begin{Csource}
while (getchar() != '\n')
{
}
\end{Csource}
لا يوجد شيء داخل الحاضنتين، إنّها اختياريّة، نظرا لأنّه ليس هناك شيء آخر لفعله. تقنيّتي الّتي تقتضي وضع فاصلة منقوطة فقط أبسط من تلك الخاصّة بالحاضنتين.
أخيرا، تقوم الدالة
\InlineCode{readCharacter}
بإرجاع المحرف الأوّل الذي قمنا بقراءته: المتغيّر
\InlineCode{character}.
خلاصة القول، في شفرتك، لا تستعمل:
\begin{Csource}
scanf("%c", &myLetter);
\end{Csource}
و إنما استعمل بدل ذلك دالّتنا الرائعة:
\begin{Csource}
myLetter = readCharacter();
\end{Csource}
\subsection{قاموس الكلمات}
لتجربة أولية للشفرة الخاصة بك، أطلب منك أن تقوم بتثبيت الكلمة السريّة مباشرة في الشفرة. أكتب مثلا:
\begin{Csource}
char secretWord[] = "RED";
\end{Csource}
طبعا ستبقى الكلمة السريّة نفسها دائما إن تركناها هكذا، هذا ليس ممتعا. لكني طلبت منك فعل ذلك لكي لا تخلط المشاكل. في الواقع، عندما تعمل لعبة
\textenglish{Pendu}
جيّدا (وفقط ابتداء من هذه اللحظة)، يمكنك البدء بالطور الثاني: إنشاء قاموس الكلمات.
\begin{question}
ما هو هذا "قاموس الكلمات"؟
\end{question}
هو ملف يحتوي كثيرا من الكلمات للعبتك
\textenglish{Pendu}.
يجب أن تكون كل كلمة على سطر. مثلا:
\begin{Console}
HOUSE
BLUE
AIRPLANE
XYLOPHONE
BEE
BUILDING
WEIGHT
SNOW
ZERO
\end{Console}
في كل جولة جديدة، يجب على برنامجك أن يفتح الملف، ويأخذ كلمة عشوائية من القائمة. بفضل هذه الطريقة، سيكون لديك ملف يمكنك التعديل عليه كلّما أردت من أجل إضافة كلمات سريّة ممكنة من أجل
\textenglish{Pendu}.
\begin{information}
ستلاحظ أنني منذ البداية تعمّدت كتابة كلّ الكلمات بالحروف الكبيرة. في الواقع، في الـ\textenglish{Pendu}
لا يتم التمييز بين الحروف الكبيرة والحروف الصغيرة، ولهذا فمن المستحسن أن نقول منذ البداية: "كل حروف كلمات اللعبة كبيرة". عليك أن تنبّه اللاعب، في دليل استخدام اللعبة مثلا، أنه يفترض به إدخال حروف كبيرة لا صغيرة.\\
بالمقابل، نتعمّد تجنب العلامات الصوتية
(\textenglish{accents})
لتبسيط اللعبة (إن بدأنا اختبار \textenglish{é}، \textenglish{è}، \textenglish{ê}، \textenglish{ë}\dots فلن ننتهي أبدًا!). عليك إذن أن تكتب كلماتك كلّها بحروف كبيرة وبدون علامات صوتيّة.
\end{information}
المشكل الذي سيحدث لك سريعا هو أنه عليك معرفة عدد الكلمات الموجودة في القاموس. في الواقع، إن أردت اختيار كلمة عشوائية، يجب أن يتم أخذ عدد بين 0 و
\textenglish{X}،
و أنت لا تعرف في بادئ الأمر كم من الكلمات يحتوي الملف.
لحلّ هذا المشكل، يوجد حلّان. يمكنك أن تشير في السطر الأول من الملفّ إلى عدد الكلمات الّتي يحويها:
\begin{Console}
3
HOUSE
BLUE
AIRPLANE
\end{Console}
إلا أن هذه الطريقة مملة، لأنه يجب إعادة حساب عدد الكلمات يدويا في كلّ مرّة تضيف فيها كلمة (أو إضافة 1 إلى هذا العدد إن كنت ماكرا بدل إعادة الحساب، لكنّها تبقى طريقة بدائيّة قليلا). لهذا، أقترح عليك أن تعدّ تلقائيّا عدد الكلمات عن طريق قراءة الملف مرّة أولى باستخدام برنامجك. معرفة كم يوجد من كلمات أمر بسيط: عليك عدّ الـ\InlineCode{\textbackslash n}
(العودة إلى السطر) في الملف.
حينما تقرأ الملفّ في مرّة أولى لعدّ
\InlineCode{\textbackslash n}،
فعليك القيام بـ\InlineCode{rewind}
للعودة إلى البداية. لن يكون عليك إذن سوى أخذ عدد عشوائيّ بين عدد الكلمات الّتي عددتها، ثمّ عليك تخزين هذه الكلمة في سلسلة محرفيّة في الذاكرة.
سأتركك قليلا لتفكّر في كلّ هذا، لن أساعدك أكثر، وإلّا فلن يكون عملا تطبيقيا! واعلم بأن كلّ المعارف الّتي تحتاجها موجودة في الفصول السابقة، فأنت قادر تماما على إنشاء هذه اللعبة. إنه يتطلّب منك بعض الوقت وهو أقلّ سهولة ممّا يبدو عليه، ولكن إذا نظّمت الأمور جيّدا (بإنشاء قدر كاف من الدوال) سوف تصل.
بالتوفيق!
\section{التصحيح (1: شفرة اللعبة)}
بقراءتك لهذه السطور، يعني أنك قد أكملت البرنامج، أو أنك لم تستطع إكماله.
لقد استغرقت شخصيّا وقتا أكبر ممّا كنت أعتقد في إنشاء هذه اللعبة البسيطة للغاية. هكذا دائما: نقول "هذا بسيط"، لكن في الحقيقة توجد الكثير من الحالات لدراستها.
رغم ذلك أصرّ على القول بأنك قادر على فعل هذا. يلزمك فقط بعض الوقت (بضع دقائق، بضع ساعات بضع أيام؟)، لكنّنا لم نكن أبدا في سباق. أنا أفضّل أن تأخذ كثيرا من الوقت للوصول إلى الحل على ألّا تجرّب سوى 5 دقائق وترى التصحيح.
لا تعتقد أنّي كتبت البرنامج من المحاولة الأولى. أنا أيضا، كنت أعمل خطوة بخطوة. بدأت بشيء بسيط جدّا، ثمّ شيئا فشيئا حسّنت الشفرة للوصول إلى النتيجة النهائيّة.\\
قمت بعدّة أخطاء أثناء كتابة الشفرة: نسيت في لحظة ما تهيئة متغير بشكل صحيح، نسيت كتابة نموذج دالة وكذلك حذف متغير لم يعد مفيدا في شفرتي. وحتى أنّي -أعترف- نسيت فاصلة منقوطة سخيفة في لحظة ما عند نهاية تعليمة.
لماذا أقول كل هذا؟ لكي أخبرك أنّني لست معصوما من الأخطاء وأنّي أواجه تقريبا نفس المشاكل مثلك
("\textit{أيّها البرنامج البائس، هل ستعمل أم لا؟!}").
سأعرض عليك الحلّ على جزأين.
\begin{itemize}
\item أوّلا سأريك كيف أنشأت شفرة اللعبة نفسها، بتثبيت الكلمة المخفيّة مباشرة في الشفرة. اخترت الكلمة
\textenglish{YELLOW}
لأنّها تسمح باختبار ما إن كنت تعاملت جيّدا مع المحارف المتكرّرة.
\item بعد ذلك، سأريك كيف أضفت العمل بقاموس الكلمات لإعطاء كلمة سرّية عشوائيّة لللاعب.
\end{itemize}
بالطبع، يمكنني أن أريك الشفرة دفعة واحدة ولكن\dots سيكون هذا كثيرا في مرّة واحدة، والبعض لن تكون لديه الشجاعة لمحاولة فهم الشفرة.
سأحاول أن أشرح لك خطوة بخطوة طريقة عملي. تذكّر أنّ ما يهم، ليس النتيجة، وإنّما طريقة التفكير.
\subsection{تحليل الدالة \texttt{main}}
مثلما يعلم الجميع، كلّ شيء يبدأ بـ\InlineCode{main}.
بجب ألا ننسى تضمين المكتبات
\InlineCode{stdio}،
\InlineCode{stdlib}
و
\InlineCode{ctype}
(من أجل الدالة
\InlineCode{toupper})
الّتي سنحتاج إليها أيضا:
\begin{Csource}
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main(int argc, char* argv[])
{
return 0;
}
\end{Csource}
حسنا، لحدّ الآن يجب على الجميع أن يتابعوا.\\
الدالة
\InlineCode{main}
ستشكّل معظم اللعبة وستقوم باستدعاء بعض الدوال حينما تحتاج إليها.
فلنبدأ بتعريف المتغيرات الضروريّة. كن متأكّدا، لم أفكّر في كلّ هذه المتغيرات من الوهلة الأولى، ولقد كان هناك أقلّ من هذا العدد في أوّل مرّة كتبت فيها الشفرة!
\begin{Csource}
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main(int argc, char* argv[])
{
char letter = 0; // Stores the letter suggested by the user
char secretWord[] = "YELLOW"; // The word that the user must find
int foundLetter[6] = {0}; // Boolean table. Each cell corresponds to a letter in the secret word. 0 = letter not found, 1 = letter found
int remainingTries = 10; // Counting the remaining tries (0 = dead)
int i = 0; // A little variable to browse the table
return 0;
}
\end{Csource}
لقد كتبت بمحض إرادتي تصريح كلّ متغير على سطر ووضعت كثيرا من التعليقات لشرح دور كل متغير. عمليّا، لست مضطرّا إلى وضع كلّ هذه التعليقات كما يمكنك وضع الكثير من التصريحات في نفس السطر.
أعتقد أن أغلب المتغيرات تبدوا منطقية: المتغير
\InlineCode{letter}
يخزّن الحرف الذي يدخله المستخدم في كلّ مرة،
\InlineCode{secretWord}
يحوي الكلمة الواجب اكتشافها.
\InlineCode{remainingTries}
يحتوي عدد المحاولات المتبقّية، إلخ. المتغيّر
\InlineCode{i}
هو متغير صغير استعمله كي أتصفّح الجدول مستعملا الحلقة
\InlineCode{for}.
فهو ليس مهمّا جدّا لكنّه ضروريّ إذا أردنا القيام بحلقات.
و أخيرًا المتغير الّذي يجب التفكير فيه، والذي سيُمثّل الفرق، إنّه عبارة عن جدول من القيم المنطقية
\InlineCode{foundLetter}.
ستلاحظ بأنّي جعلت حجم الجدول يساوي عدد حروف الكلمة السريّة (6). هذا ليس أمرًا عشوائيًا: إذ أن كلّ خانة من جدول القيم المنطقية تمثّل حرفًا من الكلمة السرية. هكذا، الخانة الأولى تمثّل الحرف الأوّل، الثانية الحرف الثاني، إلخ.\\
كلّ خانات الجدول مهيّئة في البداية على 0، والتي تعني "الحرف لم يتم إيجاده بعد". بتقدّم اللعبة، الجدول سيتمّ تعديله. من أجل كلّ حرف تمّ إيجاده من الكلمة، الخانة التي توافقها من
\InlineCode{foundLetter}
ستأخذ 1.
مثلا، إذا كان في مرحلة من الجولة، لدينا العرض
"\textenglish{Y*LL*W}"،
فإن جدول الـ\InlineCode{int}
سيحوي القيم: 101101 (1 لكل حرف تمّ إيجاده).\\
هذه الطريقة تسهّل علينا معرفة متى يربح اللاعب: يكفي التحقّق من أن جميع خانات الجدول لا تحوي سوى 1.\\
في الحالة الأخرى، سيخسر اللاعب إذا وصل العدّاد
\InlineCode{remainingTries}
إلى 0.
فلننتقل إلى التالي:
\begin{Csource}
printf("Welcome !\n\n");
\end{Csource}
هذه رسالة ترحيب، لا يوجد أي شيء مثير فيها. بالمقابل، الحلقة الرئيسيّة هي الأكثر أهميّة:
\begin{Csource}
while (remainingTries > 0 && !win(foundLetter))
{
\end{Csource}
اللعبة تستمرّ مادام قد بقي بعض المحاولات
(\InlineCode{remainingTries > 0})
و اللاعب لم يربح.\\
إذا لم تبق له أية محاولة، فهذا يعني أنه فشل. إن ربح، فهذا يعني\dots أنّه ربح. في كلتا الحالتين، يجب إيقاف اللعبة، أي إيقاف الحلقة التي تطلب قراءة حرف في كلّ مرة.
\InlineCode{win}
هي دالة تقوم بتحليل الجدول
\InlineCode{foundLetter}.
تقوم بإعادة "صحيح" (1) إذا كان اللاعب قد ربح (أي أن الجدول
\InlineCode{foundLetter}
لا يحمل سوى 1)، "خطأ" (0) إن كان لم يربح بعد.
لن أشرح لك الآن عمل الدالة بشكل مفصل، سنرى ذلك لاحقًا. حاليّا، يجب عليك فقط معرفة ما تفعله.
باقي الشفرة:
\begin{Csource}
printf("\n\nYou have %d remaining tries", remainingTries);
printf("\nWhat's the secret word ? ");
for (i = 0 ; i < 6 ; i++)
{
if (foundLetter[i]) // If the letter n° i has been found
printf("%c", secretWord[i]); // Display it
else
printf("*"); // Else, display * for the letters that are not found
}
\end{Csource}
نقوم في كل مرة بإظهار عدد المحاولات المتبقّية وكذا الكلمة السريّة (مخفيّة بـ* بالنسبة للحروف التي لم يتمّ إيجادها).\\
يتم إظهار الكلمة السريّة المخفيّة بـ* بفضل حلقة
\InlineCode{for}
حيث أننا نحلّل كلّ حرف لنرى إن تمّ إيجاده
(\InlineCode{if(foundLetter[i])}).
إن كان الشرط محققًا، سنظهر الحرف، وإلا سنظهر * لإخفاءه.
الآن بعدما أظهرنا ما يجب، سنطلب من اللاعب أن يدخل حرفًا جديدًا:
\begin{Csource}
printf("\nSuggest a letter : ");
letter = readCharacter();
\end{Csource}
أستدعي دالتنا
\InlineCode{readCharacter()}.
هذه الدالة تقرأ الحرف الأول الذي تمّ إدخاله، تجعله كبيرًا ثم تفرّغ المتغير المؤقّت، أي أنّها تمسح بقيّة الحروف التي يمكن أن تبقى في الذاكرة.
\begin{Csource}
// if it's NOT the right letter
if (!findLetter(letter, secretWord, foundLetter))
{
remainingTries--; // Decrement the remaining tries
}
}
\end{Csource}
نختبر ما إن كان الحرف الذي تمّ إدخاله موجودا في
\InlineCode{secretWord}.
نستدعي لأجل هذا دالّة أنشأناها تسمّى
\InlineCode{findLetter}.
سنرى بعد قليل شفرة هذه الدالّة.\\
حاليّا، كلّ ما يجب أن تعرفه، هو أنّ هذه الدالّة تعيد "صحيح" إن كان الحرف موجودا في الكلمة، "خطأ" إن لم تجده.
كما تلاحظ فالـ\InlineCode{if}
يبدأ بعلامة تعجّب
\InlineCode{!}
و التي تعني "لا". الشرط يُقرأ إذن بهذه الطريقة: "إذا لم يتم إيجاد الحرف".\\
ماذا نفعل في حالة عدم إيجاد الحرف؟ نقوم بتقليل عدد المحاولات المتبقية.
\begin{information}
لاحظ أيضًا أن الدالة
\InlineCode{findLetter}
تقوم بتحديث قيم الجدول
\InlineCode{foundLetter}.
تقوم بوضع 1 في الخانات الموافقة للحروف التي تمّ إيجادها.
\end{information}
الحلقة الرئيسية في اللعبة تتوقف هنا. لهذا فسنعيد من بداية الحلقة ونختبر ما إن كان قد بقي شيء من المحاولات للعب واللاعب لم بربح بعد.
عند الخروج من الحلقة الرئيسيّة، لا يبقى سوى إظهار إن كان اللاعب قد نجح في اللعبة أو خسر قبل إنهاء البرنامج:
\begin{Csource}
if (win(foundLetter))
printf("\n\nYou win ! the secret word is : %s", secretWord );
else
printf("\n\nYou lose ! the secret word is : %s", secretWord );
return 0;
}
\end{Csource}
سنستدعي الدالة
\InlineCode{win}
لنرى ما إن كان اللاعب قد ربح. إن كانت هذه هي الحالة، نقوم بإظهار الرسالة "ربح!"، وإلّا، فقد انتهت فرص اللعب، فقد خسر.
\subsection{تحليل الدالة \texttt{win}}
فلنرى الآن الشفرة الخاصة بالدالة
\InlineCode{win}:
\begin{Csource}
int win(int foundLetter[])
{
int i = 0;
int playerWins = 1;
for (i = 0 ; i < 6 ; i++)
{
if (foundLetter[i] == 0)
playerWins = 0;
}
return playerWins;
}
\end{Csource}
هذه الدالة تأخذ جدول القيم المنطقيّة
\InlineCode{foundLetter}
كمعامل. تعيد قيمة منطقية: "صحيح" إذا ربح اللاعب و"خطأ" إذا خسِر.
الشفرة الخاصة بهذه الدالة بسيطة، يفترض بك فهمها. نتصفّح
\InlineCode{foundLetter}
و نختبر ما إن كانت
\underline{إحدى}
خانات الجدول تحوي "خطأ" (0). إن كان هناك حرف واحد لم يتمّ إيجاده فلقد خسر اللاعب: سيتم وضع "خطأ" (0) في المتغير المنطقي
\InlineCode{playerWins}.
و إلّا، إن تمّ إيجاد كل الحروف، فالمتغيّر المنطقي سيكون "صحيحا" (1) والدالة تعيد "صحيح".
\subsection{تحليل الدالة \texttt{findLetter}}
لهذه الدالة مهمّتان:
\begin{itemize}
\item إرجاع متغير منطقي يشير ما إن كان الحرف موجودًا في الكلمة السرية.
\item تحديث (على 1) خانات الجدول
\InlineCode{foundLetter}
في المواضع الموافقة للحرف الذي تمّ إيجاده.
\end{itemize}
\begin{Csource}
int findLetter(char letter, char secretWord[], int foundLetter[])
{
int i = 0;
int rightLetter = 0;
// Search for the letter in the table foundLetter
for (i = 0 ; secretWord[i] != '\0' ; i++)
{
if (letter == secretWord[i]) // If it exists
{
rightLetter = 1; // Memorize that it was the right one
foundLetter[i] = 1; // Put the correspondent value to 1 in the table
}
}
return rightLetter ;
}
\end{Csource}
نتصفّح إذن السلسلة المحرفيّة
\InlineCode{secretWord}
محرفًا محرفًا. في كلّ مرّة، نختبر ما إن كان الحرف الذي اقترحه اللاعب حرفا من الكلمة، سيتم القيام بأمرين:
\begin{itemize}
\item تعديل المتغير المنطقي
\InlineCode{rightLetter}،
إلى 1، لكي تعيد الدالّة 1 لأن الحرف متواجد بالفعل في
\InlineCode{secretWord}.
\item تحديث الجدول
\InlineCode{foundLetter}
على الموضع الحالي للإشارة إلى أنّ هذا الحرف قد تمّ إيجاده.
\end{itemize}
الشيء الجيّد في هذه الطريقة، هو أننا سنتصفّح كلّ الجدول (لا نتوقف عند أول حرف تم إيجاده). هذا سيسمح لنا بتحديث الجدول
\InlineCode{foundLetter}
بشكل صحيح، في الحالة التي تحتوي فيها الكلمة حرفًا مكررًا عدّة مرّات، مثل حالة
\textenglish{L}
في
\textenglish{YELLOW}.
\section{التصحيح (2: استعمال قاموس الكلمات)}
لقد قمنا بجولة حول الوظائف الأساسيّة لبرنامجنا. إنّه يحتوي على كلّ ماهو ضروري لإدارة جولة في اللعبة، لكنه لا يعرف كيف يختار كلمة عشوائية من قاموس كلمات. لم أضع لك شفرة المرحلة الأولى كلّها لأنها كانت ستأخذ حجمًا كبيرًا كما ستكون تكرارا مع الشفرة المصدريّة النهائيّة الّتي ستراها فيما بعد.
قبل الذهاب بعيدا، الشيء الأوّل الواجب فعله هو إنشاء قاموس الكلمات. وحتى إن كان قصيرًا فهذا ليس سيّئا، سيكون مناسبا للاختبارات.
سأقوم إذن بإنشاء ملف
\InlineCode{dico.txt}
\textbf{في نفس دليل مشروعي}.
حاليّا، سأضع فيه الكلمات التالية:
\begin{Csource}
HOUSE
BLUE
AIRPLANE
XYLOPHONE
BEE
BUILDING
WEIGHT
SNOW
ZERO
\end{Csource}
ما إن أنتهي من كتابة البرنامج، سأعود بالطبع إلى هذا القاموس وأملؤه بالكثير من الكلمات الغريبة كـ\textenglish{XYLOPHONE}
و المطوّلة كـ\textenglish{ANTIDISESTABLISHMENTARIANISM}.
لكن حاليّا، لنعد إلى كتابة التعليمات.
\subsection{تحضير الملفات الجديدة}
قراءة "القاموس"
ستأخذ الكثير من الأسطر (على الأقل، لديّ إحساس قبليّ بذلك). لهذا فسآخذ الاحتياطات بإضافة ملف آخر إلى مشروعي
\InlineCode{dico.c}
(الذي سيتكفّل بقراءة القاموس).
كما سنَقُوم بإنشاء
\InlineCode{dico.h}
الّذي يحوي نماذج الدوال الموجودة في
\InlineCode{dico.c}.
في
\InlineCode{dico.c}
سأبدأ بتضمين المكتبات الّتي أنا في حاجة إليها بالإضافة إلى
\InlineCode{dico.h}.
أوّلا، كالعادة، سأحتاج إلى
\InlineCode{stdio.h}
و
\InlineCode{stdlib.h}
هنا. بالإضافة إلى هذا، يجب عليّ أن أقوم بسحب عشوائي لعدد من القاموس، سأقوم إذن بتضمين
\InlineCode{time.h}
مثلما فعلنا سابقًا من أجل العمل التطبيقي الأول "أكثر أو أقل".سأحتاج أيضا إلى تضمين
\InlineCode{string.h}
من أجل استعمال
\InlineCode{strlen}
في نهاية الدالّة.
\begin{Csource}
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "dico.h"
\end{Csource}
\subsection{الدالة \texttt{findWord}}
هذه الدالة تأخذ معاملا واحدا: مؤشرًا نحو الذاكرة حيث يمكن كتابة الكلمة. هذا المؤشّر يتم تزويدنا به عن طريق
\InlineCode{main}.\\
الدالة ستعيد
\InlineCode{int}
و سيكون قيمة منطقية: 1 = تمّ كلّ شيء على ما يرام، 0 = كان هناك خطأ ما.
هذه بداية الدالة:
\begin{Csource}
int findWord(char *chosenWord)
{
FILE* dico = NULL; // The pointer of the file
int wordsNumber = 0, chosenWordNumber = 0, i = 0;
int readCharacter = 0;
\end{Csource}
أعرّف بعض المتغيرات الّتي ستكون ضرورية لي. مثل \InlineCode{main}،
لم يخطر ببالي وضعها كلّها من البداية، يوجد بالتأكيد من قمت بإضافتها لاحقا حينما عرفت أنني بحاجة إليها.
أسماء الكلمات تعبّر عن نفسها. لدينا المؤشّر على القاموس
\InlineCode{dico}
و الذي سيمكننا من قراءة
\InlineCode{dico.txt}،
متغيرات مؤقّتة ستخزّن المحارف، إلخ.
لاحظ أنني هنا استعملت
\InlineCode{int}
لتخزين محرف
(\InlineCode{characterRead})
لأنّ الدالة
\InlineCode{fgetc}
الّتي سأستخدمها تعيد
\InlineCode{int}.
فمن الأفضل إذن تخزين النتيجة في
\InlineCode{int}.
فلنمر إلى التالي:
\begin{Csource}
dico = fopen("dico.txt", "r"); // Open the dictionary in read mode only
// Check if it's open without a problem
if (dico == NULL) // If there's a problem
{
printf("\nImpossible to load words dictionary");
return 0; // Return a zero to say that the function failed
// The function stops after reading the instruction return
}
\end{Csource}
ليس لديّ الكثير لأضيفه هنا. أفتح الملف
\InlineCode{dico.txt}
بوضع قراءة فقط
(\InlineCode{"r"})
و أتأكّد إن نجحت عن طريق اختبار إذا كان
\InlineCode{dico}
يحمل القيمة
\InlineCode{NULL}
فإن عملية فتح الملف قد فشلت (ملفّ غير موجود أو مفتوح من طرف برنامج آخر). في هذه الحالة سنظهر رسالة خطأ ونقوم بـ\InlineCode{return 0}.
لماذا تضع
\InlineCode{return}
هنا؟ في الحقيقة، التعليمة
\InlineCode{return}
تضع نهاية للدالة. إذا لم يتم فتح القاموس، فستتوقف الدالة ولن يذهب الحاسوب إلى أبعد من ذلك. إعادة 0 تشير لـ\InlineCode{main}
أنّ الدالّة قد فشلت.
في ما يلي من الدالّة نفترض أن فتح الملف نجح.
\begin{Csource}
// Count the number of words in the file (Just counting the \n signs)
do
{
characterRead = fgetc(dico);
if (characterRead == '\n')
wordsNumber++;
} while(characterRead != EOF);
\end{Csource}
هنا، نتصفّح كلّ الملف دفعة واحدة باستعمال
\InlineCode{fgetc}
(محرفا بمحرف). نعدّ الـ\InlineCode{\textbackslash n}
التي نجدها. أي أنه في كلّ مرة نلتقي بـ\InlineCode{\textbackslash n}
نزيد قيمة المتغير
\InlineCode{wordsNumber}.\\
بفضل هذه الشفرة سنتحصل في المتغير
\InlineCode{wordsNumber}
على عدد الكلمات الموجودة في الملف. تذكّر بأن الملف يحتوي على كلمة في كل سطر.
\begin{Csource}
chosenWordNumber = aleatoryNumber(wordsNumber); // Take a word by hazard
\end{Csource}
هنا أستدعي دالّة من إنشائي تختار لي عددا عشوائيا بين 1 و\InlineCode{wordsNumber}
(المعامل الذي ترسله للدالة).\\
إنها دالة بسيطة وضعتها أيضًا في الملف
\InlineCode{dico.c}
(سأشرحها بشكل موسّع لاحقًا). باختصار، تقوم بإرجاع عدد (يوافق رقم سطر الكلمة في الملف) عشوائيّ يتم تخزينه في
\InlineCode{chosenWordNumber}.
\begin{Csource}
// Start reading the file from the beginning and stop when finding the right word
rewind(dico);
while (chosenWordNumber > 0)
{
characterRead = fgetc(dico);
if (characterRead == '\n')
chosenWordNumber--;
}
\end{Csource}
و الآن ونحن نملك رقم الكلمة التي سنختارها، سنعود إلى بداية الملف باستدعاء
\InlineCode{rewind()}،
و سنتصفّح الملف محرفا بمحرف لنحسب عدد
\InlineCode{\textbackslash n}.
هذه المرّة، سنقوم بإنقاص قيمة
\InlineCode{chosenWordNumber}.
إن اخترنا مثلا الكلمة رقم 5، في كلّ إدخال سيتمّ إنقاص المتغير
\InlineCode{chosenWordNumber}
بواحد.\\
سيأخذ إذن القيم 4 ثم 3 ثم 2 ثم 1 ثم 0. \\
عندما يصل المتغير إلى 0، نخرج من الـ\InlineCode{while}،
لأن الشرط
\InlineCode{chosenWordNumber > 0}
لم يعد محققا.
هذا الجزء من الشفرة، والذي يجب عليك فهمه حتما، سيريك كيفية تصفّح الملف للوصول إلى المكان المراد. الأمر ليس معقّدا ولكنه ليس "بديهيا" أيضًا. كن متأكّدا من فهم ما أقوم بفعله هنا.
الآن، يفترض أن نملك مؤشّرا مُمَوضعا تمامًا قبل الكلمة السريّة الّتي يجب إيجادها.
سنقوم بتخزينها في
\InlineCode{chosenWordNumber}
(المعامل الذي تستقبله الدالة) بفضل
\InlineCode{fgets}
بسيط يقوم بقراءة الكلمة:
\begin{Csource}
/* The cursor of the file is placed in the best place.
Nothing is needed more than an fgets that will read the line */
fgets(chosenWord, 100, dico);
// We erase the \n at the end of the word
chosenWord[strlen(chosenWord) - 1] = '\0';
\end{Csource}
نحن نطلب من
\InlineCode{fgets}
ألا تقرأ أكثر من 100 محرف (هذا هو حجم الجدول
\InlineCode{chosenWord}،
الذي قمنا بتعريفه في \InlineCode{main}).
تذكّر أنّ
\InlineCode{fgets}
تقرأ سطرا كاملا، بما في ذلك
\InlineCode{\textbackslash n}.
بما أننا لا نريد إبقاء الـ\InlineCode{\textbackslash n}
في الكلمة النهائية، نحذفها باستبدالها بـ\InlineCode{\textbackslash 0}.
لهذا تأثير القيام بقطع الكلمة قبل
\InlineCode{\textbackslash n}.
و ها نحن ذا! لقد خزّنا الكلمة السرية في الذاكرة عند عنوان
\InlineCode{chosenWord}.
لم يتبقّ سوى غلق الملف، وإعادة القيمة 1 لتتوقف الدالة وتشير إلى أن كل شيء على ما يُرام:
\begin{Csource}
fclose(dico);
return 1; // Everything is okay, return 1
}
\end{Csource}
انتهينا من الدالة
\InlineCode{findWord} !
\subsection{الدالة
\texttt{aleatoryNumber}}
هذه الدالة التي وعدتك بشرحها سابقًا. نختار عددًا عشوائيا ونعيده:
\begin{Csource}
int aleatoryNumber(int maxNumber)
{
srand(time(NULL));
return (rand() % maxNumber);
}
\end{Csource}
السطر الأوّل يهيّئ مولّد القيم العشوائية، مثلما تعلّمنا فعل ذلك في العمل التطبيقي الأوّل "أكثر أو أقل". أما السطر الثاني فيقوم باختيار عدد عشوائي بين $ 0 $
و
\InlineCode{maxNumber}
و يعيده. يمكنك ملاحظة أنني قمت بكلّ ذلك في سطر واحد، هذا ممكن بكلّ تأكيد، رغم أنّه قد يبدو أحيانا أقل قابليّة للقراءة.
\subsection{الملف \texttt{dico.h}}
يحتوي نماذج الدوال فقط. يمكنك أن تلاحظ "الحماية" التي تقدّمها
\InlineCode{\#ifndef}
التي كنت قد طلبت منك تضمينها في كلّ ملفاتك ذات الامتداد
\InlineCode{.h}
(راجع فصل توجيهات المعالج في حالة الحاجة):
\begin{Csource}
#ifndef DEF_DICO
#define DEF_DICO
int findWord(char *chosenWord);
int aleatoryNumber(int maxNumber);
#endif
\end{Csource}
\subsection{الملف \texttt{dico.c}}
هذا هو الملف
\InlineCode{dico.c}
كاملا:
\begin{Csource}
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "dico.h"
int findWord(char *chosenWord)
{
FILE* dico = NULL; // The pointer on the file
int wordsNumber = 0, chosenWordNumber = 0, i = 0;
int characterRead = 0;
dico = fopen("dico.txt", "r"); // Open the dictionary in read mode only
// Check if it's open without a problem
if (dico == NULL) // If there's a problem
{
printf("\nImpossible to load words dictionary");
return 0; // Return a zero to say that the function failed
// The function stops after reading the instruction return
}
// Count the number of words in the file (just count the \n characters)
do
{
characterRead = fgetc(dico);
if (characterRead == '\n')
wordsNumber++;
}
chosenWordNumber = aleatoryNumber(wordsNumber); // Take a word by hazard
// Start reading the file from the beginning and stop after finding the right word
rewind(dico);
while (chosenWordNumber > 0)
{
characterRead = fgetc(dico);
if (characterRead == '\n')
chosenWordNumber--;
}
fgets(chosenWord, 100, dico);
// Erase the \n at the end of the word
chosenWord[strlen(chosenWord) - 1] = '\0';
fclose(dico);
return 1; // Everything is okay, return 1
}
int aleatoryNumber(int maxNumber)
{
srand(time(NULL));
return (rand() % maxNumber);
}
\end{Csource}
\subsection{يجب إذن تعديل الـ\texttt{main}
!}
و الآن بما أن الملف
\InlineCode{dico.c}
جاهز، سنعود للدالة
\InlineCode{main}
كي نقوم بتحديثها على حسب التغييرات التي قمنا بإجرائها.
سنبدأ أوّلا بتضمين
\InlineCode{dico.h}
إذا أردنا استدعاء دوال الملف
\InlineCode{dico.c}.
بالإضافة إلى ذلك، سنقوم أيضًا بتضمين
\InlineCode{string.h}
لأننا سنستعمل الدالة
\InlineCode{strlen}:
\begin{Csource}
#include <string.h>
#include "dico.h"
\end{Csource}
للبدأ، سيتم تغيير كيفية تعريف المتغيرات، فنحن مثلًا لن نهيّئ قيمة المتغير
\InlineCode{secretWord}،
سننشئ فقط جدول محارف من
\InlineCode{char}
(100 خانة).
بالنسبة للجدول
\InlineCode{foundLetter}
فحجمه سيعتمد على طول الكلمة التي سنختارها من القاموس، وبما أننا لازلنا لا نعرف هذا الطول، سنكتفي بتعريف مؤشّر. لاحقًا سنستعمل الدالة
\InlineCode{malloc}
و جعل هذا المؤشّر يـُؤشّر على الخانة التي سيتم حجزها.\\
و هذا مثال يعبّر تماما عن حاجتنا الماسة لاستعمال الحجز الحيّ: نحن لا نعرف حجم الجدول قبل ترجمة الشفرة، أي أننا مجبرون على تعريف مؤشّر واستدعاء
\InlineCode{malloc}.
لا يجب أن ننسى تحرير الذاكرة حين لا نحتاج إلى الخانة التي تم حجزها، ولهذا سيتم استعمال الدالة
\InlineCode{free}
في نهاية \InlineCode{main}.
نحتاج أيضًا إلى متغير
\InlineCode{wordSize}
والذي سيحوي\dots
حجم الكلمة السرية. في الواقع، لو نلاحظ \InlineCode{main}
كما كان في الشفرة السابقة، فسنرى أنه كلّما احتجنا حجم الكلمة استعملنا 6 (لأن الكلمة كانت
\textenglish{YELLOW}
ذات 6 حروف). لكن حاليّا، بما أن الكلمة ستتغير، فيجب على البرنامج أن يتلاءم مع كل الكلمات.
إليك إذن التعريفات النهائية للمتغيّرات في الدالة
\InlineCode{main}
:
\begin{Csource}
int main(int argc, char* argv[])
{
char letter = 0; // Stores the letter suggested by the user
char secretWord[100] = {0}; // The word that the user must find
int *foundLetter = NULL; // Boolean table. Each box corresponds to a letter in the secret word. 0 = letter not found, 1 = letter found
int remainingTries = 10; // Counting the remaining tries (0 = dead)
int i = 0; // A little variable to browse the table
int wordSize= 0;
\end{Csource}