React的井字過三關(2)

来源:http://www.cnblogs.com/djtao/archive/2016/12/23/6213473.html
-Advertisement-
Play Games

這篇筆記是官方教程的延續筆記,所有代碼基於第一篇筆記的結尾代碼。旨在解決教程後面提出的五個問題。 一 . 用(X,Y)來取代原有的數字坐標 原來的數字坐標是這樣的: 現在的要求是把原來的代碼坐標改為二維坐標系的表達形式,並且在歷史記錄面板中打出轉換後的坐標。 如果只是為了輸出好看。只需要寫一個轉換方 ...


這篇筆記是官方教程的延續筆記,所有代碼基於第一篇筆記的結尾代碼。旨在解決教程後面提出的五個問題。


一 . 用(X,Y)來取代原有的數字坐標

原來的數字坐標是這樣的:

現在的要求是把原來的代碼坐標改為二維坐標系的表達形式,並且在歷史記錄面板中打出轉換後的坐標。

如果只是為了輸出好看。只需要寫一個轉換方法,這些在頂層的Game組件中實現就夠了。而不需要修改原來的代碼核心。

很自然想到用switch語句,其實都行,怎麼喜歡怎麼寫。

convert:function(i){//i是一維坐標
                var x=Math.floor(i/3)+1;
                var y=0;
                if((i+1)%3===0){
                    y=3;
                }else{
                    y=(i+1)%3;
                }
                return [x,y];
            },

調用就直接在渲染前調用。把它存到state裡面去。

接下來問題是把這個方法怎麼獲取參數i,最直接的辦法是寫一個全局變數,然後從handleClick裡面拿到i。但是全局變數不環保。或許再設一個頂層狀態lastLocation是個不錯的選擇,渲染隊列是一個數組,姑且稱之為historyLocation

根據React的價值觀,能根據其它原有狀態計算出來的東西,就不需要設置額外的狀態。但如果思路不明確,就在這裡先寫出來。

回退步驟的本質

在上一篇筆記最後,官方文檔沒有說清楚一個問題。就是狀態中的stepNumber是什麼。現在再次遇到,需要寫明白給自己提個醒。

回退步驟

每走一步,history狀態就會在最後追加一個最新版本。

stepNumber實際上是一個指針,根據這個指針,發送history狀態的版本(可能是舊的,也可能是最新的),用它來調控渲染狀態。

點擊回退步驟,就是把指針往前挪。

通過暫存器刷新狀態

如果沒有任何其它操作直接觸發handleClick,stepNumber指針直接指向最新的版本。

如果在回退步驟上發生了handleClick,那麼將發生以下事情:

  • 根據指針生成若幹個狀態暫存器,這個暫存器是獨立且不具備任何效力的,拋開環境來看就是普通變數;
  • 追加新的狀態到暫存器;
  • 再用這個暫存器替換掉原有的狀態,在此,回退步驟列表將被刷新。

究竟有幾個狀態暫存器?在這裡就兩個:

  • 一個儲存history的當前指針版本:

