勇者和魔王決鬥!…雖然現代很少勇者真的跟魔王單挑,都嘛帶著後宮圍毆…咳。
除了HP以外加入防禦力的要素,傷害為攻擊力減去防禦力。
傷害最小是1,被打死時不論最後一擊多痛,HP都只顯示為0而不顯示負數。
比之前複雜了些,但只是多加了一些設定,整體沒什麼改變。
首先來收集所有要素,雙方的HP、雙方的防禦,以及雙方的攻擊力…
int boss_hp, boss_atk, boss_def;
int hero_hp, hero_atk, hero_def;
int dmg; <== 表示傷害
有了這些要素,也必須先決定雙方能力,才能開戰!
boss_hp = 1000;
boss_atk = 100;
boss_def = 20;
hero_hp = 200;
hero_atk = 80;
hero_def = 90;
理所當然持續打到死為止…
所以雙方都活著 (HP大於0表示活著!) 就持續戰鬥
while(boss_hp > 0 && hero_hp > 0) <== 雙方都還活著!
{
<== 放戰鬥過程
}
戰鬥結束後,顯示結果。由上面條件可知,雙方都活著就會繼續。
所以停止時,一定是至少有一方死了。我們看哪一方活著。
你說可能兩方都死…沒錯,但改變戰鬥的流程就可以避免同歸於盡!
if(boss_hp > 0)
{
printf("boss win! remain %d hp\n", boss_hp);
}
if(hero_hp > 0)
{
printf("hero win! remain %d hp\n", hero_hp);
}
接著是剛剛暫且先欠著的戰鬥過程。為了對魔王較悲劇的能力值表示遺憾,讓魔王先攻…
printf("boss attack!\n");
printf("atk: %d, def: %d\n", boss_atk, hero_def); <== 由於出現了2個%d,要兩個數
排排隊第1對第1,第2對第2!
dmg = boss_atk - hero_def; <== 傷害 = 攻擊 - 防禦
if(dmg < 1) <== 如果傷害不到1,以1計算
{
dmg = 1;
}
hero_hp = hero_hp - dmg; <== 將HP減去傷害,得到新HP,並存回hero_hp中!記得先算!
if(hero_hp < 0) <== 如果被打超過剩餘HP,也只將HP顯示為0,不顯示為負數
{
hero_hp = 0;
}
printf("damage: %d!! hero remain %d hp\n", dmg, hero_hp); <== 算完再輸出剩餘量
if(hero_hp == 0) <== 如果死了,輸出訊息。但是屍體是不會攻擊的…
{
printf("hero died.\n");
}
else <== 如果沒死,反擊的時間到啦!同時這條件保證了不會兩人同歸於盡!
{
printf("hero counter attack!\n");
printf("atk: %d, def: %d\n", hero_atk, boss_def); <== 記得換攻守對象
dmg = hero_atk - boss_def;
if(dmg < 1)
{
dmg = 1;
}
boss_hp = boss_hp - dmg;
if(boss_hp < 0)
{
boss_hp = 0;
}
printf("damage: %d!! boss remain %d hp\n", dmg, boss_hp);
if(boss_hp == 0)
{
printf("boss died.\n");
}
}
嗚~哇!有沒有搞錯,我可還沒算進攻擊速度、攻擊技能、狀態異常以及道具等等東西耶,竟然寫起來就長得一塌糊塗啊!這幾行啦?太扯了吧!?
我只是加個防禦力,加個規則讓HP為-5566之類的奇怪負數血量不要出現,讓攻擊不要幫敵人補血,過份嗎?
而且複製貼上很累耶!還要記得把boss和hero反轉,漏改就尷尬了!有~夠煩的啦!
最重要的是複製過去後,因為是在if裡面,整個排版都爛掉了啦!
這樣我完美的層次感都沒了,整個程式碼更亂了!一行一行移到行首按tab又很崩潰…
………
……
…
哈哈,笨蛋才乖乖按tab啦!
試試看把想內縮的幾行反白起來,用滑鼠或是按住SHIFT後按上下左右都行!
再來按下神奇CTRL+SHIFT+I,MAGIC!沒錯,它內縮了!哇~根本作弊嘛!
相反的操作是CTRL+SHIFT+U,MAGIC!
將大括號內的程式碼往內縮一個tab (由於網頁關係是用四個空白,建議是用tab;番外篇可能會再提) 這種行為叫作縮排,是很重要的喔。程式碼會整齊很多!
更重要的是哪些是在if或while底下超~清楚的啦!
如果有必要請人幫忙看程式碼抓bug,或是和人合作寫程式,這可是基本禮貌喔!
好,讓我們回到正題:
我只是加個防禦力,加個規則讓HP為-5566之類的奇怪負數血量不要出現,讓攻擊不要幫敵人補血,過份嗎?
而且複製貼上很累耶!還要記得把boss和hero反轉,漏改就尷尬了!有~夠煩的啦!
雖然縮排沒問題了…啊~好麻煩啊…如果今天有隻像printf那種小妖精,一行就能幫我們處理掉麻煩事,該有多好?
假如有隻小妖精,把傷害告訴牠,如果不到1,會告訴我們傷害是1;把新血量告訴牠,如果是負數就告訴我們0;多方便啊…而且可以用好多次!同樣的程式碼不用一直複製貼上再修改,也不怕打錯啦!
問題是,沒有這樣的小妖精啊…假設我們比較喜歡質數,所以喜歡五輪車七輪車十一輪車,但這根本沒人想買啊!誰會專為賣你而量身打造?
怎麼辦?開玩笑,我們可是學巫術的耶!如果沒有無中生有的能力,敢抬頭挺胸說自己懂巫術嗎!?
再說啦,我們本就是為了心血來潮覺得很酷,就會自行造一台十三輪車的瘋…不,是偉大的巫師!
所以,讓我們來創造一隻屬於自己的小妖精吧!首先要先畫出設計圖,就是畫出創造我們想像中的小妖精的魔法陣:
int get_dmg(int atk, int def)
{
int dmg;
dmg = atk - def;
if(dmg < 1)
{
dmg = 1;
}
return dmg;
}
喔喔,看起來很容易嘛!而且似乎在哪兒看過…
不管!首先來分解一下:
int <== 小妖精的屬性!int代表整數!隨風而來、隨風而去,只留下一個整數~
get_dmg <== 幫小妖精取名字!名字很重要的,這會跟著牠一輩子,要慎重地取唷!
( <== 裡面放我們想告訴牠的訊息。如果沒什麼想說的,也可以留空!
int atk <== 想告訴牠的訊息,int是種類,atk是代號!
, <== 有多個訊息時,以逗號分隔之!
int def <== 想告訴牠的訊息之二,以def作為代號,和訊息atk區隔!
)
{ <== 大括號就放我們賦予這隻小妖精的能力!牠能做什麼取決於此
... <== 內容省略,反正寫法是一樣的
return dmg; <== 隨風而去時,記得留下數字!執行到return表示任務結束,回報後走人!
}
別急~看到return先別嚇到!程式不會因此結束,只代表小妖精任務結束喔!詳細容後說明~
小妖精正式的統稱是函式,但這種用法又稱「副程式」,屬於「程式中的子程式」。
所以也是程式,裡面的寫法和以往寫的程式一模一樣!
當程式很多地方都要做同一件事時,比起複製貼上10份,寫另一支子程式完成它要好得多!
需要牠時,就呼叫牠的大名,召喚牠來工作!工作結束後,牠就會依約在隨風而去之前,留下一個整數。
dmg = get_dmg(hero_atk, boss_def); <== 沒加()時表示提到「這隻小妖精」,
有加()表示請這隻小妖精「執行任務」!
整個get_dmg(hero_atk, boss_def)因為屬性關係,會代表一個整數!
依return決定抄什麼樣的整數回來。return抄了什麼整數,這串就代表什麼!
意思如下:
召喚名為get_dmg的小妖精進行工作,把hero_atk抄寫在atk上、boss_def抄寫在def上傳過去;工作完成後回報結果。
為何說是抄寫呢?因為啊,get_dmg是動不到hero_atk的喔!他只拿到一張抄著hero_atk現有值的紙,這張紙叫作atk,當然改不到原本的值啦!
在執行到召喚牠的指令時,程式會暫時停止,等這隻小妖精完成任務,程式才會繼續運作!通常需要先讓牠完成任務,否則後面的事做不下去。
程式是按順序一條一條指令完成的,沒有並行的概念! (關於平行的概念,番外篇預計會提)
就像接力賽,不會第一個人剛起跑,後面的人跟著也跑了;等接到棒子才會開始!
否則你在煮菜,請你妹幫忙切菜,結果他才剛開始切,你就把菜扔鍋裡了,肯定只切一半或根本還沒切啊!先完成前置工作,像是切菜,是很重要的!
此外,記得要先宣布牠的存在!要先讓程式在召喚牠之前,先畫出創造與召喚用魔法陣,並且對其命名,程式才會認得牠!
否則…就好像你對女朋友說我家小鈴好可愛好可愛、和牠在一起就有被治癒的感覺、甚至常常抱著一起裸睡之類的,他可能會以為你們之間有小三,而不知道你在講你家的貓,因為他不會讀心知道小鈴是你家貓名!
知道嚴重性了吧?所以在召喚之前,要先宣告身份才行!寫起來會像這樣:
#include <stdio.h>
int get_dmg(int atk, int def) <== 宣告在main之前,才能為main所知!但不可宣告在main內。
{
int dmg;
dmg = atk - def;
if(dmg < 1)
{
dmg = 1;
}
return dmg;
}
int main()
{
int boss_hp, boss_atk, boss_def;
int hero_hp, hero_atk, hero_def;
int dmg;
boss_hp = 1000;
boss_atk = 100;
boss_def = 20;
hero_hp = 200;
hero_atk = 80;
hero_def = 90;
while(boss_hp > 0 && hero_hp > 0)
{
printf("boss attack!\n");
printf("atk: %d, def: %d\n", boss_atk, hero_def);
dmg = get_dmg(boss_atk, hero_def); <== 改召喚小妖精進行計算!
hero_hp = hero_hp - dmg;
if(hero_hp < 0)
{
hero_hp = 0;
}
printf("damage: %d!! hero remain %d hp\n", dmg, hero_hp);
if(hero_hp == 0)
{
printf("hero died.\n");
}
else
{
printf("hero counter attack!\n");
printf("atk: %d, def: %d\n", hero_atk, boss_def);
dmg = get_dmg(hero_atk, boss_def); <== 只需要召喚小妖精,不用重寫!歡呼!
boss_hp = boss_hp - dmg;
if(boss_hp < 0)
{
boss_hp = 0;
}
printf("damage: %d!! boss remain %d hp\n", dmg, boss_hp);
if(boss_hp == 0)
{
printf("boss died.\n");
}
}
}
if(boss_hp > 0)
{
printf("boss win! remain %d hp\n", boss_hp);
}
if(hero_hp > 0)
{
printf("hero win! remain %d hp\n", hero_hp);
}
return 0;
}
太開心了,馬上按下F9來看看可愛乖巧的小妖精服從地做事的模樣吧,啊哈哈哈哈!
也許你會發現,小妖精的寫法跟起手式裡的main不是沒兩樣嗎?
沒錯!main也是小妖精喔!當世界一開始,做完前置處理,隨即世界就會召喚main小妖精執行任務;
執行完世界就結束了。所以牠是一切的開始,同時意味著一切的結束。
對小妖精而言執行到return代表任務結束。main內的return代表main結束任務,這同時意味著程式終止。
因此return真意並非程式終止,只是在main裡的return同時意味著程式的終止而已。
也許細心的人會發現: 有兩行int dmg;也就是dmg這傢伙被宣告了兩次!這樣沒有問題嗎?
而且小妖精就先做了dmg = atk - def;了,為何還要dmg = get_dmg(...)呢?
道理很簡單!小妖精認知中的dmg和main中的dmg根本不相同!
就像你認知的「我老婆」和我認知的「我老婆」不會是同一個人…理論上。說到「我家女僕」和你提及的「我家女僕」大概也不會是同一人,除非是住同一個家…嗯。
所以,小妖精get_dmg認知中的dmg和main認知的dmg不是同一個東西,自然中間就需要溝通!
即使小妖精的認知改變了,main認知中的dmg也不會跟著變!你交了新的女朋友,所以對你而言,女朋友從10變成20,可是對我而言,女朋友也不會因此從30變成40!除非變成是同一人…,也就是我原本女朋友是20,而你從10變成20;
但即使如此,在我這得到消息或被甩掉前會算是劈腿狀態,所以也不會變動,直到出現變動為止。
因此,在main的dmg正式改為get_dmg回報的值之前,它是不會改變的。
所以dmg = get_dmg(...)不是多餘的動作。
宣告在自己地盤中(也就是大括號中)的,稱為區域(或者私有)變數;宣告於最外層的稱為全域(共用)變數。
因此兩個dmg以位置而言,都是私下的宣稱,也就是只屬於個人的認知;像是家裡的女僕小菲,一樣的暱稱但八成指的不同人。
可是,如果是公開的宣稱的話,大家就會有共同的認知!比如說某某女僕桌遊店的小菲,一提到他,可能大家認知中的都是同一個人!
所以一旦被更改,所有人看到的都會被更改。
如果對某人而言,共同認知與私有認知重疊的話!這時私有認識會遮蔽共同認知喔!
這也很好理解,世界上共同認知覺得巨才是美,可是你覺得貧才是美;
世界上認知的好看是瘦,在你的認知中的好看卻是胖!
世界上認知的美人可能是林志玲之類,但你認知中的美人可能是你妹妹。
所以對於一個函式,當同時存在同名之區域及全域變數時,它看到的是區域變數。全域會被區域所遮蔽!
利用這個特性,我們可以把原本的程式碼,改成像這樣子!
仔細觀察看看和原本有何異同:
#include <stdio.h>
int dmg; <== 所有人的共同認知
int boss_hp, boss_atk, boss_def;
int hero_hp, hero_atk, hero_def;
int get_dmg(int atk, int def)
{
int dmg; <== 和共同認知互衝的私人認知;私下認知會蓋過共同認知
dmg = atk - def;
if(dmg < 1)
{
dmg = 1;
}
return dmg;
}
int adjust_hp(int hp, int dmg)
{
hp = hp - dmg;
if(hp < 0)
{
hp = 0;
}
return hp;
}
int hero_counter()
{
printf("hero counter attack!\n");
printf("atk: %d, def: %d\n", hero_atk, boss_def);
dmg = get_dmg(hero_atk, boss_def);
boss_hp = adjust_hp(boss_hp, dmg);
printf("damage: %d!! boss remain %d hp\n", dmg, boss_hp);
if(boss_hp == 0)
{
printf("boss died.\n");
}
return 7-11; <== 其實不需要回報什麼,所以隨便回報吧。7減11是負4。
}
int boss_attack()
{
printf("boss attack!\n");
printf("atk: %d, def: %d\n", boss_atk, hero_def);
dmg = get_dmg(boss_atk, hero_def);
hero_hp = adjust_hp(hero_hp, dmg);
printf("damage: %d!! hero remain %d hp\n", dmg, hero_hp);
return 7-11; <== 雖然沒必要時不回報也可以,但回報是好習慣!
}
int set_basic_power()
{
boss_hp = 1000;
boss_atk = 100;
boss_def = 20;
hero_hp = 200;
hero_atk = 80;
hero_def = 90;
return 7-11;
}
int main()
{
set_basic_power(); <== 召喚來工作,但是無視回報的結果
while(boss_hp > 0 && hero_hp > 0)
{
boss_attack();
if(hero_hp == 0)
{
printf("hero died.\n");
}
else
{
hero_counter();
}
}
if(boss_hp > 0)
{
printf("boss win! remain %d hp\n", boss_hp);
}
if(hero_hp > 0)
{
printf("hero win! remain %d hp\n", hero_hp);
}
return 0;
}
單看main裡面,是不是精簡易讀多了呢?在邏輯上非常清楚!如果把符號一同翻譯的話,幾乎能把整篇翻成中文喔!
依個人風格不同,也會寫出不一樣的程式碼!就像藝術品一樣,有的只是不同想法所引導出的表達,沒有絕對正確的解答!
如同前面所提的程式寫法: 先寫好每一步要做什麼,再一步一步去轉換成程式碼;考慮某一步時不考慮其它步!
這樣我們可以把大程式拆成許多小程式獨立完成,絕對輕鬆許多!
現在,我們可以先把問題切成一個個獨立的小問題,每個都寫成召喚小妖精的形式,之後只要集中心思創造每一隻小妖精,並注意牠們之間如何溝通就行了!
這樣會比一次想好整個程式怎麼寫,來得容易多了,不是嗎?
那麼,試著用自己的風格、自己的話語,重新用C語言述說一次如何解決此問題吧!然後試著思考,為什麼你想這樣寫、和我寫的差在哪裡?慢慢地關於程式要怎麼寫,就會有自己的想法。
也許一開始會因為看過這份程式碼,而帶有其影子;但不斷在理解後用自己的言語重新述說,慢慢就能擺脫、找出自己的風格!
學著自行創作、並對自己的程式負責,不要只是抄襲,才會確實地進步!即使必須從抄寫起步也一樣。
我們要成為的是能自行創作的藝術家,而非擅長抄抄改改東拼西湊的山寨專家!
記住,程式設計是一門針對問題和需求,在各方面寫法做出各種取捨的藝術與學問。
永遠沒有標準解,永遠沒有最佳解。只有取捨而已,一切端看需求而定!
沒有留言:
張貼留言