• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            posts - 15,comments - 21,trackbacks - 0
                  之前因為時間匆忙,大區里產生唯一ID只是簡單寫了一個方法,今天趁著有時間,把里面一些過程寫出來,也算是個總結。
                  首先說說需求,現在游戲里數據庫為了分散熱點,都采用分表方式,以前那種一張表AUTO_INCREMENT的方式顯然不行了,那么將唯一ID放到業務進程里可行嗎?這個也不好,因為現在后臺都是多個部署,每個進程可以保證ID唯一,但是存到數據庫的時候就可能重復,所以我們還是得在DB上做文章。
                  因此就有了上篇文章的解決方案,單獨一張表,這張表的作用就是專門產生唯一ID,而且為多個業務提供唯一ID,多進程情況下也不需要鎖表,效率比較高,是不是很像設計模式里的工廠。接著我來分析下這張表。我們再來看看表結構和用法
            create table SeqTab 
               iSeqNo bigint(20) not null default 0,   //表示唯一ID
               iSeqType int(11) not null default 0,   //表示業務ID
               primary key(iSeqNo,iSeqType));
               insert into SeqTab values(0,1);        //初始化,假設一個業務編號為1
               update SeqTab set iSeqNo=last_insert_id(iSeqNo+1) where iSeqType=1;
               select last_insert_id();                   //這兩句是獲取一個業務的唯一ID,供業務進程使用。
               其實說到這張表,關鍵是說LAST_INSERT_ID()這個方法。它有兩種形式LAST_INSERT_ID(),LAST_INSERT_ID(expr)。
               這個東西的特點是,1.屬于每個CONNECTION,CONNECTION之間相互不會影響 2.不屬于某個具體的表 3.返回最后一次INSERT AUTO_INCREMENT的值 4.假如以此使用INSERT插入    多行數據,只返回第一行數據產生的值 5.如果你UPDATE某個AUTO_INCREMENT的值,不會影響LAST_INSERT_ID()返回值
               LAST_INSERT_ID(expr)與LAST_INSERT_ID()稍有不同,首先它返回expr的值,其次它的返回值會記錄在LAST_INSERT_ID()。
               我們這里主要使用到了第一和第二個特點,每個進程并發執行update SeqTab set iSeqNo=last_insert_id(iSeqNo+1) where iSeqType=1;,就獲取屬于進程自己的iSeqNo并且記錄在 LAST_INSERT_ID中,通過第二句取出該值。   
                接著我們在比較下其他一些辦法。
                第一種是直接一張表,里面有一個ID字段,設置成AUTO_INCREMENT。這個的問題是每個業務ID不是連續的,是離散的。
                第二種是使用GUID或者UUID,但是這個問題我個人覺得是效率上的差異,字符串沒有數字的效率好,另外數字ID今后也可以拼接一些區信息,之后跨區的時候可以方便獲取對象是哪個區的.
            posted @ 2012-12-06 23:01 梨樹陽光 閱讀(2221) | 評論 (2)編輯 收藏
            Mysql中除了使用auto_increment字段作為自增序號以外 還有另外一種辦法 可以提供唯一序列號并且可以由一張表完成對多個序列
            要求的滿足。
            大致如下:
            1.創建一個簡單表 
               create table SeqTab
               ( iSeqNo int not null default 0,
                 iSeqType int not null default 0);
            2.插入一調記錄
               insert into SeqTab values(0,13);  // 0表示SeqNo初始值,13為SeqType,同一張表可對多個應用提供Seq序列服務
            3.不管多進程 or 單進程對SeqTab表同時進行訪問,使用一下方法獲取序列號
               1st->update SeqTab set iSeqNo=last_insert_id(iSeqNo+1) where iSeqType=13;
               2nd->select last_insert_id();
            4.因多進程對SeqTab同時訪問,進行update SeqTab set iSeqNo=last_insert_id(iSeqNo+1);時,數據庫保證了update的事務
            完整性,且last_insert_id()是與當前mysql連接相關的,所以多進程同時使用時,也不會沖突。

            posted @ 2012-12-05 11:54 梨樹陽光 閱讀(1712) | 評論 (2)編輯 收藏
                  相信大家在開發后臺的過程中都遇到過中文亂碼的問題,今天我就來講講其中的原因。
                  我這建了3張表,test_latin1,test_utf8,test_gbk,表結構如下
                  +-------+----------+------+-----+---------+-------+
                  | Field | Type     | Null | Key | Default | Extra |
                  +-------+----------+------+-----+---------+-------+
                  | name  | char(32) | YES  |     | NULL    |       |
                  +-------+----------+------+-----+---------+-------+

                  我的前端是gbk的編碼
                  執行下面的語句
                  set names 'latin1'
                  insert into test_latin1 set name='王';('王'字是GBK編碼)
                  select name from test_latin1;
                  結果是否為亂碼?

                  執行下面的語句
                  set names 'gbk'
                  insert into test_latin1 set name='王';('王'字是GBK編碼)
                  select name from test_latin1;
                  結果是否為亂碼?

                  執行下面的語句
                  set names 'latin1'
                  insert into test_utf8 set name='王';('王'字是GBK編碼)
                  select name from test_utf8 ;
                  結果是否為亂碼?

                  我們舉個例子,假設一個漢字的字符編碼為0xFFFF,它在屏幕上能夠正常顯示,如果漢字存入數據庫的時候和從數據庫中取出的時候,編碼一致,那么它肯定不是亂碼。反過來,如果輸出的時候是亂碼,那么它肯定被轉碼了,至于為什么被轉碼了,我們得看看mysql里面做了什么(mysql難道會把無碼片變成了有碼片?)
                  首先mysql里面有2個概念,一個叫character set,一個叫collation。我們先說說character set。字符集就是數字,英文字符,漢字等編碼格式,我們常見的是utf8,gbk,gb2312。mysql里面比較復雜,有4個東西跟它有關,分別是character_set_client,character_set_connection,character_set_database,character_set_results。set names (latin1)其實就是character_set_client=latin1,character_set_connection=latin1,character_set_results=latin1,它的流程是character_set_client ==> character_set_connection ==> Table Character ==> character_set_results。
                  我們按照上面的流程,來分析第一個問題。
                  set names 'latin1'----執行了character_set_client=latin1,character_set_connection=latin1,character_set_results=latin1;
                  insert into test_latin1 set name='王';這句話,mysql做了什么事呢?首先,character_set_client,它會把王字的編碼當成latin1的編碼傳遞給character_set_connection(此時不會轉碼),character_set_connection會把編碼傳遞給Table Character,因為表本身是latin1,所以此時也不需要轉碼,select name from test_latin1;mysql會把test_latin1中的編碼傳遞給前端,此時也不需要轉碼,所以,走個流程下來,我們輸入的是什么編碼,輸出的還是相同的編碼,因此,第一個問題的答案是不會是亂碼。我畫個流程圖latin1==>latin1==>latin1==>latin1,沒有轉碼的過程
                  
                  我們在來看第二個問題。
                  set names 'test_gbk'----執行了character_set_client=gbk,character_set_connection=gbk,character_set_results=gbk;
                  insert into test_latin1 set name='王';character_set_client,它會把王字的編碼當成gbk的編碼傳遞給character_set_connection(此時不會轉碼),character_set_connection會把編碼傳遞給Table Character,因為表是lanti1的編碼格式,這個過程的時候就會進行轉碼,但是latin1的字符集小于gbk的字符集,所以它會找不到對應字符的編碼,此時會以?代替。select name from test_latin1,此時會從latin1轉碼成gbk,但是此時latin1已經是錯誤的數據了,所以得到的gbk編碼也是錯誤的了。流程gbk==>gbk==>latin1==>gbk,其中gbk==>latin1出了問題,我們select出來的數據也就不可能是輸入時候的數據了。因此,這個問題的答案是亂碼。

                  第三個。
                  set names 'test_latin1'
                  insert into test_utf8 set name='王';character_set_client,它會把王字的編碼當成latin1的編碼傳遞給character_set_connection(此時不會轉碼),character_set_connection會把編碼傳遞給Table Character,此時表是utf8的格式,因此會進行轉碼,latin1==>utf8,因為utf8的字符集>latin1字符集,因此,轉碼正常。select name from test_utf8;會從utf8轉碼成latin1,此時可以轉碼成功,因此我們最終得到的和輸入的時候是一致的,因此答案不是亂碼。流程latin1==>latin1==>utf8==>latin1,從小的字符集到大的字符集再到小的字符集,轉碼是不會有問題的。
                  屁話了這么多,無非想告訴大家一個萬精油方法,表創建的字符集和set names都設置成同一個字符集,就基本可以滿足輸入數據不會在轉換過程中失真,也就是說輸入是什么,輸出就是什么。建議有中文的都設置成utf8字符集,一勞永逸。
            posted @ 2012-11-26 19:56 梨樹陽光 閱讀(2538) | 評論 (2)編輯 收藏

            這是Riot的Design Director Tom Cadwell專門為中國玩家寫的講解匹配系統工作原理的帖子。


            同時為了讓大家更好的理解匹配系統,如果您覺得您遇到了特別不公平的匹配,請回復游戲開始時間和比賽結束截圖,我們會調查該局匹配是如何完成的,坑爹的玩家是為何加入到這一局的。


            很多人抱怨看不懂,我來個精簡比喻版的:

            有個籃球聯盟,有無數個球員和大概20個等級的聯賽。


            所有球員都是10級聯賽的成員,他們自由組合互相比賽,贏的人,升級到11級聯賽,輸的人降到9級聯賽。


            然后每個等級聯賽再次開賽,又有的人升級有的人降級,最終這20級的聯賽都有球員參加。


            我們的大量的數據證明,一個球員的水平,會讓其穩定在大約3個聯賽之間,也就是科比是參加20級聯賽的,且當他和4個17級聯賽的人組隊,基本不會輸給17級聯賽的人。且,把科比降到10級聯賽,他會輕松的在20局之內回到20級。


            理想情況下,球員都是在跟自己同樣經歷的球員玩,一個中等水平玩家完全不會匹配到科比,科比也不會匹配到剛玩游戲的玩家。


            事實上匹配系統的分級會比這個更復雜更智能,采用的是國際象棋所采用的elo系統。


            再增加個FAQ:

            Q:系統為了保持勝率50%,是否會在我連勝后故意塞給我一些菜隊友讓我輸?

            A:系統的目的不是為了保持你的勝率,而是讓水平差不多的玩家一起玩。當你和水平差不多的玩家一起玩時勝率會趨近50%,所以,系統是不會故意坑你的。


            Q:我才100勝,為什么系統老匹配600勝的玩家給我?

            A:勝場并不能反應一個人的水平。如果把匹配系統比作跑步,練習了3年才能跑進11秒的和第一次就跑進11秒的人我們是同等看待的。匹配系統基于水平而不是基于經驗。


            Q:我勝率60%,為什么匹配40%勝率的隊友、60%勝率的對手給我?

            A:勝率也不能反映水平。匹配系統不但要看你是否贏了,也要看你贏了誰。就像war3的sky在職業圈勝率其實并不高,但是虐一般的玩家勝率是100%。同樣水平的玩家,會因為隨機匹配到對手的關系,勝率會40%~60%不等。


            Q:你說水平差不多,為什么我覺得他們這么菜?

            A:匹配系統提供的是公平的機會,而未必是你理想的結果。我們能追求系統公正,但是無法預測玩家單局內的表現。

            系統100%匹配曼聯對陣皇馬,但是不能保證某一次曼聯不會4:0碾壓皇馬,且在這局中,C羅表現yts,完全就在拖后腿。或者曼聯也可能連勝皇馬3次之類的。但是,系統只會把曼聯去匹配皇馬而不會出現曼聯對陣中超深圳隊。具體到某一局是皇馬贏還是曼聯贏取決于那一場的排兵布陣,臨場發揮,以及戰術意圖。


            如果這個坑爹玩家真的不在你的水平等級,他就會一直坑隊友,一直輸,等級一直降低,這樣會讓他離開你的匹配范圍,讓他不再可以和你匹配到。根據我們的數據,玩家的elo基本是穩定在較小范圍內的。這也就是深圳隊和皇馬的差距,也是中國國家隊能贏法國隊,確永遠打不進世界杯的理由。


            系統沒辦法給你完美隊友,玩家會因為很多原因發揮不好:使用不會的英雄、打了不想打的位置、玩法風格和隊友不夠搭配,前期不利想掛機等等。但是你和對方玩家遇到這種情況的概率是相同的,系統并不會偏袒任何一方。所以想要完美隊友,請和朋友組隊,不過那樣你也會碰見更厲害的對手。


            如果大家在如何鑒定玩家水平上有好的想法歡迎提,但是如果想要通過抱怨玩家游戲內表現來證明匹配系統不公平,就是在和風車決斗了。每個人的看法都不一樣的,系統判斷他和你在遇到同樣的隊友和對手時候,勝率差不多,這也是我們目前能做到最好的了。




            以下是文章的正文。


            概述:

            匹配系統的目的如下,優先級從高到低:


            1、  保護新手不被有經驗的玩家虐;讓高手局中沒有新手。

            2、  創造競技和公平的游戲對局,使玩家的游戲樂趣最大化。

            3、  無需等待太久就能找到對手進入游戲。



            匹配系統盡其所能的匹配水平接近的玩家,玩家的水平是來自他們在此之前贏了誰以及他們對手的水平。當你戰勝對手,系統會認為你更強,當你輸給對手,系統會認為你更弱。雖然這對于某一局游戲并不是那么的公平,但是長期來看,對于多局游戲是相當的公平:因為好的玩家總會對游戲結果造成正面的、積極的影響。我們使用了這樣一個方法測試:給水平高的玩家一個新帳號,然后看他們游戲數局后的結果。我們通過大量的測試來證明了我們的想法。



            并且,匹配系統知道預先組隊的玩家有一些優勢,如果你是預先組隊,會給你一些更強的玩家。我們用一些非常巧妙的數學方法來解決預先組隊的玩家VS solo玩家的匹配公平問題。我甚至讓兩個數學博士來驗證,他們都說給力!



            匹配是怎么完成的?


            首先,系統將你放進適當的匹配池里——根據游戲模式(匹配模式、排位solo/雙人、排位5人、其他模式等等)


            然后,系統會嘗試將匹配池里的人分到更細的匹配池里——5人組隊 VS 5人組隊,低等級新手 vs 其他一些低等級新手,如此這般。


            當你在匹配池中,系統會開始嘗試找到合適的配對,目標是撮合一個雙方獲勝機會都為50%的游戲。



            第1步:確定你的實力:


            *如果你是solo,就直接使用你的個人匹配分(也就是elo值,匹配模式和排位賽有不同的匹配分)

            *如果你是預先組隊的,你的匹配分是你隊伍的平均分,并且會根據你組隊的規模稍微提高一些,這樣才能保證你匹配到更強的對手來抵消你組隊的優勢。我和一個計算機生物學的博士(Computational Biology Ph.D)通過研究成百上千的游戲結果,計算出了預先組隊到底有多大的優勢。我們還在幕后做了一些其他調整,比如新手和高玩組隊,比如某地圖上藍隊和紫隊的玩家哪個更有優勢,諸如此類。



            第2步:確定你合適的對手:


            *首先,系統會基于你的elo值,給你匹配跟你非常相近的玩家。最終,系統會放寬匹配的條件,給你一些不是那么完美的匹配,因為你肯定也不想永遠匹配不到人。

            *新手會得到一些特殊的保護,通常新手只會匹配到其他新手(在成熟的服務器里,這個比例達到了99%+。除非這個新手和一個高級玩家朋友預先組隊)



            第3步:確定匹配:


            *最終,系統會匹配10個大體上同水平、同等級的玩家,促成一個游戲。

            *系統會嘗試平衡這個隊伍,盡量使雙方的獲勝機會都為50%。在絕大多數時間,誤差會在3%之內——類似50/50,49/51,48/52。實際上的獲勝機會會有一點點差別(會在Q&A里面回答這個問題),但是我們的研究標明,在絕大多數情況下,這實際上是一個非常精確的預測。



            長期來講,我的匹配分(Elo值)是如何被測量的?

            我們使用了一個修改過的ELO系統。ELO系統的基本要點通過使用數學比較兩個人的積分,來預測兩人的比賽結果——類似“A和B比賽數局,A會贏掉75%的局”。



            然后,比賽結果出來了。如果你贏了,你會加分,如果你輸了,你會被扣分。如果你是“出人意料”的贏了(系統認為你輸的可能性更大),你會贏得更多的分數。額外的,如果你是一個新玩家,你會加分減分更快,以便于你可以快速的進入到你的水平等級。長期來看,這意味著好的玩家會得到高的匹配分,因為他們總是超過系統的預期,他們會不斷加分直到系統可以正確的預測他們的勝率。



            我們修改這個系統給團隊比賽使用,基本概念是:基于該團隊的所有玩家,得到一個團隊ELO值。如果你的隊伍勝利,系統會假設該隊伍的所有玩家都要比系統猜測的“更強”,并且加分。雖然有一些問題,但是總體上來講是有效的,特別是玩家預先組隊的時候。



            舉例,本人在北美的服務器上有2000的普通匹配模式elo。如果我建一個小號,就算沒有天賦和符文,我打到8級的時候就已經有1800elo了。這個系統并不完美,但是確實能夠讓玩家快速的接近自己水平所在的位置。



            當你才開始玩的時候,我們也對ELO做一些微調,讓你更快的進入你水平所在的位置。

            *我們有大量的,有優先級的方法來鑒定一個玩家,相比一個標準的新玩家是否更有技巧,更猛。如果發現是的,我們會在幕后提高他的elo一個檔次。

            *我們同樣也會分辨真的菜鳥新手。

            *提升等級也會極大的提高你的elo值。這個也將幫助系統將30級滿級的召喚師和低等級的召喚師區分開來



            如果你想知道ELO系統的理論,以及更多細節,你可以看看這:

            http://en.wikipedia.org/wiki/Elo_rating_system

            http://zh.wikipedia.org/wiki/ELO



            呃,等等,你是怎么處理組隊玩家 vs solo(單排)玩家的?

            我們大多數情況下,會通過將5人組隊的隊伍匹配給另外一個5人組隊的隊伍來避免這種情況的發生(幾乎是所有情況下)。



            對于“部分”組隊,我們進行了大量的研究,發現優勢并沒有想象的那么大,所以我們也會把他們混到solo(單排)的玩家里。我們發現有大量的因素會影響到組隊優勢的大小:從預先組隊的規模(比如2、3、4、5組隊),到組隊玩家的水平,到高玩帶菜鳥的組合,到玩家水平不同而導致的情況不同,以及其他的一些必須考慮到的微妙因素。這個要比一些我們曾見過的點對點算法-將任意的統計數據雜糅在一起猜測分數-要可靠的多 

            發現這些優勢,我們就知道對于預先組隊的隊伍,需要提高多少elo值,來達成一個公平的匹配,確定一個適當的,在數學上合理的調整。結果在有些情況下非常令人驚訝(同時會校正統計數據)。



            雖然我們不會給出精確的數值,因為這是商業機密,但是我們可以告訴您:

            *5人組隊只是比5個路人稍強。

            *部分組隊只是比5個路人略強。

            *菜鳥5人組隊并不會帶來太大的優勢,但是高玩組隊會有很大的優勢。

            *團隊實力方差高的隊伍,會比方差低的隊伍更強。(方差簡單來說,是在平均值相同的情況下反應各個元素的大小差異,方差大表示差異大,高方差的隊伍類似高玩帶低玩,低方差的隊伍各個隊員實力接近。)

            *這說明了大體上,高水平玩家的Carry作用(可以理解為帶領或者大腿),比低水平玩家的送人頭作用(feeder)要強力。



            好吧…那為什么要把預先組隊的玩家和非組隊玩家匹配到一起?

            這是一些原因:


            *這會幫助系統更快的找到適合你的匹配分,讓系統更快的給你公平的匹配。這個的工作原理是,如果你組隊,會減低運氣所帶來的成分,如果你單排,你的隊友的好壞將對你輸贏的影響更大。如果你預先組隊,你會和你水平差不多的玩家組成隊伍,你隨機遇到猛男/坑爹隊友幾率會更小。因為游戲的結果更多來自你和水平相近的朋友的表現,而不是隨機因素,所以你的匹配分會更快的到達精確的值。

            *我們希望玩家可以和自己的朋友一起玩,因為這樣會讓他們玩的更有樂趣。你也不可能為5v5的游戲設置單獨的2人匹配池或者3人匹配池,你需要組合他們來讓系統工作。我們選擇包含5人組隊,因為這非常有樂趣。如果我們以后有足夠大的匹配池,我們可能會將5人組隊和部分組隊區分開來,但是數據告訴我們,這基本不會提升匹配的公平程度,兩者的效果基本相同。



            其他一些常見的問題:


            Q為什么不加入一些其他的細節,類似擊殺數等等來確定我的匹配分?

            A因為這是有偏差的,并且因為非常難以給擊殺數這個數值來評分,你使用一個gank英雄的時候(類似老鼠和易大師),要殺多少人才能算是好的呢?而且這會讓好的輔助玩家非常吃虧,因為他們的目的就不是拿人頭,甚至會為了自己的Carry擋死。最后,玩家會為了刷數據,故意拖長游戲時間,然后拿大量farm對方的人頭,而不是為了贏得比賽。我們盡量把測量玩家水平和激勵玩家的機制放到努力取勝上面,我們避免了一些不必要的周邊行為,而這些行為既沒樂趣,還會擾亂匹配系統。



            Q我非常憤怒,因為匹配系統老給我坑爹隊友(feeders,送人頭的)。為什么不阻止這種情況發生?

            A我們的確有試圖阻止這種情況發生,但是如果你被匹配到一個明顯很弱的玩家,這也說明匹配系統同時匹配給你了一個或者多個強力的玩家。根據我們的研究,我們發現Carry(大腿)對隊伍的帶領作用要比feeder(送人頭,坑爹)的坑爹作用更強。原因是在LOL里,多次擊殺同一個玩家的收益是會遞減的,并不像其他的同類游戲。我們的分析標明,在平均elo相同的情況下,提高或者降低這個隊伍的某個玩家的elo值100(其他玩家相應降低/提高以保持平均分相同),整個隊伍的實力會提高約7點elo值。這也表明,LOL中Carry的作用要比feeder的作用更給力一些。確實,有時候你會因為匹配到feeder而輸掉這一局比賽,但是那是因為你們隊的Carry不夠給力。



            Q這樣的話,如果我連勝了數盤,我是不是會被匹配到一些完全不可戰勝的對手?

            A不全是。連勝導致你的匹配分會提高,你會不斷遇到更強的對手——但是我們并不是故意的讓你的勝率保持在50%的,我們的目的只是為了系統能夠正確的預測游戲結果。最終,你會達到你的極限,你將會大致保持50%的勝率。比平均水平高的玩家,往往勝率會比50%略高,因為比他們弱的玩家更多,比他們強的玩家更少。所以匹配時,往往會略微“向下匹配”。對于排位頂尖的高端玩家,他們經常會有90%的勝率。



            Q你們會如何設計固定的隊伍?類似WOW的競技場隊伍?

            A這是一個非常好的想法,并且讓我們有機會設計出更好的匹配系統。我們遲早會做這個,并且使用我們開發的新方法。我們需要檢驗并且搞清楚你大體上有多強力(例如你的個人積分),同時允許你創建/解散隊伍。這是個非常大的工程,但是我們對此非常有激情~



            Q如果匹配系統真的那么公平,那為何我老遇見那種一邊倒的比賽?

            A有兩個原因。第一,LOL有時候“雪球效應”會非常明顯。前期太差的表現會導致游戲讓人感覺非常一邊倒。特別是某些隊伍,如果他們開始很順風,就會一直很順風。我們遇到過同樣的隊伍,第一局25-5取勝,第2局確以類似的比分輸掉。第二個原因是,玩家發揮的并不好,隊伍選取陣容也不好。要進行一局勢均力敵的比賽,你需要平衡玩家水平和平衡陣容的選取。有時候玩家選了一個比較渣的陣容,比如5個近戰dps,或者3坦克2法師之類的,或者沒選打野英雄而對面有。這樣的話,盡管你的隊伍實力也很不錯,但是情況往往慘不忍睹。



            Q為什么我作為一個高等級玩家,有時候會匹配到一些低等級玩家?他們看上去都是來送人頭的。

            A當一個高等級玩家和一個低等級玩家組隊,這是一個非常令人頭疼的問題。我們希望玩家可以和自己的朋友一起玩,并且希望這是一種愉快的體驗。但是我們并不希望將一部分人的快樂建立在另一部分人的痛苦之上,所以我們往往將這種組合評分更高,保護新玩家不會被高等級玩家虐待。非常不幸的是,不管我們怎么做,我們把這樣的組合匹配到任何的游戲中,都有可能造成不愉快的體驗。因此,我們計劃將實施一個“不平衡組隊”的隊列,類似我們盡量將5人組隊匹配給5人組隊。



            Q我20級了,然后我被匹配到了一些10級的和一些29級的,怎么回事?

            A當不同等級的玩家組隊,我們會使用他們的平均等級來作為匹配的參考。等級并不是匹配系統的主導參數——匹配系統通常是使用實力來匹配——但是我們也會盡量將等級相近的玩家匹配到一起。在預先組隊的情況下,我們沒法替玩家選擇,所以我們盡我們所能,使用平均等級。我們會在這個計算系統里把30級的玩家看作36級,所以我們通常能讓中等級玩家的游戲沒有30級玩家,然而有時候呢,29級玩家能插進來。

            posted @ 2012-11-09 14:20 梨樹陽光 閱讀(1612) | 評論 (1)編輯 收藏
            記錄下這個小問題。root下可以顯示路徑但是切換到其他用戶時沒有路徑顯示,很不方便。
            在/etc/profile中加入
            export PS1='\u@\h:\w>',其中\u顯示當前用戶賬號,\h顯示當前主機名,\W顯示當前路徑。然后source一下就搞定了
            posted @ 2012-11-08 11:49 梨樹陽光 閱讀(1389) | 評論 (0)編輯 收藏
            開門見山,我先提出幾個問題,大家可以先想想,然后我再說出我的方法
            1.如何判斷一個數M是否為2的N次方?
            2.一個數N,如何得到一個數是M,M是不小于N的最小2的K次方

            先說第一個問題,我有兩個思路
            第一,可以通過判斷M的二進制中1的個數。而判斷M中1的個數可以通過下面方法獲得
            int GetOneCnt(int m) 
            {
                if ( m == 0 )
                    return 0;
                
                int cnt = 1;
                while(m & (m-1))
                {
                    cnt++;
                    m--;
                }
                
                return cnt;
            }
            很明顯M中1的個數為1和M是2的N次方互為沖要條件
            第二個思路,我們可以這樣,還是利用M的二進制表示,從最高位開始,以變量high_pos表示第一個1的下標,接著從最低位開始,變量low_pos表示第一個1的下標,如果high_pos=low_pos,則M為2的N次方
            int HighestBitSet(int input)
            {
                register int result;

                if (input == 0)
                {
                    return  -1;
                }

            #ifdef WIN32
                _asm bsr eax, input
                _asm mov result, eax
            #else
                asm("bsr %1, %%eax;"
                "movl %%eax, %0"
                :"=r"(result)
                :"r"(input)
                :"%eax");
            #endif

                return result;
            }
            int LowestBitSet(int input)
            {
                register int result;

                if (input == 0)
                {
                    return  -1;
                }

            #ifdef WIN32
                _asm bsf eax, input
                _asm mov result, eax
            #else
                asm("bsf %1, %%eax;"
                "movl %%eax, %0"
                :"=r"(result)
                :"r"(input)
                :"%eax");
            #endif

                return result;
            }

            再說第二個問題
            其實有了第一個問題的思路,這個問題就更好解決了,先判斷一個數是否為2^N,如果是,直接返回,否則返回2^(N+1)
            代碼如下
            int CeilingPowerOfTwo(int iInput)
            {
                if (iInput <= 1)
                    return  1;

                int32_t highestBit = HighestBitSet(iInput);
                int32_t mask = iInput & ((1 << highestBit) - 1); // 相當于input對2^highestBit求余
                highestBit += ( mask > 0 );

                return (1<<highestBit);
            }
            posted @ 2012-10-01 15:53 梨樹陽光 閱讀(1369) | 評論 (4)編輯 收藏
            如果你對能很快回答出unicode和utf-8的關系,你可以直接跳過這篇文章。下面我來說說兩者的關系和轉換。(本文使用符號2字代表所有的漢字,英文,數字等)
            首先明確一點,UTF-8是UNICODE一種實現方式。
            UNICODE:代表一種符號集合,它規定了一種符合的二進制表示,沒有指明存儲方式。(http://www.unicode.org/)
            UTF-8:實現了UNICODE,使用多字節的存儲方式。
            我們先來考慮幾個問題。
            第一,如果使用單字節表示符號,很明顯,完全不夠用
            第二,如果使用多字節表示符號,那么,機器在讀取的時候,它怎么知道3個字節表示一個符號,還是表示3個符號
            第三,如果使用2個字節表示一個符號,首先,最多能表示65535個字符還是會不夠用,就算夠用,比如ASCII碼這類僅需1個字節就可以表示的符號,用2個字節表示,浪費空間了。
            因此,UTF-8孕育而生。
            首先UTF-8使用變長表示符號,簡單的說,有的時候用1個字節表示符號,有的時候用2個字節表示符號,這樣解決了浪費空間的問題。那么,如何解決第二個問題的呢,我們得了解下UFT-8的編碼規則。
            1.對于單字節的符號,字節第一個為0,后面7為為這個符號的unicode碼
            2.對于N字節的符號(N>1),第一個字節前N位為1,第N+1位為0,后面字節的前兩位設為10,剩下可編碼的位,為該符號的UNICODE編碼。
            這里我從網上找了一副圖
                           Unicode符號范圍 | UTF-8編碼方式
                                    (十六進制) | (二進制)
            0000 0000-0000 007F | 0xxxxxxx
            0000 0080-0000 07FF | 110xxxxx 10xxxxxx
            0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
            0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
            下面我具體解釋下這幅圖。
            首先看第一行,它的意思是00000000到0000007F的UNICODE編碼,對應的UTF-8的編碼方式為0XXXXXXX(X表示可編碼位,不足的補0)。
            第二行表示00000080到000007FF的UNICODE編碼,對應的UTF-8的編碼方式為110XXXXX 10XXXXXX。以此類推
            那么,問題是,這個范圍是怎么定的?
            很簡單,我們還是從第一行說起。007F,實際有效位只有7位,所以,0xxxxxxx就足矣。但是0800開始,有效位至少為8位,我們得增加一個字節,按照UTF-8的規定,2字節的表示方式為110XXXXX 10XXXXXX,我們的編碼位為11位(X的個數),所以,我們最多可以表示UNICODE編碼位11位的字符,也就是07FF。07FF過了就是0800,有效位至少為12位,我們得用3字節來表示,按照UTF-8的規定,1110XXXX 10XXXXXX 10XXXXXX,最大編碼位為16位,也就是FFFF,最后一行我就不再解釋了。
            通過上面這個過程我們了解了,UNICODE轉UTF-8的過程,當然,逆過來就是UTF-8轉換成UNICODE。
            我們通過一個例子來演示上面的過程。漢字“楊”,UNICODE的編碼位0x6768,二進制形式為0110011101101000,根據上面的圖,我們知道它屬于第三行,因此,它應該放入1110XXXX 10XXXXXX 10XXXXXX的模板中,結果是11100110 10011101 10101000,十六進制表示為E69DA8。
            另外設計編碼問題,我們繞不開另一個問題,就是大端小端的問題,不過這個問題,網上資料很多,也很好實踐,這里我就不多啰嗦了。
            posted @ 2012-09-23 22:56 梨樹陽光 閱讀(1791) | 評論 (1)編輯 收藏
            進程間通信方式包括了管道,消息隊列,FIFO,共享內存,而共享內存是其中效率最高的。下圖解釋了其效率最高的原因(圖片截取自《UNIX網絡編程》)

            我們可以看到上面的拷貝次數是4次,下面則是2次。
            接下來我們看看使用,其實網上和書上都有了很多資料,API就是那么幾個。
            int shmget(key_t key,size_t size,int shmflag)。
            key:可以指定IPC_PRIVATE,那么系統會為你創建一個key,并且返回一個id號。也可以通過ftok函數生成一個key(不了解ftok的童鞋可以動手man一下)。那么為什么要一個IPC既要一個key又有一個ID呢。這里我覺得是為了方便其他進程訪問。進程A創建了一個共享內存,內核為其分配一個ID,這個時候進程B想要訪問,他怎么獲取這個ID(難道需要A把ID發送給B??)。但是我們用一個key就很方便了。事先A,B進程都知道這個key,那么A創建了,B就可以通過事先知道key找到這塊內存。
            size:共享內存的大小。如果是獲得一塊內存,則該值應該為0。
            shmflag:讀寫權限的組合,可以與IPC_CREAT和IPC_EXCL按位或。當指定一個key時,IPC_CREAT和IPC_EXCL配合使用可以在存在該key的共享內存時返回-1。
            當該函數調用成功后,返回一個系統分配的共享內存,并且size大小的字節被初始化為0

            void *shmat(int shmid, const void *shmaddr, int shmflg)
            有了一塊共享內存后,進程需要映射該內存到進程的地址空間。這個函數就是作用。
            shmid就是之前獲得ID,shmaddr如果指定了,就會配合shmflg確定映射地址,不過一般都不這么干的。返回值就是獲得的地址,你可以往里面寫你或者讀你需要的數據了。同時調用了該函數,系統會修改shmid_ds數據。

            int shmdt(const void *shmaddr)
            解除綁定關系,參數就是我們之前獲取的那個返回地址。(其實我覺得這里參數如果為ID貌似更統一些吧)

            int shmctl(int shmid, int cmd, struct shmid_ds *buf)
            這個函數主要做一些修改和查詢。比如設置鎖,解鎖,移除一塊共享內存。

            簡單介紹了API,還要說一下一些注意的東西
            1.共享內存不會把數據寫入磁盤文件中,這個區別于mmap
            2.即使沒有進程綁定在共享內存,共享內存也不會消失的。必須通過shmctl或者ipcrm刪除(或者更暴力的方式關掉電腦)

            另外我們可能會考慮,系統最多創建多少個共享內存,一個進程最多可以綁定多少個內存,一個共享內存創建的size最大最小值是多少。其實這些設置在/proc/sys/kernel下面,我們也可以自己寫程序來讀取。貼一段代碼用來獲取上面的信息
            #define MAX_SHMIDS        8196

            int main(int argc,char *argv[])
            {
                int i,j;
                int shmid[MAX_SHMIDS] = {0};
                void *addr[MAX_SHMIDS] = {0};
                
                //測試可以創建多少個共享內存
                for ( i = 0;i < MAX_SHMIDS;++i )
                {
                    shmid[i] = shmget(IPC_PRIVATE,1024,0666|IPC_CREAT);
                    if ( shmid[i] == -1 )
                    {
                        printf("create shared memory failed,max create num[%d],%s\r\n",i,strerror(errno));
                        break;
                    }
                }
                
                for ( int j = 0;j < i;++j )
                {
                    shmctl(shmid[j],IPC_RMID,NULL);
                }
                
                //測試每個進程可以attach的最大數
                for ( i = 0;i < MAX_SHMIDS;++i )
                {
                    shmid[i] = shmget(IPC_PRIVATE,1024,0666|IPC_CREAT);
                    if ( shmid[i] != -1 )
                    {
                        addr[i] = shmat(shmid[i],0,0);
                        if ( addr[i] == (void *)-1 )
                        {
                            printf("process attach shared memory failed,max num[%d],%s\r\n",i,strerror(errno));
                            shmctl(shmid[i],IPC_RMID,NULL);
                            break;
                        }
                    }
                    else
                    {
                        printf("max num of process attach shared memory is[%d]\r\n",i-1);
                        break;
                    }
                }
                
                for ( j = 0;j < i;++j )
                {
                    shmdt(addr[j]);
                    shmctl(shmid[j],IPC_RMID,NULL);
                }
                
                //測試一個共享內存創建最小的size
                size_t size = 0;
                for ( ;;size++ )
                {
                    shmid[0] = shmget(IPC_PRIVATE,size,0666|IPC_CREAT);
                    if ( shmid[0] != -1 )
                    {
                        printf("create shared memory succeed,min size[%d]\r\n",size);
                        shmctl(shmid[0],IPC_RMID,NULL);
                        break;
                    }
                }
                
                //測試共享內存創建最大的size
                for ( size = 65536;;size += 1024 )
                {
                    shmid[0] = shmget(IPC_PRIVATE,size,0666|IPC_CREAT);
                    if ( shmid[0] == -1 )
                    {
                        printf("create shared memory failed,max size[%ld],%s\r\n",size,strerror(errno));
                        break;
                    }
                    
                    shmctl(shmid[0],IPC_RMID,NULL);
                }
                
                exit(0);
            }
            好了,下篇開始介紹如何控制讀寫。
            posted @ 2012-09-06 19:26 梨樹陽光 閱讀(2233) | 評論 (0)編輯 收藏

            1.拷貝構造函數的形式

            對于類X,如果它的函數形式如下

            a) X&

            b) const X&

            c) volatile X&

            d) const volatile X&

            且沒有其他參數或其他參數都有默認值,那么這個函數是拷貝構造函數

            X::X(const X&);是拷貝構造函數

            X::X(const X&,int val = 10);是拷貝構造函數

             

            2.一個類中可以存在超過一個拷貝構造函數

            class X {      

            public:      

              X(const X&);      

              X(X&);            // OK   

            }; 

            編譯器根據實際情況調用const拷貝構造函數或非const的拷貝構造函數

             

            3.默認的拷貝構造函數行為

            a)先調用父類的拷貝構造函數

            b)如果數據成員為一個類的實例,則調用該類的拷貝構造函數

            c)其他成員按位拷貝

             

            4.默認的賦值構造函數行為

            a)先調用父類的賦值構造函數

            b)如果數據成員為一個類的實例,則調用該類的賦值構造函數

             c)其他成員按位拷貝

             

            5.提供顯示的拷貝和賦值構造函數

            基本的原則是子類一定要調用父類的相應函數,參考方式

            Derive(const Derive& obj):Base(obj)

            {

            …...

            }

             

            Derive& operator =(const Derive &obj)

            {

            if ( this == &obj )

            return *this;

             

            //方式一

            Base::operator =(obj);

             

            //方式二

            static_cast<Base&>(*this) = obj;

            return *this;

            }

            另外當你的成員變量有const或者引用,系統無法為你提供默認的拷貝和賦值構造函數,我們必須自己處理這些特殊的情況

            posted @ 2012-08-31 17:13 梨樹陽光 閱讀(1723) | 評論 (0)編輯 收藏
            我們在類里面會定義成員函數,同時我們也希望定義成員函數指針。因此需要解決3個問題,第一怎么定義類的函數指針,第二賦值,第三使用。
            我定義一個類,
            class A
            {
            public:
                
            int add(int,int);
                
            int mul(int,int);
                
            int div(int,int);
            };

            int A::add(int a,int b)
            {
                
            return a + b;
            }

            int A::mul(int a,int b)
            {
                
            return a * b;
            }

            int A::div(int a,int b)
            {
                
            return (b !=0 ? a/b : a);
            }
            我現在想要定義一個指針,指向A類型中返回值為int,參數為兩個int的函數指針。熟悉C的同學馬上會寫出typedef int (*oper)(int,int)。但是這個用到C++里就有問題了,
            因為我們知道,類的非static成員方法實際上還有this指針的參數。比如add方法,它實際的定義可能會這樣int add(A* this,int a,int b);因此,我們不能簡單把C語言里的那套東西搬過來,我們需要重新定義這種類型的指針。正確做法是加上類型,typedef int (A::*Action)(int,int)
            typedef int (A::* Action)(int,int);

            int main()
            {
                A a;
                Action p 
            = &A::add;
                cout 
            << (a.*p)(1,2<< endl;
                
            return 0;
            }
            Action p = &A::add;這個就是賦值語句
            調用的時候注意,直接這樣(*p)(1,2)是不行的,它必須綁定到一個對象上
            (a.*p)(1,2);我們也可以把類的函數指針定義在類的聲明里。
            class A
            {
            public:
                
            int add(int,int);
                
            int mul(int,int);
                
            int div(int,int);

                
            int (A::*oper)(int,int);
            };
            上面這種方式是針對非static成員函數的,那么static成員函數的函數指針應該怎么定義呢,還能用上面這種方式嗎?我們知道static成員函數是沒有this指針的,所以不會加上的類的限定,它的函數指針定義方式和C里面的函數指針定義方式一樣的。
            class A
            {
            public:
                
            int add(int,int);
                
            int mul(int,int);
                
            int div(int,int);

                
            static int sub(int,int);
            };

            int A::sub(int a,int b)
            {
                
            return a - b;
            }

            int main()
            {
                
            int (*pp)(int,int= &A::sub;
                cout 
            << (*pp)(10,5<< endl;
                
                
            return 0;
            }
            總結起來,非static成員函數的函數指針需要加上類名,而static的成員函數的函數指針和普通的函數指針一樣,沒有什么區別。
            另外注意一點的是
            如果函數指針定義在類中,調用的時候有點區別。
            class A
            {
            public:

                
            int add(int,int);
                
            int mul(int,int);
                
            int div(int,int);

                
            int (A::*pfunc)(int,int);
            };

            int main()
            {
                A a;
                a.pfunc 
            = &A::add;
                cout 
            << (a.*(a.pfunc))(1,2<< endl;
                
            return 0;
            }
            posted @ 2012-08-14 17:17 梨樹陽光 閱讀(1411) | 評論 (0)編輯 收藏
            僅列出標題  下一頁
            91精品国产乱码久久久久久| 久久嫩草影院免费看夜色| 一本久久综合亚洲鲁鲁五月天亚洲欧美一区二区 | 久久综合九色综合欧美就去吻| 久久这里只有精品视频99| 亚洲狠狠婷婷综合久久蜜芽| 丰满少妇高潮惨叫久久久| 久久精品国产第一区二区| 亚洲欧洲日产国码无码久久99| 久久久久亚洲AV片无码下载蜜桃| 久久精品国产亚洲AV高清热 | 区亚洲欧美一级久久精品亚洲精品成人网久久久久 | 精品国产99久久久久久麻豆| 狠狠色丁香久久婷婷综合图片| 亚洲国产精品无码久久一区二区 | 久久婷婷是五月综合色狠狠| 久久人人妻人人爽人人爽| 国产精品久久久久久久午夜片 | 精品国产一区二区三区久久久狼 | 国产成人精品久久一区二区三区| 色偷偷91久久综合噜噜噜噜| 久久久久久久99精品免费观看| 久久久久亚洲AV无码去区首| 成人资源影音先锋久久资源网| 久久久久国产日韩精品网站| 97久久精品国产精品青草| 色妞色综合久久夜夜| 亚洲精品无码久久一线| 久久亚洲精品人成综合网| 日本欧美国产精品第一页久久| 国产精品VIDEOSSEX久久发布| 午夜精品久久久久久毛片| 中文字幕亚洲综合久久菠萝蜜| 亚洲一区中文字幕久久| 国产精品无码久久综合| 午夜精品久久久久久毛片| 蜜臀久久99精品久久久久久小说| 久久婷婷色香五月综合激情 | 日本五月天婷久久网站| 武侠古典久久婷婷狼人伊人| 久久久青草青青国产亚洲免观|