javascript handleClick:function(i){ //history指針版本暫存 var history=this.state.history.slice(0,this.state.stepNumber+1); var lastHistory=history[this.state.stepNumber]; var squares=lastHistory.squares.slice();

  • 一個儲存二維坐標軸版本:

javascript /**上接handleClick***/ //二維坐標數據暫存器 var historyLocation=this.state.historyLocation; historyLocation=historyLocation.slice(0,this.state.stepNumber); historyLocation.push(this.convert(i));//刷新狀態暫存器

通過狀態暫存器,既可以在指針位置重新開始,又能在屏幕上保留歷史步驟數方便查看,即實現官方文檔所謂的“時間旅行”。

在這裡意識到狀態暫存器其實應該只有一個是最好的。

judgeWinner的完善

按理來說,判斷勝負的函數judgeWinner應該是在組件的裡面,這樣比較環保,也可以更好地調用組件中的狀態。

現在就把它拿進來。直接生成渲染方法中的status。並且添加一個和棋的判斷。實現思路是調用指針版本的history狀態數組。然後遍歷這個數組對象,如果發現9個位置全部不為null,就返回和棋。

放進來之後,參數也沒有必要再寫,全部改為state相關的表達。

這樣一來就沒辦法用原來的禁著點判斷了。因為不好判斷棋局是否完結。在此根據返回的結果進行indexOf判斷,留下的坑後面填。

所以到此為止,Game組件應該是:

getInitialState:function(){
                return {
                    history:[
                        {squares:Array(9).fill(null)}
                    ],
                    turnToX:true,
                    stepNumber:0,
                    historyLocation:[]
                }
            },
            // 判斷勝負的函數,窮舉法
            judgeWinner:function(){
                var history=this.state.history;
                var lastHistory=history[this.state.stepNumber];
                var squares=lastHistory.squares;
                var win=[
                    [0,1,2],
                    [0,3,6],
                    [0,4,8],
                    [1,4,7],
                    [2,5,8],
                    [2,4,6],
                    [3,4,5],
                    [6,7,8]
                ];
                for(var i=0;i<win.length;i++){
                    var winCase=win[i];
                    if(squares[winCase[0]]&&squares[winCase[0]]===squares[winCase[1]]&&squares[winCase[1]]===squares[winCase[2]]){//三子一線
                        return ('獲勝方是:'+squares[winCase[0]]);//返回勝利一方的標識
                    }
                }

                // 定義當前棋盤上被填滿的格子數量
                var fill=lastHistory.squares.filter((item)=>item!=null).length;
                if(fill==9){
                    return '和棋!'
                }else{
                    var player=this.state.turnToX?'X':'O';
                    return ('輪到'+player+'走');
                }
            },
            // 點擊事件是把暫存器的內容存為真正的狀態。
            handleClick:function(i){
                //歷史squares暫存
                var history=this.state.history;
                history=history.slice(0,this.state.stepNumber+1);

                var lastHistory=history[this.state.stepNumber];
                var winner=this.judgeWinner();
                var squares=lastHistory.squares.slice();

                //歷史步驟暫存器
                var historyLocation=this.state.historyLocation;
                historyLocation=historyLocation.slice(0,this.state.stepNumber);
                historyLocation.push(this.convert(i));

                if((winner.indexOf('輪到')==-1)||squares[i]){
                    return false;
                    //勝負已分或是已有子則不可落子。indexOf這是一種暫時的非主流寫法
                }
                // 判斷下棋的輪換色
                squares[i]=this.state.turnToX?'X':'O';

                this.setState({
                    history:history.concat([{squares:squares}]),
                    turnToX:!this.state.turnToX,
                    stepNumber:history.length,
                    historyLocation:historyLocation
                });

            },
            // 歷史步驟跳轉是把狀態還原到某個時間點,狀態根據stepNumber呈現內容,但不會改變最終狀態。
            jumpTo:function(step){
                this.setState({
                    stepNumber:step,
                    turnToX:step%2?false:true
                });
            },
            // 坐標轉換函數
            convert:function(i){
                var x=Math.floor(i/3)+1;
                var y=0;
                if((i+1)%3===0){
                    y=3;
                }else{
                    y=(i+1)%3;
                }
                return [x,y];
            },

            render:function(){
                var history=this.state.history.slice();
                var lastHistory=history[this.state.stepNumber];//渲染方法遵照的是stepNumber而不是最後一步
                var status=this.judgeWinner();//獲勝狀態
                var arr=[];
                var location=this.state.historyLocation.slice();
                var _this=this;
                history.map(function(step,move){
                    var content='';
                    if(move!==0){
                        content='Move#'+move+':'+'('+location[move-1][0]+','+location[move-1][1]+')';
                        //console.log(location[move-1])
                    }else{
                        content='游戲開始~';
                    }
                    arr.push(<li key={move}><a onClick={()=>_this.jumpTo(move)} href="javascript:;">{content}</a></li>);
                });

                return (
                    <div className="game">
                        <Board lastHistory={lastHistory.squares} onClick={(i)=>this.handleClick(i)} />
                        <div className="info">
                            <div>{status}</div>
                            <ol>{arr}</ol>
                        </div>
                    </div>
                );
            }
        });

