forked from lrthw/lrthw.github.com
-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
4368 lines (3827 loc) · 347 KB
/
atom.xml
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[Learn Ruby The Hard Way]]></title>
<link href="http://lrthw.github.com/atom.xml" rel="self"/>
<link href="http://lrthw.github.com/"/>
<updated>2011-10-13T01:08:54+08:00</updated>
<id>http://lrthw.github.com/</id>
<author>
<name><![CDATA[xdite]]></name>
</author>
<generator uri="http://octopress.org/">Octopress</generator>
<entry>
<title type="html"><![CDATA[一個老程式設計師的建議]]></title>
<link href="http://lrthw.github.com/advice/"/>
<updated>2011-07-21T00:00:00+08:00</updated>
<id>http://lrthw.github.com/advice</id>
<content type="html"><![CDATA[<p>你已經完成了這本書而且打算繼續寫程式。也許這會成為你的一門職業,也許你只是作為業餘愛好玩玩。無論如何,你都需要一些建議以保證你在正確的道路上繼續前行,並且讓這項新的愛好為你帶來最大程度的享受。</p>
<p>我從事寫程式這行已經太長時間,長到對我來說寫程式已經是非常乏味的事情了。我寫這本書的時候,已經懂得大約20 種程式語言,而且可以在大約一天或者一個星期內學會一門程式語言(取決於這門語言有多古怪)。現在對我來說寫程式這件事情已經很無聊,已經談不上什麼興趣了。當然這不是說寫程式本身是一件無聊的事情,也不是說你以後也一定會這樣覺得,這只是我個人在當前的感覺而已。</p>
<p>在這麼久的旅程下來我的體會是:程式語言這東西並不重要,重要的是你用這些語言做的事情。事實上我一直知道這一點,不過以前我會周期性地被各種程式語言分神而忘記了這一點。現在我是永遠不會忘記這一點了,你也不應該忘記這一點。</p>
<p>你學到和用到的程式語言並不重要。不要被圍繞某一種語言的宗教把你扯進去,這只會讓你忘掉了語言的真正目的,也就是作為你的工具來實現有趣的事情。</p>
<p>寫程式作為一項智力活動,是唯一一種能讓你創建交互式藝術的藝術形式。你可以建立專案讓別人使用,而且你可以間接地和使用者溝通。沒有其他的藝術形式能做到如此程度的交互性。電影領著觀眾走向一個方向,繪畫是不會動的。而程式碼卻是雙向互動的。</p>
<p>寫程式作為一項職業只是一般般有趣而已。寫程式可能是一份好工作,但如果你想賺更多的錢而且過得更快樂,你其實開一間快餐分店就可以了。你最好的選擇是將你的程式技術作為你其他職業的秘密武器。</p>
<p>技術公司裡邊會寫程式的人多到一毛錢一打,根本得不到什麼尊敬。而在生物學、醫藥學、政府部門、社會學、物理學、數學等行業領域從事寫程式的人就能得到足夠的尊敬,而且你可以使用這項技能在這些領域做出令人驚異的成就。</p>
<p>當然,所有的這些建議都是沒啥意義的。如果你跟著這本書學習寫軟體而且覺得很喜歡這件事情的話,那你完全可以將其當作一門職業去追求。你應該繼續深入拓展這個近五十年來極少有人探索過的奇異而美妙的智力工作領域。若能從中得到樂趣當然就更好了。</p>
<p>最後我要說的是學習創造軟體的過程會改變你而讓你與眾不同。不是說更好或更壞,只是不同了。你也許會發現因為你會寫軟件而人們對你的態度有些怪異,也許會用「怪人「這樣的詞來形容你。也許你會發現因為你會戳穿他們的邏輯漏洞而他們開始討厭和你爭辯。甚至你可能會發現有人因為你懂得電腦怎麼運作而覺得你是個討厭的怪人。</p>
<p>對於這些我只有一個建議: 讓他們去死吧。這個世界需要更多的怪人,他們知道東西是怎麼工作的而且喜歡找到答案。當他們那樣對你時,只要記住這是你的旅程,不是他們的。「與眾不同」不是誰的錯,告訴你「與眾不同是一種錯」的人只是嫉妒你掌握了他們做夢都不能想到的技能而已。</p>
<p>你會寫程式。他們不會。這真他媽的酷。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[下一步]]></title>
<link href="http://lrthw.github.com/next/"/>
<updated>2011-07-20T00:00:00+08:00</updated>
<id>http://lrthw.github.com/next</id>
<content type="html"><![CDATA[<p>現在還不能說你是一個程式員。這本書的目的相當於給你一個「程式設計師棕帶」。你已經了解了足夠的寫程式基礎,並且有能力閱讀別的寫程式書籍了。讀完這本書,你應該已經掌握了一些學習的方法,並且具備了該有的學習態度,這樣你在閱讀其他 Ruby 書籍時也許會更順利,而且能學到更多東西。</p>
<blockquote><p><strong>Rob says:</strong> 為了更有趣,我推薦你閱讀 Why’s (Poignant) Guide to Ruby:
<a href="http://mislav.uniqpath.com/poignant-guide">http://mislav.uniqpath.com/poignant-guide</a>
大部份的程式內容現在都正在被 review。但是 Why 的心智無比聰明,而且他的書就像是一件藝術品。去看看它的一些 opensource 專案,你可以從他的程式碼裡學到許多東西。</p></blockquote>
<p>或許,你現在已經可以開始鼓搗一些程式出來了。如果你手上有需要解決的問題,試著寫個程式解決一下。你一開始寫的東西可能很挫,不過這沒有關係。以我為例,我在學每一種語言的初期都是很挫的。沒有哪個初學者能寫出完美的代碼來,如果有人告訴你他有這本事,那他只是在厚著臉皮撒謊而已。</p>
<p>最後,記住學習寫程式是要投入時間的,你可能需要至少每天晚上練習幾個小時。順便告訴你,當你每晚學習 Ruby 的時候,我在努力學習彈吉他。我每天練習2 到4 小時,而且還在學習基本的音階。</p>
<p>每個人都是某一方面的菜鳥。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[習題 52: 創造你的網頁遊戲]]></title>
<link href="http://lrthw.github.com/ex52/"/>
<updated>2011-07-19T00:00:00+08:00</updated>
<id>http://lrthw.github.com/ex52</id>
<content type="html"><![CDATA[<p>這本書馬上就要結束了。本章的練習對你是一個真正的挑戰。當你完成以後,你就可以算是一個能力不錯的 Ruby 初學者了。為了進一步學習,你還需要多讀一些書,多寫一些程式,不過你已經具備進一步學習的技能了。接下來的學習就只是時間、動力、以及資源的問題了。</p>
<p>在本章習題中,我們不會去創建一個完整的遊戲,取而代之的是我們會為《習題42》中的遊戲建立一個“引擎(engine)”,讓這個遊戲能夠在瀏覽器中運行起來。這會涉及到將《習題42》中的遊戲「重構(refactor)」,將《習題47》中的架構混合進來,添加自動測試程式碼,最後建立一個可以運行遊戲的web 引擎。</p>
<p>這是一節很「龐大」的習題。我預測你要花一周到一個月才能完成它。最好的方法是一點一點來,每晚上完成一點,在進行下一步之前確認上一步有正確完成。</p>
<h2>重構《習題42》的遊戲</h2>
<p>你已經在兩個練習中修改了 <code>gothonweb</code> 專案,這節習題中你會再修改一次。這種修改的技術叫做「重構(refactoring)」,或者用我喜歡的講法來說,叫「修修補補(fixing stuff)」。重構是一個程式術語,它指的是清理舊程式碼或者為舊程式碼添加新功能的過程。你其實已經做過這樣的事情了,只不過不知道這個術語而已。這是寫軟體過程的第二個自然屬性。</p>
<p>你在本節中要做的,是將《習題47》中的可以測試的房間地圖,以及《習題42》中的遊戲這兩樣東西歸併到一起,創建一個新的遊戲架構。遊戲的內容不會發生變化,只不過我們會通過“重構”讓它有一個更好的架構而已。</p>
<p>第一步是將 <code>ex47.rb</code> 的內容複製到 <code>gothonweb/lib/map.rb</code> 中,然後將 <code>ex47_tests.rb</code> 的內容複製到 <code>gothonweb/test/test_map.rb</code>中,然後再次運行測試,確認他們還能正常運作。</p>
<blockquote><p><strong>Note:</strong> 從現在開始我不會再向你展示運行測試的輸出了,我就假設你回去運行這些測試,而且知道怎樣的輸出是正確的。</p></blockquote>
<p>將《習題47》的程式碼拷貝完畢後,你就該開始重構它,讓它包含《習題42》中的地圖。我一開始會把基本架構為你準備好,然後你需要去完成<code>map.rb</code>和<code>map_tests.rb</code> 裡邊的內容。</p>
<p>首先要做的是使用 <code>Room</code> 類來構建基本的地圖架構:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
<span class='line-number'>50</span>
<span class='line-number'>51</span>
<span class='line-number'>52</span>
<span class='line-number'>53</span>
<span class='line-number'>54</span>
<span class='line-number'>55</span>
<span class='line-number'>56</span>
<span class='line-number'>57</span>
<span class='line-number'>58</span>
<span class='line-number'>59</span>
<span class='line-number'>60</span>
<span class='line-number'>61</span>
<span class='line-number'>62</span>
<span class='line-number'>63</span>
<span class='line-number'>64</span>
<span class='line-number'>65</span>
<span class='line-number'>66</span>
<span class='line-number'>67</span>
<span class='line-number'>68</span>
<span class='line-number'>69</span>
<span class='line-number'>70</span>
<span class='line-number'>71</span>
<span class='line-number'>72</span>
<span class='line-number'>73</span>
<span class='line-number'>74</span>
<span class='line-number'>75</span>
<span class='line-number'>76</span>
<span class='line-number'>77</span>
<span class='line-number'>78</span>
<span class='line-number'>79</span>
<span class='line-number'>80</span>
<span class='line-number'>81</span>
<span class='line-number'>82</span>
<span class='line-number'>83</span>
<span class='line-number'>84</span>
<span class='line-number'>85</span>
<span class='line-number'>86</span>
<span class='line-number'>87</span>
<span class='line-number'>88</span>
<span class='line-number'>89</span>
<span class='line-number'>90</span>
<span class='line-number'>91</span>
<span class='line-number'>92</span>
<span class='line-number'>93</span>
<span class='line-number'>94</span>
<span class='line-number'>95</span>
<span class='line-number'>96</span>
<span class='line-number'>97</span>
<span class='line-number'>98</span>
<span class='line-number'>99</span>
<span class='line-number'>100</span>
<span class='line-number'>101</span>
<span class='line-number'>102</span>
<span class='line-number'>103</span>
<span class='line-number'>104</span>
<span class='line-number'>105</span>
<span class='line-number'>106</span>
<span class='line-number'>107</span>
<span class='line-number'>108</span>
<span class='line-number'>109</span>
<span class='line-number'>110</span>
<span class='line-number'>111</span>
<span class='line-number'>112</span>
<span class='line-number'>113</span>
<span class='line-number'>114</span>
<span class='line-number'>115</span>
<span class='line-number'>116</span>
<span class='line-number'>117</span>
<span class='line-number'>118</span>
<span class='line-number'>119</span>
<span class='line-number'>120</span>
<span class='line-number'>121</span>
<span class='line-number'>122</span>
<span class='line-number'>123</span>
<span class='line-number'>124</span>
<span class='line-number'>125</span>
<span class='line-number'>126</span>
<span class='line-number'>127</span>
<span class='line-number'>128</span>
<span class='line-number'>129</span>
<span class='line-number'>130</span>
<span class='line-number'>131</span>
<span class='line-number'>132</span>
<span class='line-number'>133</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">Room</span>
</span><span class='line'>
</span><span class='line'> <span class="kp">attr_accessor</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:description</span><span class="p">,</span> <span class="ss">:paths</span>
</span><span class='line'>
</span><span class='line'> <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">description</span><span class="p">)</span>
</span><span class='line'> <span class="vi">@name</span> <span class="o">=</span> <span class="nb">name</span>
</span><span class='line'> <span class="vi">@description</span> <span class="o">=</span> <span class="n">description</span>
</span><span class='line'> <span class="vi">@paths</span> <span class="o">=</span> <span class="p">{}</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'> <span class="k">def</span> <span class="nf">go</span><span class="p">(</span><span class="n">direction</span><span class="p">)</span>
</span><span class='line'> <span class="vi">@paths</span><span class="o">[</span><span class="n">direction</span><span class="o">]</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'> <span class="k">def</span> <span class="nf">add_paths</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span>
</span><span class='line'> <span class="vi">@paths</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'><span class="k">end</span>
</span><span class='line'>
</span><span class='line'><span class="n">central_corridor</span> <span class="o">=</span> <span class="no">Room</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"Central Corridor"</span><span class="p">,</span>
</span><span class='line'><span class="sx">%q{</span>
</span><span class='line'><span class="sx">The Gothons of Planet Percal #25 have invaded your ship and destroyed</span>
</span><span class='line'><span class="sx">your entire crew. You are the last surviving member and your last</span>
</span><span class='line'><span class="sx">mission is to get the neutron destruct bomb from the Weapons Armory,</span>
</span><span class='line'><span class="sx">put it in the bridge, and blow the ship up after getting into an </span>
</span><span class='line'><span class="sx">escape pod.</span>
</span><span class='line'>
</span><span class='line'><span class="sx">You're running down the central corridor to the Weapons Armory when</span>
</span><span class='line'><span class="sx">a Gothon jumps out, red scaly skin, dark grimy teeth, and evil clown costume</span>
</span><span class='line'><span class="sx">flowing around his hate filled body. He's blocking the door to the</span>
</span><span class='line'><span class="sx">Armory and about to pull a weapon to blast you.</span>
</span><span class='line'><span class="sx">}</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'><span class="n">laser_weapon_armory</span> <span class="o">=</span> <span class="no">Room</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"Laser Weapon Armory"</span><span class="p">,</span>
</span><span class='line'><span class="sx">%q{</span>
</span><span class='line'><span class="sx">Lucky for you they made you learn Gothon insults in the academy.</span>
</span><span class='line'><span class="sx">You tell the one Gothon joke you know:</span>
</span><span class='line'><span class="sx">Lbhe zbgure vf fb sng, jura fur fvgf nebhaq gur ubhfr, fur fvgf nebhaq gur ubhfr.</span>
</span><span class='line'><span class="sx">The Gothon stops, tries not to laugh, then busts out laughing and can't move.</span>
</span><span class='line'><span class="sx">While he's laughing you run up and shoot him square in the head</span>
</span><span class='line'><span class="sx">putting him down, then jump through the Weapon Armory door.</span>
</span><span class='line'>
</span><span class='line'><span class="sx">You do a dive roll into the Weapon Armory, crouch and scan the room</span>
</span><span class='line'><span class="sx">for more Gothons that might be hiding. It's dead quiet, too quiet.</span>
</span><span class='line'><span class="sx">You stand up and run to the far side of the room and find the</span>
</span><span class='line'><span class="sx">neutron bomb in its container. There's a keypad lock on the box</span>
</span><span class='line'><span class="sx">and you need the code to get the bomb out. If you get the code</span>
</span><span class='line'><span class="sx">wrong 10 times then the lock closes forever and you can't</span>
</span><span class='line'><span class="sx">get the bomb. The code is 3 digits.</span>
</span><span class='line'><span class="sx">}</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'><span class="n">the_bridge</span> <span class="o">=</span> <span class="no">Room</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"The Bridge"</span><span class="p">,</span>
</span><span class='line'><span class="sx">%q{</span>
</span><span class='line'><span class="sx">The container clicks open and the seal breaks, letting gas out.</span>
</span><span class='line'><span class="sx">You grab the neutron bomb and run as fast as you can to the</span>
</span><span class='line'><span class="sx">bridge where you must place it in the right spot.</span>
</span><span class='line'>
</span><span class='line'><span class="sx">You burst onto the Bridge with the netron destruct bomb</span>
</span><span class='line'><span class="sx">under your arm and surprise 5 Gothons who are trying to</span>
</span><span class='line'><span class="sx">take control of the ship. Each of them has an even uglier</span>
</span><span class='line'><span class="sx">clown costume than the last. They haven't pulled their</span>
</span><span class='line'><span class="sx">weapons out yet, as they see the active bomb under your</span>
</span><span class='line'><span class="sx">arm and don't want to set it off.</span>
</span><span class='line'><span class="sx">}</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'><span class="n">escape_pod</span> <span class="o">=</span> <span class="no">Room</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"Escape Pod"</span><span class="p">,</span>
</span><span class='line'><span class="sx">%q{</span>
</span><span class='line'><span class="sx">You point your blaster at the bomb under your arm</span>
</span><span class='line'><span class="sx">and the Gothons put their hands up and start to sweat.</span>
</span><span class='line'><span class="sx">You inch backward to the door, open it, and then carefully</span>
</span><span class='line'><span class="sx">place the bomb on the floor, pointing your blaster at it.</span>
</span><span class='line'><span class="sx">You then jump back through the door, punch the close button</span>
</span><span class='line'><span class="sx">and blast the lock so the Gothons can't get out.</span>
</span><span class='line'><span class="sx">Now that the bomb is placed you run to the escape pod to</span>
</span><span class='line'><span class="sx">get off this tin can.</span>
</span><span class='line'>
</span><span class='line'><span class="sx">You rush through the ship desperately trying to make it to</span>
</span><span class='line'><span class="sx">the escape pod before the whole ship explodes. It seems like</span>
</span><span class='line'><span class="sx">hardly any Gothons are on the ship, so your run is clear of</span>
</span><span class='line'><span class="sx">interference. You get to the chamber with the escape pods, and</span>
</span><span class='line'><span class="sx">now need to pick one to take. Some of them could be damaged</span>
</span><span class='line'><span class="sx">but you don't have time to look. There's 5 pods, which one</span>
</span><span class='line'><span class="sx">do you take?</span>
</span><span class='line'><span class="sx">}</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'><span class="n">the_end_winner</span> <span class="o">=</span> <span class="no">Room</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"The End"</span><span class="p">,</span>
</span><span class='line'><span class="sx">%q{</span>
</span><span class='line'><span class="sx">You jump into pod 2 and hit the eject button.</span>
</span><span class='line'><span class="sx">The pod easily slides out into space heading to</span>
</span><span class='line'><span class="sx">the planet below. As it flies to the planet, you look</span>
</span><span class='line'><span class="sx">back and see your ship implode then explode like a</span>
</span><span class='line'><span class="sx">bright star, taking out the Gothon ship at the same</span>
</span><span class='line'><span class="sx">time. You won!</span>
</span><span class='line'><span class="sx">}</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'><span class="n">the_end_loser</span> <span class="o">=</span> <span class="no">Room</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"The End"</span><span class="p">,</span>
</span><span class='line'><span class="sx">%q{</span>
</span><span class='line'><span class="sx">You jump into a random pod and hit the eject button.</span>
</span><span class='line'><span class="sx">The pod escapes out into the void of space, then</span>
</span><span class='line'><span class="sx">implodes as the hull ruptures, crushing your body</span>
</span><span class='line'><span class="sx">into jam jelly.</span>
</span><span class='line'><span class="sx">}</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="n">escape_pod</span><span class="o">.</span><span class="n">add_paths</span><span class="p">({</span>
</span><span class='line'> <span class="s1">'2'</span> <span class="o">=></span> <span class="n">the_end_winner</span><span class="p">,</span>
</span><span class='line'> <span class="s1">'*'</span> <span class="o">=></span> <span class="n">the_end_loser</span>
</span><span class='line'><span class="p">})</span>
</span><span class='line'>
</span><span class='line'><span class="n">generic_death</span> <span class="o">=</span> <span class="no">Room</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"death"</span><span class="p">,</span> <span class="s2">"You died."</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="n">the_bridge</span><span class="o">.</span><span class="n">add_paths</span><span class="p">({</span>
</span><span class='line'> <span class="s1">'throw the bomb'</span> <span class="o">=></span> <span class="n">generic_death</span><span class="p">,</span>
</span><span class='line'> <span class="s1">'slowly place the bomb'</span> <span class="o">=></span> <span class="n">escape_pod</span>
</span><span class='line'><span class="p">})</span>
</span><span class='line'>
</span><span class='line'><span class="n">laser_weapon_armory</span><span class="o">.</span><span class="n">add_paths</span><span class="p">({</span>
</span><span class='line'> <span class="s1">'0132'</span> <span class="o">=></span> <span class="n">the_bridge</span><span class="p">,</span>
</span><span class='line'> <span class="s1">'*'</span> <span class="o">=></span> <span class="n">generic_death</span>
</span><span class='line'><span class="p">})</span>
</span><span class='line'>
</span><span class='line'><span class="n">central_corridor</span><span class="o">.</span><span class="n">add_paths</span><span class="p">({</span>
</span><span class='line'> <span class="s1">'shoot!'</span> <span class="o">=></span> <span class="n">generic_death</span><span class="p">,</span>
</span><span class='line'> <span class="s1">'dodge!'</span><span class="o">=></span> <span class="n">generic_death</span><span class="p">,</span>
</span><span class='line'> <span class="s1">'tell a joke'</span> <span class="o">=></span> <span class="n">laser_weapon_armory</span>
</span><span class='line'><span class="p">})</span>
</span><span class='line'>
</span><span class='line'><span class="no">START</span> <span class="o">=</span> <span class="n">central_corridor</span>
</span></code></pre></td></tr></table></div></figure>
<p>你會發現我們的 <code>Room</code> 類和地圖有一些問題:</p>
<ol>
<li><p>在進入一個房間以前會打出一段文字作為房間的描述,我們需要將這些描述和每個房間關聯起來,這樣房間的次序就不會被打亂了,這對我們的遊戲是一件好事。這些描述本來是在 <code>if-else</code> 結構中的,這是我們後面要修改的東西。</p></li>
<li><p>原版遊戲中我們使用了專門的程式來生成一些內容,例如炸彈的激活鍵碼,艦艙的選擇等,這次我們做遊戲時就先使用預設值好了,不過後面的加分習題裡,我會要求你把這些功能再加到遊戲中。</p></li>
<li><p>我為所有的遊戲中的失敗結尾寫了一個 <code>generic_death</code>,你需要去補全這個函式。你需要把原版遊戲中所有的失敗結尾都加進去,並確保程式碼能正確運行。</p></li>
<li><p>我添加了一種新的轉換模式,以<code>"*"</code>為標記,用來在遊戲引擎中實現「catch-all」動作。</p></li>
</ol>
<p>等你把上面的程式碼基本寫好以後,接下來就是引導你繼續寫下去的自動測試的內容 <code>test/test_map.rb</code> 了:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="nb">require</span> <span class="s1">'test/unit'</span>
</span><span class='line'><span class="n">require_relative</span> <span class="s1">'../lib/map'</span>
</span><span class='line'>
</span><span class='line'><span class="k">class</span> <span class="nc">MapTests</span> <span class="o"><</span> <span class="no">Test</span><span class="o">::</span><span class="no">Unit</span><span class="o">::</span><span class="no">TestCase</span>
</span><span class='line'>
</span><span class='line'> <span class="k">def</span> <span class="nf">test_room</span><span class="p">()</span>
</span><span class='line'> <span class="n">gold</span> <span class="o">=</span> <span class="no">Room</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"GoldRoom"</span><span class="p">,</span>
</span><span class='line'> <span class="sx">%q{This room has gold in it you can grab. There's a</span>
</span><span class='line'><span class="sx"> door to the north.}</span><span class="p">)</span>
</span><span class='line'> <span class="n">assert_equal</span><span class="p">(</span><span class="n">gold</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="s2">"GoldRoom"</span><span class="p">)</span>
</span><span class='line'> <span class="n">assert_equal</span><span class="p">(</span><span class="n">gold</span><span class="o">.</span><span class="n">paths</span><span class="p">,</span> <span class="p">{})</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'> <span class="k">def</span> <span class="nf">test_room_paths</span><span class="p">()</span>
</span><span class='line'> <span class="n">center</span> <span class="o">=</span> <span class="no">Room</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"Center"</span><span class="p">,</span> <span class="s2">"Test room in the center."</span><span class="p">)</span>
</span><span class='line'> <span class="n">north</span> <span class="o">=</span> <span class="no">Room</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"North"</span><span class="p">,</span> <span class="s2">"Test room in the north."</span><span class="p">)</span>
</span><span class='line'> <span class="n">south</span> <span class="o">=</span> <span class="no">Room</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"South"</span><span class="p">,</span> <span class="s2">"Test room in the south."</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'> <span class="n">center</span><span class="o">.</span><span class="n">add_paths</span><span class="p">({</span><span class="s1">'north'</span> <span class="o">=></span> <span class="n">north</span><span class="p">,</span> <span class="s1">'south'</span> <span class="o">=></span> <span class="n">south</span><span class="p">})</span>
</span><span class='line'> <span class="n">assert_equal</span><span class="p">(</span><span class="n">center</span><span class="o">.</span><span class="n">go</span><span class="p">(</span><span class="s1">'north'</span><span class="p">),</span> <span class="n">north</span><span class="p">)</span>
</span><span class='line'> <span class="n">assert_equal</span><span class="p">(</span><span class="n">center</span><span class="o">.</span><span class="n">go</span><span class="p">(</span><span class="s1">'south'</span><span class="p">),</span> <span class="n">south</span><span class="p">)</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'> <span class="k">def</span> <span class="nf">test_map</span><span class="p">()</span>
</span><span class='line'> <span class="n">start</span> <span class="o">=</span> <span class="no">Room</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"Start"</span><span class="p">,</span> <span class="s2">"You can go west and down a hole."</span><span class="p">)</span>
</span><span class='line'> <span class="n">west</span> <span class="o">=</span> <span class="no">Room</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"Trees"</span><span class="p">,</span> <span class="s2">"There are trees here, you can go east."</span><span class="p">)</span>
</span><span class='line'> <span class="n">down</span> <span class="o">=</span> <span class="no">Room</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"Dungeon"</span><span class="p">,</span> <span class="s2">"It's dark down here, you can go up."</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'> <span class="n">start</span><span class="o">.</span><span class="n">add_paths</span><span class="p">({</span><span class="s1">'west'</span> <span class="o">=></span> <span class="n">west</span><span class="p">,</span> <span class="s1">'down'</span> <span class="o">=></span> <span class="n">down</span><span class="p">})</span>
</span><span class='line'> <span class="n">west</span><span class="o">.</span><span class="n">add_paths</span><span class="p">({</span><span class="s1">'east'</span> <span class="o">=></span> <span class="n">start</span><span class="p">})</span>
</span><span class='line'> <span class="n">down</span><span class="o">.</span><span class="n">add_paths</span><span class="p">({</span><span class="s1">'up'</span> <span class="o">=></span> <span class="n">start</span><span class="p">})</span>
</span><span class='line'>
</span><span class='line'> <span class="n">assert_equal</span><span class="p">(</span><span class="n">start</span><span class="o">.</span><span class="n">go</span><span class="p">(</span><span class="s1">'west'</span><span class="p">),</span> <span class="n">west</span><span class="p">)</span>
</span><span class='line'> <span class="n">assert_equal</span><span class="p">(</span><span class="n">start</span><span class="o">.</span><span class="n">go</span><span class="p">(</span><span class="s1">'west'</span><span class="p">)</span><span class="o">.</span><span class="n">go</span><span class="p">(</span><span class="s1">'east'</span><span class="p">),</span> <span class="n">start</span><span class="p">)</span>
</span><span class='line'> <span class="n">assert_equal</span><span class="p">(</span><span class="n">start</span><span class="o">.</span><span class="n">go</span><span class="p">(</span><span class="s1">'down'</span><span class="p">)</span><span class="o">.</span><span class="n">go</span><span class="p">(</span><span class="s1">'up'</span><span class="p">),</span> <span class="n">start</span><span class="p">)</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'> <span class="k">def</span> <span class="nf">test_gothon_game_map</span><span class="p">()</span>
</span><span class='line'> <span class="n">assert_equal</span><span class="p">(</span><span class="no">START</span><span class="o">.</span><span class="n">go</span><span class="p">(</span><span class="s1">'shoot!'</span><span class="p">),</span> <span class="n">generic_death</span><span class="p">)</span>
</span><span class='line'> <span class="n">assert_equal</span><span class="p">(</span><span class="no">START</span><span class="o">.</span><span class="n">go</span><span class="p">(</span><span class="s1">'dodge!'</span><span class="p">),</span> <span class="n">generic_death</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'> <span class="n">room</span> <span class="o">=</span> <span class="no">START</span><span class="o">.</span><span class="n">go</span><span class="p">(</span><span class="s1">'tell a joke'</span><span class="p">)</span>
</span><span class='line'> <span class="n">assert_equal</span><span class="p">(</span><span class="n">room</span><span class="p">,</span> <span class="n">laser_weapon_armory</span><span class="p">)</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<p>你在這部分練習中的任務是完成地圖,並且讓自動測試可以完整地檢查過整個地圖。這包括將所有的 <code>generic_death</code> 物件修正為遊戲中實際的失敗結尾。讓你的程式碼成功運行起來,並讓你的測試越全面越好。後面我們會對地圖做一些修改,到時候這些測試將保證修改後的程式碼還可以正常工作。</p>
<h2>會話(session)和用戶跟踪</h2>
<p>在你的 web 程式運行的某個位置,你需要追踪一些信息,並將這些信息和用戶的瀏覽器關聯起來。在HTTP 協議的框架中,web 環境是「無狀態(stateless)」的,這意味著你的每一次請求和你其它的請求都是相互獨立的。如果你請求了頁面A,輸入了一些資料,然後點了一個頁面B 的鏈接,那你在頁面A 輸入的數據就全部消失了。</p>
<p>解決這個問題的方法是為 web 程式建立一個很小的資料儲存功能,給每個瀏覽器賦予一個獨一無二的數字,用來跟踪瀏覽器所作的事情。這個功能通常適用資料庫或者是存儲在磁碟上的檔案來實現。在 Sinatra 這個框架中實現這樣的功能是很容易的,以下就是一個這樣的例子(使用 Rack middleware):</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="nb">require</span> <span class="s1">'rubygems'</span>
</span><span class='line'><span class="nb">require</span> <span class="s1">'sinatra'</span>
</span><span class='line'>
</span><span class='line'><span class="n">use</span> <span class="no">Rack</span><span class="o">::</span><span class="no">Session</span><span class="o">::</span><span class="no">Pool</span>
</span><span class='line'>
</span><span class='line'><span class="n">get</span> <span class="s1">'/count'</span> <span class="k">do</span>
</span><span class='line'> <span class="n">session</span><span class="o">[</span><span class="ss">:count</span><span class="o">]</span> <span class="o">||=</span> <span class="mi">0</span>
</span><span class='line'> <span class="n">session</span><span class="o">[</span><span class="ss">:count</span><span class="o">]</span> <span class="o">+=</span><span class="mi">1</span>
</span><span class='line'> <span class="s2">"Count: </span><span class="si">#{</span><span class="n">session</span><span class="o">[</span><span class="ss">:count</span><span class="o">]</span><span class="si">}</span><span class="s2">"</span>
</span><span class='line'><span class="k">end</span>
</span><span class='line'>
</span><span class='line'><span class="n">get</span> <span class="s1">'/reset'</span> <span class="k">do</span>
</span><span class='line'> <span class="n">session</span><span class="o">.</span><span class="n">clear</span>
</span><span class='line'> <span class="s2">"Count reset to 0."</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<h2>建立引擎</h2>
<p>你應該已經寫好了遊戲地圖和它的單元測試程式碼碼。現在我要求你製作一個簡單的遊戲引擎,用來讓遊戲中的各個房間運轉起來,從玩家收集輸入,並且記住玩家到了那一幕。我們將用到你剛學過的會話來製作一個簡單的引擎,讓它可以:</p>
<ol>
<li>為新使用者啟動新的遊戲。</li>
<li>將房間展示給使用者。</li>
<li>接受使用者的輸入。</li>
<li>在遊戲中處理使用者的輸入。</li>
<li>顯示遊戲的結果,繼續遊戲的下一幕,知道玩家角色死亡為止。</li>
</ol>
<p>為了建立這個引擎,你需要將我們久經考驗的<code>lib/gothonsweb.rb</code> 搬過來,建立一個功能完備的、基於 session 的遊戲引擎。這裡的難點是我會先使用基本的 HTML 檔案創建一個非常簡單的版本,接下來將由你完成它,基本的引擎是這個樣子的:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="n">require_relative</span> <span class="s2">"gothonweb/version"</span>
</span><span class='line'><span class="n">require_relative</span> <span class="s2">"map"</span>
</span><span class='line'><span class="nb">require</span> <span class="s2">"sinatra"</span>
</span><span class='line'><span class="nb">require</span> <span class="s2">"erb"</span>
</span><span class='line'>
</span><span class='line'><span class="k">module</span> <span class="nn">Gothonweb</span>
</span><span class='line'>
</span><span class='line'> <span class="n">use</span> <span class="no">Rack</span><span class="o">::</span><span class="no">Session</span><span class="o">::</span><span class="no">Pool</span>
</span><span class='line'>
</span><span class='line'> <span class="n">get</span> <span class="s1">'/'</span> <span class="k">do</span>
</span><span class='line'> <span class="c1"># this is used to "setup" the session with starting values</span>
</span><span class='line'> <span class="nb">p</span> <span class="no">START</span>
</span><span class='line'> <span class="n">session</span><span class="o">[</span><span class="ss">:room</span><span class="o">]</span> <span class="o">=</span> <span class="no">START</span>
</span><span class='line'> <span class="n">redirect</span><span class="p">(</span><span class="s2">"/game"</span><span class="p">)</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'> <span class="n">get</span> <span class="s1">'/game'</span> <span class="k">do</span>
</span><span class='line'> <span class="k">if</span> <span class="n">session</span><span class="o">[</span><span class="ss">:room</span><span class="o">]</span>
</span><span class='line'> <span class="n">erb</span> <span class="ss">:show_room</span><span class="p">,</span> <span class="ss">:locals</span> <span class="o">=></span> <span class="p">{</span><span class="ss">:room</span> <span class="o">=></span> <span class="n">session</span><span class="o">[</span><span class="ss">:room</span><span class="o">]</span><span class="p">}</span>
</span><span class='line'> <span class="k">else</span>
</span><span class='line'> <span class="c1"># why is there here? do you need it?</span>
</span><span class='line'> <span class="n">erb</span> <span class="ss">:you_died</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'> <span class="n">post</span> <span class="s1">'/game'</span> <span class="k">do</span>
</span><span class='line'> <span class="n">action</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">params</span><span class="o">[</span><span class="ss">:action</span><span class="o">]</span> <span class="o">||</span> <span class="kp">nil</span><span class="si">}</span><span class="s2">"</span>
</span><span class='line'> <span class="c1"># there is a bug here, can you fix it?</span>
</span><span class='line'> <span class="k">if</span> <span class="n">session</span><span class="o">[</span><span class="ss">:room</span><span class="o">]</span>
</span><span class='line'> <span class="n">session</span><span class="o">[</span><span class="ss">:room</span><span class="o">]</span> <span class="o">=</span> <span class="n">session</span><span class="o">[</span><span class="ss">:room</span><span class="o">].</span><span class="n">go</span><span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:action</span><span class="o">]</span><span class="p">)</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'> <span class="n">redirect</span><span class="p">(</span><span class="s2">"/game"</span><span class="p">)</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<p>下一步,你應該刪除 <code>lib/views/hello_form.erb</code> 和 <code>lib/views/index.erb</code> 然後創作兩個在上述 code 提到的 template,這裡是一個非常簡單的 <code>lib/views/show_room.erb</code>:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
</pre></td><td class='code'><pre><code class='erb'><span class='line'><span class="x"><h1></span><span class="cp"><%=</span> <span class="n">room</span><span class="o">.</span><span class="n">name</span> <span class="cp">%></span><span class="x"></h1></span>
</span><span class='line'>
</span><span class='line'><span class="x"><pre></span>
</span><span class='line'><span class="cp"><%=</span> <span class="n">room</span><span class="o">.</span><span class="n">description</span> <span class="cp">%></span><span class="x"></span>
</span><span class='line'><span class="x"></pre></span>
</span><span class='line'>
</span><span class='line'><span class="cp"><%</span> <span class="k">if</span> <span class="n">room</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"death"</span> <span class="cp">%></span><span class="x"></span>
</span><span class='line'><span class="x"> <p></span>
</span><span class='line'><span class="x"> <a href="/">Play Again?</a></span>
</span><span class='line'><span class="x"> </p></span>
</span><span class='line'><span class="cp"><%</span> <span class="k">else</span> <span class="cp">%></span><span class="x"></span>
</span><span class='line'><span class="x"> <p></span>
</span><span class='line'><span class="x"> <form action="/game" method="POST"></span>
</span><span class='line'><span class="x"> - <input type="text" name="action"> <input type="SUBMIT"></span>
</span><span class='line'><span class="x"> </form></span>
</span><span class='line'><span class="x"> </p></span>
</span><span class='line'><span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span><span class="x"></span>
</span></code></pre></td></tr></table></div></figure>
<p>這就用來顯示遊戲中的房間的模板。接下來,你需要在使用者跑到地圖的邊界時,用一個模板告訴使用者他的角色的死亡信息,也就是<code>lib/views/you_died.erb</code> 這個模板:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='erb'><span class='line'><span class="x"><h1>You Died!</h1></span>
</span><span class='line'>
</span><span class='line'><span class="x"><p>Looks like you bit the dust.</p></span>
</span><span class='line'><span class="x"><p><a href="/">Play Again</a></p></span>
</span></code></pre></td></tr></table></div></figure>
<p>準備好了這些文件,你現在可以做下面的事情了:</p>
<ol>
<li>讓測試代碼 <code>test/test_gothonsweb.rb</code> 再次運行起來,這樣你就可以去測試這個遊戲。由於 session 的存在,你可能頂多只能實現幾次點擊,不過你應該可以做出一些基本的測試來。</li>
<li>執行 lib/gothonsweb.rb` 腳本,試玩一下你的遊戲。</li>
<li>你需要和往常一樣刷新和修正你的遊戲,慢慢修改遊戲的HTML 檔案和引擎,直到你實現遊戲需要的所有功能為止。</li>
</ol>
<h2>你的期末考試</h2>
<p>你有沒有覺著我一下子給了你超多的資訊呢?那就對了,我想要你在學習技能的同時可以有一些可以用來鼓搗的東西。為了完成這節習題,我將給你最後一套需要你自己完成的練習。你將注意到,到目前為止你寫的遊戲並不是很好,這只是你的第一版程式碼而已。你現在的任務是讓遊戲更加完善,實現下面的這些功能:</p>
<ol>
<li>修正程式碼中所有我提到和沒提到的bug,如果你發現了新的bug,你可以告訴我。</li>
<li>改進所有的自動測試,讓你可以測試更多的內容,直到你可以不用瀏覽器就能測到所有的內容為止。</li>
<li>讓HTML 頁面看上去更美觀一些。</li>
<li>研究一下網頁登錄系統,為這個程式創建一個登錄界面,這樣人們就可以登錄這個遊戲,並且可以保存遊戲高分。</li>
<li>完成遊戲地圖,盡可能地把遊戲做大,功能做全。</li>
<li>給用戶一個「幫助系統」,讓他們可以查詢每個房間裡可以執行哪些命令。</li>
<li>為你的遊戲添加新功能,想到什麼功能就添加什麼功能。</li>
<li>創建多個地圖,讓用戶可以選擇他們想要玩的一張來進行遊戲。你的 <code>lib/gothonsweb.rb</code> 應該可以運行提供給它的任意的地圖,這樣你的引擎就可以支持多個不同的遊戲。</li>
<li>最後,使用你在習題 48 和49 中學到的東西來創建一個更好的輸入處理器。你手頭已經有了大部分必要的程式碼,你只需要改進語法,讓它和你的輸入表單以及遊戲引擎掛鉤即可。</li>
</ol>
<p>祝你好運!</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[習題 51: 從瀏覽器中取得輸入]]></title>
<link href="http://lrthw.github.com/ex51/"/>
<updated>2011-07-19T00:00:00+08:00</updated>
<id>http://lrthw.github.com/ex51</id>
<content type="html"><![CDATA[<p>雖然能讓瀏覽器顯示「Hello World」是很有趣的一件事情,但是如果能讓用戶通過表單(form)向你的應用程序提交資訊就更有趣了。這節習題中,我們將使用form 改進你的web 程式,並且搞懂如何為一個網站程式寫自動化測試。</p>
<h2>Web 運作原理</h2>
<p>該學點無趣的東西了。在建立 form 前你需要先多學一點關於 web的運作原理。這裡講的並不完整,但是相當準確,在你的程式出錯時,它會幫你找到出錯的原因。另外,如果你理解了form 的應用,那麼建立form 對你來說就會更容易了。</p>
<p>我將以一個簡單的圖示講起,它向你展示了web 請求的各個不同的部分,以及資訊傳遞的大致流程:</p>
<p><img src="http://learnpythonthehardway.org/book/_images/http_request_diagram.png" alt="http request diagram" /></p>
<p>為了方便講述HTTP 請求(request) 的流程,我在每條線上面加了字母標籤以作區別。</p>
<ol>
<li>你在瀏覽器中輸入網址http://learnpythonthehardway.org/,然後瀏覽器會通過你的電腦的網路設備發出request(<code>線路A</code>)。</li>
<li>你的request 被傳送到網際網路(<code>線路B</code>),然後再抵達遠端服務器(<code>線路C</code>),然後我的伺服器將接受這個request。</li>
<li>我的伺服器接受 request 後,我的 web 應用程式就去處理這個請求(<code>線路D</code>),然後我的軮頁應用程式就會去運行 <code>/</code> (index) 這個「處理程序(handler)」。</li>
<li>在程式碼 return 的時候,我的伺服器就會發出響應(response),這個響應會再通過<code>線路D</code>傳遞到你的瀏覽器。</li>
<li>這個網站所在的伺服器將響應由<code>線路D</code>獲取,然後通過<code>線路C</code>傳至網際網路。</li>
<li>響應通過網路網路由<code>線路B</code>傳至你的電腦,電腦的網路卡再通過<code>線路A</code>將響應傳給你的瀏覽器。</li>
<li>最後,你的瀏覽器顯示了這個響應的內容。</li>
</ol>
<p>這段詳解中用到了一些術語。你需要掌握這些術語,以便在談論你的 web 應用時你能明白而且應用它們:</p>
<h3>瀏覽器(browser)</h3>
<p>這是你幾乎每天都會用到的軟件。大部分人不知道它真正的原理,他們只會把它叫作「網際網路」。它的作用其實是接收你輸入到地址欄網址(例如<a href="http://learnpythonthehardway.org">http://learnpythonthehardway.org</a>),然後使用該資訊向該網址對應的伺服器提出請求(request)。</p>
<h3>IP 位址 ( Address )</h3>
<p>通常這是一個像 <a href="http://learnpythonthehardway.org/">http://learnpythonthehardway.org/</a> 一樣的URL (Uniform Resource Locator,統一資源定位符 ),它告訴瀏覽器該打開哪個網站。前面的 <code>http</code> 指出了你要使用的協議(protocol),這裡我們用的是「超文本傳輸協議(Hyper-Text Transport Protocol)」。你還可以試試ftp://ibiblio.org/,這是一個「FTP文件傳輸協議(File Transport Protocol)‘的例子。<code>learnpythonthehardway.org</code> 這部分是「主機名(hostname)」,也就是一個便於人閱讀和記憶的字串,主機名會被匹配到一串叫作「IP 位址」的數字上面,這個「IP 位址」就相當於網路中一台電腦的電話號碼,通過這個號碼可以訪問到這台電腦。最後,URL中還可以尾隨一個「路徑「,例如http://learnpythonthehardway.org/book/ 中的 <code>/book/</code>,它對應的是伺服器上的某個文件或者某些資源,通過訪問這樣的網址,你可以向伺服器發出請求,然後獲得這些資源。網站地址還有很多別的組成部分,不過這些是最主要的。</p>
<h3>連接(connection)</h3>
<p>一旦瀏覽器知道了協議(http)、伺服器(learnpythonthehardway.org)、以及要獲得的資源,它就要去建立一個連接。這個過程中,瀏覽器讓操作系統(Operating System, OS) 打開計算機的一個「埠號(port)」(通常是80埠號),埠號準備好以後,操作系統會回傳給你的程式一個類似檔案的東西,它所做的事情就是通過網路傳輸和接收資料,讓你的電腦和learnpythonthehardway.org這個網站所屬的伺服器之間實現資料交流。當你使用 http://localhost:4567/ 訪問你自己的站點時,發生的事情其實是一樣的,只不過這次你告訴了瀏覽器要訪問的是你自己的電腦(localhost),要使用的端口不是默認的80,而是 4567 。你還可以直接訪問http://learnpythonthehardway.org:80/,這和不輸入埠號效果一樣,因為HTTP的默認埠號本來就是80。</p>
<h3>請求(request)</h3>
<p>你的瀏覽器通過你提供的地址建立了連接,現在它需要從遠端伺服器要到它(或你)想要的資源。如果你在URL的結尾加了 <code>/book/</code>,那你想要的就是<code>/book/</code> 對應的檔案或資源,大部分的伺服器會直接為你呼叫<code>/book/index.html</code> 這個檔案,不過我們就假裝不存在好了。瀏覽器為了獲得伺服器上的資源,它需要向伺服器發送一個「請求」。這裡我就不講細節了,為了得到伺服器上的內容,你必須先向伺服器發送一個請求才行。有意思的是,「資源」不一定非要是檔案。例如當瀏覽器向你的應用程序提出請求的時候,伺服器返回的其實是你的程式碼生成的一些東西。</p>
<h3>伺服器(server)</h3>
<p>伺服器指的是瀏覽器另一端連接的電腦,它知道如何回應瀏覽器請求的檔案和資源。大部分的 web 伺服器只要發送檔案就可以了,這也是伺服器流量的主要部分。不過你學的是使用 Ruby 組建一個伺服器,這個伺服器知道如何接受請求,然後返回用 Ruby 處理過的字符串。當你使用這種處理方式時,你其實是假裝把檔案發給了瀏覽器,其實你用的都只是程式碼而已。就像你在《習題50》中看到的,要構建一個「響應」其實也不需要多少程式碼。</p>
<h3>響應(response)</h3>
<p>這就是你的伺服器回覆給你的請求,傳回至瀏覽器的HTML,它裡邊可能有css、javascript、或者圖像等內容。以檔案響應為例,伺服器只要從磁碟讀取檔案,發送給瀏覽器就可以了,不過它還要將這些內容包在一個特別定義的「header]」中,這樣瀏覽器就會知道它獲取的是什麼類型的內容。以你的web 應用程式為例,你發送的其實還是一樣的東西,包括 header 也一樣,只不過這些資料是你用 Ruby 程式碼即時生成的。</p>
<p>這個可以算是你能在網上找到的關於瀏覽器如何訪問網站的最快的快速課程了。這節課程應該可以幫你更容易地理解本節的習題,如果你還是不明白,就到處找資料多多了解這方面的資訊,知道你明白為止。有一個很好的方法,就是你對照著上面的圖示,將你在《習題50》中創建的 web 程式中的內容分成幾個部分,讓其中的各部分對應到上面的圖示。如果你可以正確地將程式的各部分對應到這個圖示,你就大致開始明白它的運作原理了。</p>
<h2>表單(form)的運作原理</h2>
<p>熟悉「表單」最好的方法就是寫一個可以接收表單資料的程式出來,然後看你可以對它做些什麼。先將你的<code>lib/gothonsweb.rb</code> 修改成下面的樣子:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="n">require_relative</span> <span class="s2">"gothonweb/version"</span>
</span><span class='line'><span class="nb">require</span> <span class="s2">"sinatra"</span>
</span><span class='line'><span class="nb">require</span> <span class="s2">"erb"</span>
</span><span class='line'>
</span><span class='line'><span class="k">module</span> <span class="nn">Gothonweb</span>
</span><span class='line'> <span class="n">get</span> <span class="s1">'/'</span> <span class="k">do</span>
</span><span class='line'> <span class="n">greeting</span> <span class="o">=</span> <span class="s2">"Hello, World!"</span>
</span><span class='line'> <span class="n">erb</span> <span class="ss">:index</span><span class="p">,</span> <span class="ss">:locals</span> <span class="o">=></span> <span class="p">{</span><span class="ss">:greeting</span> <span class="o">=></span> <span class="n">greeting</span><span class="p">}</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'> <span class="n">get</span> <span class="s1">'/hello'</span> <span class="k">do</span>
</span><span class='line'> <span class="nb">name</span> <span class="o">=</span> <span class="n">params</span><span class="o">[</span><span class="ss">:name</span><span class="o">]</span> <span class="o">||</span> <span class="s2">"Nobody"</span>
</span><span class='line'> <span class="n">greeting</span> <span class="o">=</span> <span class="s2">"Hello, </span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span>
</span><span class='line'> <span class="n">erb</span> <span class="ss">:index</span><span class="p">,</span> <span class="ss">:locals</span> <span class="o">=></span> <span class="p">{</span><span class="ss">:greeting</span> <span class="o">=></span> <span class="n">greeting</span><span class="p">}</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<p>重啟你的 Sinatra(按CTRL-C後重新運行),確認它有運行起來,然後使用瀏覽器訪問 http://localhost:4567/hello,這時瀏覽器應該會顯示 “I just wanted to say Hello , Nobody.”,接下來,將瀏覽器的地址改成 http://localhost:4567/hello?name=Frank,然後你可以看到頁面顯示為 “Hello, Frank.”,最後將 <code>name=Frank</code> 修改為你自己的名字,你就可以看到它對你說 Hello 了。</p>
<p>讓我們研究一下你的程式裡做過的修改。</p>
<ol>
<li>我們沒有直接為 greeting 賦值,而是使用了 <code>params</code> Hash 從瀏覽器獲取數據。這Sinatra 個函數會將一組在 URL <code>?</code> 後面的部份的 key / value 組加進 <code>prarms</code> Hash 裡。</li>
<li>然後我從 <code>params[:name]</code> 中找到 <code>name</code> 的值,並為 <code>greeting</code> 賦值,這部份相信你已經很熟悉了。</li>
<li>其他的內容和以前是一樣的,我們就不再分析了。</li>
</ol>
<p>URL中該還可以包含多個參數。將本例的URL改成這樣子: <code>http://localhost:4567/hello?name=Frank&greet=Hola</code>。然後修改程式碼,讓它去存取 <code>prarams[:name]</code> 和 <code>params[:greet]</code>,如下所示:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="n">greeting</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">greet</span><span class="si">}</span><span class="s2">, </span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span>
</span></code></pre></td></tr></table></div></figure>
<h2>創建HTML表單</h2>
<p>你可以通過URL參數實現表單提交,不過這樣看上去有些醜陋,而且不方便一般人使用,你真正需要的是一個「POST表單」,這是一種包含了<code><form></code>標籤的特殊 HTML 檔案。這種表單收集使用者輸入並將其傳遞給你的web程式,這和你上面實現的目的基本是一樣的。</p>
<p>讓我們來快速建立一個,從中你可以看出它的運作原理。你需要創建一個新的HTML文件,叫做 <code>lib/views/hello_form.erb</code>:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
</pre></td><td class='code'><pre><code class='erb'><span class='line'><span class="x"><html></span>
</span><span class='line'><span class="x"> <head></span>
</span><span class='line'><span class="x"> <title>Sample Web Form</title></span>
</span><span class='line'><span class="x"> </head></span>
</span><span class='line'><span class="x"><body></span>
</span><span class='line'>
</span><span class='line'><span class="x"><h1>Fill Out This Form</h1></span>
</span><span class='line'>
</span><span class='line'><span class="x"><form action="/hello" method="POST"></span>
</span><span class='line'><span class="x"> A Greeting: <input type="text" name="greet"></span>
</span><span class='line'><span class="x"> <br/></span>
</span><span class='line'><span class="x"> Your Name: <input type="text" name="name"></span>
</span><span class='line'><span class="x"> <br/></span>
</span><span class='line'><span class="x"> <input type="submit"></span>
</span><span class='line'><span class="x"></form></span>
</span><span class='line'>
</span><span class='line'><span class="x"></body></span>
</span><span class='line'><span class="x"></html></span>
</span></code></pre></td></tr></table></div></figure>
<p>然後將 <code>lib/gothonsweb.rb</code>改成這樣:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="n">require_relative</span> <span class="s2">"gothonweb/version"</span>
</span><span class='line'><span class="nb">require</span> <span class="s2">"sinatra"</span>
</span><span class='line'><span class="nb">require</span> <span class="s2">"erb"</span>
</span><span class='line'>
</span><span class='line'><span class="k">module</span> <span class="nn">Gothonweb</span>
</span><span class='line'>
</span><span class='line'> <span class="n">get</span> <span class="s1">'/'</span> <span class="k">do</span>
</span><span class='line'> <span class="n">greeting</span> <span class="o">=</span> <span class="s2">"Hello, World!"</span>
</span><span class='line'> <span class="n">erb</span> <span class="ss">:index</span><span class="p">,</span> <span class="ss">:locals</span> <span class="o">=></span> <span class="p">{</span><span class="ss">:greeting</span> <span class="o">=></span> <span class="n">greeting</span><span class="p">}</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'> <span class="n">get</span> <span class="s1">'/hello'</span> <span class="k">do</span>
</span><span class='line'> <span class="n">erb</span> <span class="ss">:hello_form</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'> <span class="n">post</span> <span class="s1">'/hello'</span> <span class="k">do</span>
</span><span class='line'> <span class="n">greeting</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">params</span><span class="o">[</span><span class="ss">:greet</span><span class="o">]</span> <span class="o">||</span> <span class="s2">"Hello"</span><span class="si">}</span><span class="s2">, </span><span class="si">#{</span><span class="n">params</span><span class="o">[</span><span class="ss">:name</span><span class="o">]</span> <span class="o">||</span> <span class="s2">"Nobody"</span><span class="si">}</span><span class="s2">"</span>
</span><span class='line'> <span class="n">erb</span> <span class="ss">:index</span><span class="p">,</span> <span class="ss">:locals</span> <span class="o">=></span> <span class="p">{</span><span class="ss">:greeting</span> <span class="o">=></span> <span class="n">greeting</span><span class="p">}</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<p>都寫好以後,重啟 web 程式,然後通過你的瀏覽器訪問它。</p>
<p>這回你會看到一個表單,它要求你輸入「一個問候語句(A Greeting)」和「你的名字(Your Name)」,等你輸入完後點擊「提交(Submit)」按鈕,它就會輸出一個正常的問候頁面,不過這一次你的URL還是 http://localhost:4567/hello,並沒有添加參數進去。</p>
<p>在<code>hello_form.erb</code> 裡面關鍵的一行是<code><form action="/hello" method="POST"></code>,它告訴你的瀏覽器以下內容:</p>
<ol>
<li>從表單中的各個欄位收集使用者輸入的資料。</li>
<li>讓瀏覽器使用一種POST類型的請求,將這些資料發送給服務器。這是另外一種瀏覽器請求,它會將表單欄位「隱藏」起來。</li>
<li>將這個請求發送至<code>/hello</code> URL,這是由<code>action="/hello"</code>告訴瀏覽器的。</li>
<li>你可以看到兩段<code><input></code>標籤的名字屬性(name)和程式碼中的變數是對應的,另外我們在 class index 中使用的不再只是 GET 方法,而是另一個 POST 方法。</li>
</ol>
<p>這個新程式的運作原理如下:</p>
<ol>
<li>瀏覽器訪問到 web 程式的 <code>/hello</code> 目錄,它發送了一個 GET 請求,於是我們的 <code>get '/hello/</code> 就運行了並傳回了hello_form。</li>
<li>你填好了瀏覽器的表單,然後瀏覽器依照<code><form></code>中的要求,將資料通過POST 請求的方式發給web程式。</li>
<li>Web 程式運行了 <code>post '/hello'</code> 而不是不是 <code>get '/hello/</code>來處理這個請求。</li>
<li>這個 <code>post '/hello'</code>完成了它正常的功能,將 <code>hello</code> 頁面返回,這裡並沒有新的東西,只是一個新函式名稱而已。</li>
</ol>
<p>作為練習,在 <code>lib/views/index.erb</code> 中添加一個鏈接,讓它指向 <code>/hello</code>,這樣你可以反覆填寫並提交表單查看結果。確認你可以解釋清楚這個鏈接的工作原理,以及它是如何讓你實現在 <code>lib/views/index.erb</code> 和<code>lib/views/hello_form.erb</code>之間循環跳轉的,還有就是要明白你新修改過的 Ruby 程式碼,你需要知道在什麼情況下會運行到哪一部分程式碼。</p>
<h2>Creating A Layout Template</h2>
<p>在你下一節練習建立遊戲的過程中,你需要建立很多的小 HTML 頁面。如果你每次都寫一個完整的網頁,你會很快感覺到厭煩的。幸運的是你可以建立一個「外觀 (layout」模板,也就是一種提供了通用的 headers 和 footers 的外殼模板,你可以用它將你所有的其他網頁包裹起來。好程式設計師會盡可能減少重複動作,所以要做一個好程式設計師,使用外觀模板是很重要的。</p>
<p>將 <code>lib/views/index.erb</code> 修改成這樣:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='erb'><span class='line'><span class="cp"><%</span> <span class="k">if</span> <span class="n">greeting</span> <span class="cp">%></span><span class="x"></span>
</span><span class='line'><span class="x"> <p>I just wanted to say <em style="color: green; font-size: 2em;"></span><span class="cp"><%=</span> <span class="n">greeting</span> <span class="cp">%></span><span class="x"></em>.</span>
</span><span class='line'><span class="cp"><%</span> <span class="k">else</span> <span class="cp">%></span><span class="x"></span>
</span><span class='line'><span class="x"> <em>Hello</em>, world!</span>
</span><span class='line'><span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span><span class="x"></span>
</span></code></pre></td></tr></table></div></figure>
<p>然後把 <code>lib/views/hello_form.erb</code> 修改成這樣:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='erb'><span class='line'><span class="x"><h1>Fill Out This Form</h1></span>
</span><span class='line'>
</span><span class='line'><span class="x"><form action="/hello" method="POST"></span>
</span><span class='line'><span class="x"> A Greeting: <input type="text" name="greet"></span>
</span><span class='line'><span class="x"> <br/></span>
</span><span class='line'><span class="x"> Your Name: <input type="text" name="name"></span>
</span><span class='line'><span class="x"> <br/></span>
</span><span class='line'><span class="x"> <input type="submit"></span>
</span><span class='line'><span class="x"></form></span>
</span></code></pre></td></tr></table></div></figure>
<p>面這些修改的目的,是將每一個頁面頂部和底部的反覆用到的「樣板 (boilerplate)」程式碼剝掉。這些被剝掉的程式碼會被放到一個單獨的<code>lib/views/layout.erb</code> 檔案中,從此以後,這些反覆用到的程式碼就由<code>lib/views/layout.erb</code> 來提供了。</p>
<p>上面的都改好以後,建立一個 <code>lib/views/layout.erb</code> 檔案,內容如下:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='erb'><span class='line'><span class="x"><html></span>
</span><span class='line'><span class="x"> <head></span>
</span><span class='line'><span class="x"> <title>Gothons From Planet Percal #25</title></span>
</span><span class='line'><span class="x"> </head></span>
</span><span class='line'><span class="x"> <body></span>
</span><span class='line'><span class="x"> </span><span class="cp"><%=</span> <span class="k">yield</span> <span class="cp">%></span><span class="x"></span>
</span><span class='line'><span class="x"> </body></span>
</span><span class='line'><span class="x"></html></span>
</span></code></pre></td></tr></table></div></figure>
<p>Sinatra 預設會自動去找名字為 <code>layout</code> 的外觀模板,並且使用它作為其他模板的「基礎」模板。你也可以修改已經用作任何頁面的基礎模板的 template。重啟你的程式觀察一下,然後試著用各種方法修改你的layout模板,不要修改你別的模板,看看輸出會有什麼樣的變化。</p>
<h2>為表單撰寫自動測試程式碼</h2>
<p>使用瀏覽器測試 web 程式是很容易的,只要點刷新按鈕就可以了。不過畢竟我們是程式設計師嘛,如果我們可以寫一些程式碼來測試我們的程式,為什麼還要重複手動測試呢?接下來你要做的,就是為你的web 程式寫一個小測試。這會用到你在《習題47》學過的一些東西,如果你不記得的話,可以回去複習一下。</p>
<p>我已經為此建立了一個簡單的小函式,讓你判斷(assert) web程序的響應,這個函數有一個很合適的名字,就叫 <code>assert_response</code>。創建一個 <code>tests/tools.rb</code> 檔案,內容如下:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>