这篇笔记是官方教程的延续笔记,所有代码基于第一篇笔记的结尾代码。旨在解决教程后面提出的五个问题。


一 . 用(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的当前指针版本:

    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();
  • 一个储存二维坐标轴版本:

    /**上接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');
}
}
e.target.classList.add('back-active');
this.setState({
stepNumber:step,
turnToX:step%2?false:true
});
},
// 坐标转换函数
convert:function(i){
var x=Math.floor(i/3)+1;
var y=(i+1)%3===0?3:y=(i+1)%3; return [x,y];
},
// 获取历史步骤列表
getMoveList:function(){
var history=this.state.history.slice();
var arr=[];
var _this=this;
var rectangular=this.getRectangular();//获取二维坐标 history.forEach(function(step,move){
var content='';
if(move!==0){
content='Move#'+move+':'+'('+rectangular[move-1][0]+','+rectangular[move-1][1]+')';
}else{
content='游戏开始~';
}
// 高亮最后一个
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>
);
}
});
// 排序方式
if(this.state.isAscending=='降序排列'){
return arr;
}else{
return arr.reverse();
}
},
// 切换排序方式
sortToggle:function(){
this.setState(function(prevState){
var sort=prevState.isAscending;
var content='';
if(sort=='升序排列'){
content='降序排列';
}else{
content='升序排列'
}
return {
isAscending:content
}
})
},
// 重置
reset:function(){
this.setState({
history:[
{squares:Array(9).fill(null)}
],
turnToX:true,
stepNumber:0,
isAscending:'降序排列'
});
}, render:function(){
var lastHistory=this.state.history[this.state.stepNumber];//渲染遵照的是stepNumber而不是最后一步
var status=this.judgeWinner()[0];//获胜描述
var winCase=this.judgeWinner()[1];//获胜状态 return (
<div className="game"> <div>
<h1 classNme="status">React的井字过三关(2)</h1>
<Board
winCase={winCase}
lastHistory={lastHistory.squares}
onClick={(i)=>this.handleClick(i)} />
</div>
<div className="info">
<div>{status}</div>
<input
type="button"
value={this.state.isAscending}
onClick={this.sortToggle} />
<input
type="button"
value="重置"
onClick={this.reset} />
<ol>{this.getMoveList()}</ol>
</div>
</div>
);
}
}); var Board=React.createClass({
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)} />
);
}, 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>
);
}
}); 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>
);
}
return (
<button
className="square"
onClick={() => this.props.onClick()}>
{this.props.value}
</button>
); }
}); ReactDOM.render(
<Game />,
document.getElementById('container')
);

React的井字过三关(2)的更多相关文章

  1. React的井字过三关(1)

    React的井字过三关(1) 本文系React官方教程的Tutorial: Intro To React的笔记.由笔者用ES5语法改写. 在本篇笔记中,尝试用React构建一个可交互的井字棋游戏. 开 ...

  2. React的井字过三关(3)

    这是React井字棋项目的最后一篇笔记,记述AI实现. 一. 是开头都会说的原理 但凡懂一点围棋的人都知道"大场"这个概念,可以浅显地把它理解为布局时棋盘上各处的要点.棋谚&quo ...

  3. [HTML5实现人工智能]小游戏《井字棋》发布,据说IQ上200才能赢

    一,什么是TicTacToe(井字棋)   本 游戏 为在下用lufylegend开发的第二款小游戏.此游戏是大家想必大家小时候都玩过,因为玩它很简单,只需要一张草稿纸和一只笔就能开始游戏,所以广受儿 ...

  4. 『HTML5实现人工智能』小游戏《井字棋》发布,据说IQ上200才能赢【算法&代码讲解+资源打包下载】

    一,什么是TicTacToe(井字棋) 本游戏为在下用lufylegend开发的第二款小游戏.此游戏是大家想必大家小时候都玩过,因为玩它很简单,只需要一张草稿纸和一只笔就能开始游戏,所以广受儿童欢迎. ...

  5. Pascal小游戏 井字棋

    一个很经典的井字棋游戏 Pascal源码Chaobs奉上 注意:1.有的FP版本不支持汉语,将会出现乱码.2.别想赢电脑了,平手不错了. 井字过三关: program TicTacToe; uses ...

  6. 使用 Vue.js 改写 React 的官方教程井字棋

    React 的官方教程井字棋很好的引导初学者一步步走进 React 的世界,我想类似的教程对 Vue.js 的初学者应该也会有启发,于是使用 Vue.js 进行了改写 可以先查看最终的结果,尝试点击体 ...

  7. [CareerCup] 17.2 Tic Tac Toe 井字棋游戏

    17.2 Design an algorithm to figure out if someone has won a game oftic-tac-toe. 这道题让我们判断玩家是否能赢井字棋游戏, ...

  8. quick cocos2d-x 入门---井字棋

    学习quick cocos2d-x 第二天 ,使用quick-x 做了一个井字棋游戏 . 我假设读者已经 http://wiki.quick-x.com/doku.php?id=zh_cn阅读了这个链 ...

  9. 程序设计入门—Java语言 第五周编程题 2井字棋(5分)

    2 井字棋(5分) 题目内容: 嗯,就是视频里说的那个井字棋.视频里说了它的基本思路,现在,需要你把它全部实现出来啦. 你的程序先要读入一个整数n,范围是[3,100],这表示井字棋棋盘的边长.比如n ...

随机推荐

  1. AngularJS 模块& 表单

    模块定义了一个应用程序. 模块是应用程序中不同部分的容器. 模块是应用控制器的容器. 控制器通常属于一个模块. 应用("myApp") 带有控制器 ("myCtrl&qu ...

  2. [AlwaysOn Availability Groups]AG排查和监控指南

    AG排查和监控指南 1. 排查场景 如下表包含了常用排查的场景.根据被分为几个场景类型,比如Configuration,client connectivity,failover和performance ...

  3. Windows Server 2012 虚拟化实战:网络(二)

    关于Windows Server的虚拟化网络,前文描述了在操作系统层面上的出现的配置变化.其中的一些配置通过Windows Server提供的小工具即可实现,如网卡组的配置,而有些需要安装Window ...

  4. [转].NET Core中的认证管理解析

    本文转自:http://www.cnblogs.com/durow/p/5783089.html 0x00 问题来源 在新建.NET Core的Web项目时选择“使用个人用户账户”就可以创建一个带有用 ...

  5. 个人CTF资源聚合

    i春秋 幻泉 CTF入门课程笔记 视频地址 能力 思维能力 快速学习能力 技术能力 基础 编程基础 (c语言 汇编语言 脚本语言) 数学基础 (算法 密码学) 脑洞 (天马行空的想象推理) 体力耐力( ...

  6. 初始webservice

    webservice 可以用来查天气,以及手机号码类型等功能,这写都是简单的 方法有很多: 1.通过创建 web service exploer 创建出一个web services explorer ...

  7. JavaScript第一节课

    1.用法:位于<script></script>可以位于body和head中,不限制标签数量,也可以创建外部Js文件,然后引入.(引入方法:<script src=&qu ...

  8. 关于EventEmitter的用法

    EventEmitter的基本用法: var EventEmitter = require("events").EventEmitter; var ee = new EventEm ...

  9. openssl、x509、crt、cer、key、csr、ssl、tls 这些都是什么鬼?

    今天尝试在mac机上搭建docker registry私有仓库时,杯具的发现最新的registry出于安全考虑,强制使用ssl认证,于是又详细了解linux/mac上openssl的使用方法,接触了一 ...

  10. [LeetCode] Remove Duplicate Letters 移除重复字母

    Given a string which contains only lowercase letters, remove duplicate letters so that every letter ...