二. 對右方的被選中的當前記錄進行加粗顯示

樣式這種東西,就交給CSS來實現吧!

.back-active{
    font-weight: bolder;
    color: #EE9611;
}

簡單實現

思路就是加個class。操作方法是jumpTo。

問題在於,當前的jumpTo已經給定了參數。為了拿到e.target還得在改改。

jumpTo在這個問題中實際上要完成兩件事,刪除所有a的class中可能.back-active;給當前對象加個.back-active

有了e.target,就能用DOM找到該有的內容。比方說e.target.parentNode.parentNode.childNode就代表所有點擊對象上層的所有li集合

然而這個集合不是一個數組啊,不能map。只能用for迴圈。根據查到的性能資料,for迴圈還真的比其它迭代方法高。

jumpTo:function(e,step){
                // console.log(e.target)
                var aList=e.target.parentNode.parentNode.childNodes;
                for(var i=0;i<aList.length;i++){
                    var item=aList[i];
                    if(item.childNodes[0].classList.contains('back-active')){
                        item.childNodes[0].classList.remove('back-active');
                    }
                }

                e.target.classList.add('back-active');
                this.setState({
                    stepNumber:step,
                    turnToX:step%2?false:true
                });
            },

這個問題就算解決了。

點擊實現高亮當前的步驟

其實就個人理解來說,不應該再對handleClick再加什麼高亮當前步驟的操作了。當前步驟明擺著就是最後一個。縱觀就當前的代碼實現,用戶體驗已經很好了,進程不會亂七八糟,用戶還可以很清晰地知道指針指向的還原點。還高亮什麼?

但是假設老闆就要求點擊按鈕時最後一步也高亮,那也只能照做。

顯然,這個應該放渲染前判斷:如果這是狀態最後一步(是this.state.history.length-1,不是this.state.stepNumber),那麼就高亮。反正樣式也不要錢,就多寫一個樣式給它。

.process-active{
    font-weight: bolder;
    color: green;
}/*寫在.back-active之後,方便覆蓋*/

這樣,渲染前的處理里還得多加一個判斷:是最後一個就加.process-active——這段獲取歷史步驟的方法已經變得太長了。為了閱讀方便把它放一個getMoveList函數里吧。

...
    getMoveList:function(){
                var history=this.state.history.slice();
                var arr=[];
                var location=this.state.historyLocation.slice();
                var _this=this;
                history.map(function(step,move){
                    var content='';
                    if(move!==0){
                        content='Move#'+move+':'+'('+location[move-1][0]+','+location[move-1][1]+')';
                        //console.log(location[move-1])
                    }else{
                        content='游戲開始~';
                    }
                    //console.log(_this.state.stepNumber)
                    if(arr.length==_this.state.history.length-1){
                        arr.push(<li key={move}><a className="process-active" onClick={(e)=>_this.jumpTo(e,move)} href="javascript:;">{content}</a></li>);
                    }else{
                        arr.push(<li key={move}><a onClick={(e)=>_this.jumpTo(e,move)} href="javascript:;">{content}</a></li>);
                    }
                });
                return arr;
            },
              ...

這樣,第二個問題就解決了。


三. 用兩個迴圈重寫Board組件,替代掉原來生硬的代碼結構

因為只有9宮格,復用也毫無意義。所以寫死也問題不大。

想到的處理方法就是這樣了。

var Board=React.createClass({
            renderSquare:function(i){
                return <Square key={i} value={this.props.lastHistory[i]} onClick={() => this.props.onClick(i)} />
            },
            getSquare:function(rows){
                var index=rows*3;
                var arr=[];
                for(var i=index;i<index+3;i++){
                    arr.push(this.renderSquare(i));
                }
                return arr;
            },
            getBoardRow:function(){
                var arr=[];
                for(var i=0;i<3;i++){
                    arr.push(<div key={i} className="board-row">{this.getSquare(i)}</div>)
                }
                return arr;
            },
            render:function(){
                return (
                    <div clasName="board">
                        <div className="status"></div>
                        {this.getBoardRow()}
                    </div>
                );
            }
        });

四. 對你的歷史記錄進行升降序排列

接下來又回到Game組件上面來了。在渲染結構中加一個按鈕。點擊,觸發事件。大概就是這樣。

<input type="button" value={this.state.isAscending} onClick={this.sortToggle} />

因為預設就是降序,因此這個toggleSort只做一件事:切換開關。至於是升序還是降序,又要多設置一個開關狀態(isAscending,初始為降序排列)。

根據這個狀態,getMoveList方法決定生成數組後是直接return還是return arr.reverse()

sortToggle:function(){
    this.setState(function(prevState){
        var sort=prevState.isAscending;
        var content='';
        if(sort=='升序排列'){
            content='降序排列';
        }else{
            content='升序排列'
        }
        return {
            isAscending:content
        }
    })
},

然後再到getMoveList方法的最後,加一個判斷:

.....
                if(this.state.isAscending=='降序排列'){
                    return arr;
                }else{
                    return arr.reverse();
                }
}

嗯,第四個問題解決。


五. 高亮顯示獲勝的結果

擴展judgeWinner的功能

judgeWinner判斷函數已經被納入到了組件中,而且只是返回一個status,現在要擴展它的功能,把勝負情況反應出來。

在原來的判斷勝負函數裡面加個console就可以知道勝負手了。

for(var i=0;i<win.length;i++){
                    var winCase=win[i];
                    if(squares[winCase[0]]&&squares[winCase[0]]===squares[winCase[1]]&&squares[winCase[1]]===squares[winCase[2]]){//三子一線
                        console.log(winCase)//這裡的winCase就是勝負手
                        return ('獲勝方是:'+squares[winCase[0]]);//返回勝利一方的標識
                    }
                }

既然是擴展功能,再來大改就沒必要了。可以考慮把return一個字元串改為return一個數組。第0項放status,第1項放winCase或null

有了這個方法,handleClick中那種非主流寫法就可以刪掉了。

                var winner=this.judgeWinner();
                if(winner[1]||squares[i]){
                    return false;
                    //勝負已分或已有子:則不可落子。
                }

傳遞勝負手

再寫一個 CSS

.win-case{
    color: red;
}

現在可以通過winner[1]拿到勝負手了。它是一個數組。現在就得在Game組件render方法裡面在var一個數據。通過props傳下去,傳到Board組件之後,做一個判斷,看看參數是否符合點位條件,是的話就繼續把class名傳下去。

/********<Game/>*******/
render:function{
    var winCase=this.judgeWinner()[1];//獲勝狀態
    return (
        <div className="game">
        <Board winCase={winCase} lastHistory={lastHistory.squares} onClick={(i)=>this.handleClick(i)} />
...
/**********<Board/>***********/
renderSquare:function(i){
                if(this.props.winCase){
                    for(var j=0;j<this.props.winCase.length;j++){
                        if(this.props.winCase[j]==i){
                            return <Square winCase="win-case" key={i} value={this.props.lastHistory[i]} onClick={() => this.props.onClick(i)} />
                        }
                    }
                }
                return <Square key={i} value={this.props.lastHistory[i]} onClick={() => this.props.onClick(i)} />
            },
...
/************<square/>******************/
  var Square=React.createClass({
              render:function(){
                  if(this.props.winCase){
                      return (
                          <button className={"square "+this.props.winCase} onClick={() => this.props.onClick()}>{this.props.value}</button>
                      );
                  }else{
                      return (
                          <button className="square" onClick={() => this.props.onClick()}>{this.props.value}</button>
                      );
                  }
              }
        });
。。。。。。

那麼第五個問題就完成了。


結束

現在,功能已經完備。思路已經理清。再看之前留下的大坑:historyLocation

之前提到過,historyLocation是可以和history相互計算得出的。historyLocation只用於展示步數。組件的判斷引擎是用相容history的一維數組實現的,為了後期實現AI書寫方便,也顯然是history更好。還是刪掉這個historyLocation。

不好之處在於每次都要多一點計算,相比React每次動輒重新渲染,這點計算也不是很多。

寫一個根據history獲取坐標的方法,拿到坐標之後在轉換為二維坐標,這本質上是一件事,所以convert方法也可以刪掉了。

getRectangular:function(){
                var arr=[];
                var mainArr=this.state.history.slice();
                for(var i=0;i<mainArr.length;i++){
                    if(i<mainArr.length-1){
                        for(var j=0;j<9;j++){
                            //比較mainArr[i].squares和mainArr[i+1].squares[j])不同,拿到坐標值
                            if(mainArr[i].squares[j]!==mainArr[i+1].squares[j]){
                                arr.push(j);
                            }
                        }
                    }
                }
                var result=[]
                for(var i=0;i<arr.length;i++){
                    var x=Math.floor(arr[i]/3)+1;
                    var y=(arr[i]+1)%3===0?3:(arr[i]+1)%3;
                    result.push([x,y])
                }
                return result;
            },

可以再自己優化下演算法和css,或者加個重置button之類的。把不必要的變數刪掉。

效果:

下一篇筆記將解決最大的一個坑。


附錄:組件代碼

var Game=React.createClass({
            getInitialState:function(){
                return {
                    history:[
                        {squares:Array(9).fill(null)}
                    ],
                    turnToX:true,
                    stepNumber:0,
                    isAscending:'降序排列'
                }
            },
            // 判斷勝負的函數,窮舉法,返回一個數組,如果勝負已定,第二個元素就是勝負手
            judgeWinner:function(){
                var lastHistory=this.state.history[this.state.stepNumber];//獲取指針版本
                var squares=lastHistory.squares;
                var win=[
                    [0,1,2],
                    [0,3,6],
                    [0,4,8],
                    [1,4,7],
                    [2,5,8],
                    [2,4,6],
                    [3,4,5],
                    [6,7,8]
                ];
                for(var i=0;i<win.length;i++){
                    var winCase=win[i];
                    if(squares[winCase[0]]
                      &&squares[winCase[0]]===squares[winCase[1]]
                      &&squares[winCase[1]]===squares[winCase[2]]){//三子一線
                        return [('獲勝方是:'+squares[winCase[0]]),winCase];//返回一個status和勝負情況
                    }
                }
                // 獲取當前棋盤上被填滿的格子數量
                var fill=lastHistory.squares.filter((item)=>item!=null).length;
                if(fill==9){
                    return ['和棋!',null];
                }else{
                    var player=this.state.turnToX?'X':'O';
                    return [('輪到'+player+'走'),null];
                }
            },

            // 點擊事件是把暫存器的內容存為真正的狀態。
            handleClick:function(i){
                //history指針版本暫存
                var history=this.state.history.slice(0,this.state.stepNumber+1);
                var lastHistory=history[this.state.stepNumber];
                var squares=lastHistory.squares.slice();


                var winner=this.judgeWinner();
                if(winner[1]||squares[i]){
                    return false;
                    //勝負已分或是已有子則不可落子。
                }
                // 判斷下棋的輪換色
                squares[i]=this.state.turnToX?'X':'O';
                //覆蓋掉原來的狀態!
                this.setState({
                    history:history.concat([{squares:squares}]),
                    turnToX:!this.state.turnToX,
                    stepNumber:history.length
                });

            },
            // 轉化history狀態為各個版本的平面直角坐標
            getRectangular:function(){
                var arr=[];
                var mainArr=this.state.history.slice();
                for(var i=0;i<mainArr.length;i++){
                    if(i<mainArr.length-1){
                        for(var j=0;j<9;j++){
                            //比較mainArr[i].squares和mainArr[i+1].squares[j])不同,拿到坐標值
                            if(mainArr[i].squares[j]!==mainArr[i+1].squares[j]){
                                arr.push(j);
                            }
                        }
                    }
                }
                var result=[]
                for(var i=0;i<arr.length;i++){
                    var x=Math.floor(arr[i]/3)+1;
                    var y=(arr[i]+1)%3===0?3:(arr[i]+1)%3;
                    result.push([x,y])
                }
                return result;
            },
            // 歷史步驟跳轉是把狀態還原到某個時間點,狀態根據stepNumber呈現內容,但不會改變最終狀態。
            jumpTo:function(e,step){
                var aList=e.target.parentNode.parentNode.childNodes;
                for(var i=0;i<aList.length;i++){
                    var item=aList[i];
                    if(item.childNodes[0].classList.contains('back-active')){
                        item.childNodes[0].classList.remove('back-active');
                    }
                }
                
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Asp.Net MVC FilterAttribute特性、讀取xml反序列化、NLog實戰系列文章 首先新建一個MVC project。 一、NLog的配置。 作者:Jarosław Kowalski <[email protected]> 翻譯:CrazyCoder 原文:http://www ...
  • 背景:博主本是一位Windows桌面應用程式開發工程師,對網路通信一知半解。一日老婆逛完某寶,問:“為什麼他們知道我的地址呢,他們是怎麼獲取我的地址的呢?” 順著這個問題我們的探秘開始: 第一步:簡單的服務搭建 思路,通過HttpListener在本地搭建一個簡易的伺服器,開發程式為控制台介面,核心 ...
  • 筆者最近要負責有個項目工程網站的安裝進度過程,實現的效果要求大概如下圖所示 由於筆者沒有參與到資料庫的製作,得知他們這個項目設計工序的時候就一個開始日期的和完成日期,連整個項目的安裝結束時間都沒有簡直了。這裡公開一下我的資料庫 有點,總之就是說不出話的感覺。 之前筆者寫前臺table表綁定的時候一般 ...
  • 異常: 在可以調用 OLE 之前,必須將當前線程設置為單線程單元(STA)模式。請確保您的 Main 函數帶有 STAThreadAttribute 標記。 只有將調試器附加到該進程才會引發此異常。 方法1: 方法2: ...
  • 在我們編寫程式中,往往需要一些存儲過程,在LINQ to SQL中怎麼使用呢?也許比原來的更簡單些。下麵我們以NORTHWND.MDF資料庫中自帶的幾個存儲過程來理解一下。 1.標量返回 在資料庫中,有名為Customers Count By Region的存儲過程。該存儲過程返回顧客所在"WA"區 ...
  • 對象載入 延遲載入 在查詢某對象時,實際上你只查詢該對象。不會同時自動獲取這個對象。這就是延遲載入。 例如,您可能需要查看客戶數據和訂單數據。你最初不一定需要檢索與每個客戶有關的所有訂單數據。其優點是你可以使用延遲載入將額外信息的檢索操作延遲到你確實需要檢索它們時再進行。請看下麵的示例:檢索出來Cu ...
  • lpStatuss是一個 的指針類型實例,並包含SensorDust欄位 ...
  • 前言 馬上2016年就要過去了,時間可是真快啊。 上次寫完 Identity 系列之後,反響還不錯,所以本來打算寫一個 ASP.NET Core 中間件系列的,但是中間遇到了很多事情。首先是 NPOI 的移植工作,移植過後還有一些Bug需要修複,然後一個事情是一個有關於分散式架構中消息一致性的一個中 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...