前回の続き。
Reactチュートリアルの○×ゲームの改良アイデアリストをこなしてみます。
チュートリアル簡単なので、自分が出来る子に思えてちょっと楽しくなってきましたw
1.履歴内のそれぞれの着手の位置を (col, row) というフォーマットで表示する。
Gameのhistoryに位置を追加しました。
一つ前のhistoryと比較して位置を出す方法より簡単っぽかったので。
history: [{
squares: Array(9).fill(null),
putpoint:null,
}],
handleClick()でsquaresだけでなくputpointも記録させる。
this.setState({
history: history.concat([{
squares: squares,
putpoint: i,
}]),
アトはrender()でcol,rowを出して表示に加えるだけ。
const moves = history.map((step,move) => {
let pt = null;
if( move ){
let a = parseInt(step.putpoint / 3);
let b = step.putpoint % 3 + 1;
pt = '(' + (1 + a) + ',' + b + ')';
}
const desc = move ?
'Go to move $' + move + pt:
'Go to game start';
割り算とかでrow,col出すのは昔に何かでやった記憶があったので比較的すんなりいきました。
…本当に1つ前のhistoryと比較して位置を出す方法より簡単だったのか不明です。
こまけーこたぁいいんだよ!
2.着手履歴のリスト中で現在選択されているアイテムを太字にする。
よく考えたらチュートリアルでCSSの方に触れるの初めてっすね。
index.cssを開いてcssを追加。
li.current button{
font-weight: bold;
}
Gameのrender()にCSSで指定したcurrentってclass名を付ける処理を入れる。
const cu = this.state.stepNumber===move ? 'current' : '';
return (
<li key={move} className={cu}>
<button onClick={()=>this.jumpTo(move)}>{desc}</button>
</li>
);
classの指定はclassNameらしい。
本当にコレで合ってるか分からんけど、動いたのでいーや。
次いこ、次!
3.Board でマス目を並べる部分を、ハードコーディングではなく 2 つのループを使用するように書き換える。
いきなり難しくなった。
流石にrender()のreturnのトコにfor文を入れるとか無理だよね?
分からな過ぎるのでググった。
forからググった。
このJSXとやらは変数に代入出来るの?ってトコから分からん。
普通に公式のDocsが親切でした。
コンパイルされるとオブジェクトになるって事は素直に+でくっつけちゃ駄目だろうな。
「JSX for」でググったら解決した。push()使うのね。
https://webbibouroku.com/Blog/Article/react-jsx-if-for
let divs = [];
let i = 0;
for( let x = 0; x < 3; x++ )
{
let squares = [];
for( let y = 0; y < 3; y++ )
{
squares.push( this.renderSquare(i) );
++i;
}
divs.push( <div className="board-row" key={x}>{squares}</div> );
}
return( <div>{divs}</div>);
Boardのrender()を総変更。
一応動いたけど、正攻法なのか不明。
あとkey関連で警告が出たのでrenderSquare()にもkeyを追加。
<Square
key={i}
value={this.props.squares[i]}
onClick={ ()=> this.props.onClick(i) }
/>
Reactのリストはkeyを入れないと警告出るのが少し面倒…だけど、keyのお陰で便利に使えるんだろうね!知らんけど!
4.着手履歴のリストを昇順・降順いずれでも並べかえられるよう、トグルボタンを追加する。
いる?これいる?w
とりあえず、現状の並び順を保持するstateが必要なのは分かります。
素直にlistsortって追加。
class Game extends React.Component {
constructor( props ) {
super( props );
this.state = {
history: [{
squares: Array(9).fill(null),
putpoint:null,
}],
stepNumber: 0,
xIsNext: true,
listsort: true,
}
}
listsortの値をトグルする関数追加。
toggleList() {
this.setState({
listsort: ! this.state.listsort,
});
}
render()にトグルボタンの表示を追加。
あと、listsortの値によって着手履歴をリバース。
ごめん、正直「降順ならこの並び」とか考えてないっす。
アレなら逆にしてw
const togglebtn = ( <button onClick={()=>this.toggleList()}>{this.state.listsort?'昇順':'降順'}</button>);
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => this.handleClick(i) }
/>
</div>
<div className="game-info">
<div>{status}</div>
<div>{togglebtn}</div>
<ol>{this.state.listsort?moves:moves.reverse()}</ol>
</div>
</div>
);
こんな感じで動きました。
コレも何かもっとこう…正攻法なやり方があるんだろうな~…。
5.どちらかが勝利した際に、勝利につながった 3 つのマス目をハイライトする。
こんなん、calculateWinner()の返り値を追加してあげるだけやん!
早速、 calculateWinner()の勝利時の返り値を変更。
return {winner:squares[a],line:lines[i]};
render()のwinnerを利用してる部分を変更して、Boardへのpropsに追加。
let status;
let line = [];
if( winner ) {
status = 'Winner: ' + winner.winner;
line = winner.line;
}else{
status = 'Next Player: ' + (this.state.xIsNext ? 'X' : 'O' );
}
<Board
squares={current.squares}
line={line}
onClick={(i) => this.handleClick(i) }
/>
BoardのrenderSquare()で勝利判定されたSquareかチェックしてSquareにわたす。
最初、Squareコンポーネントの方で判定しようとしたら、判定にはkey(i)が必要だけどkeyはpropsじゃないから別途key(i)を渡す為のpropsを用意しないと駄目でした。
じゃー面倒だからココで判定しちゃえばいっか!てきなー。
renderSquare(i) {
const cls = this.props.line.indexOf(i) < 0 ? '' : 'winline ';
return(
<Square
key={i}
cls={cls}
value={this.props.squares[i]}
onClick={ ()=> this.props.onClick(i) }
/>
);
}
Squareコンポーネントで渡されたclsを付与。
クォーテーションの中にprops.clsとか書いちゃってclassの追加にちょっと迷走しちゃったのは秘密。
function Square(props) {
return (
<button className={props.cls+"square"} onClick={props.onClick}>
{props.value}
</button>
);
}
最後にcssを追加して終わり!
.square.winline {
background-color:aqua;
}
JavaScriptやReactの基礎知識が無いと時間かかりますわ~。
6.どちらも勝利しなかった場合、結果が引き分けになったというメッセージを表示する。
…ん?
if( winner ) {
status = 'Winner: ' + winner.winner;
line = winner.line;
}else{
if( history.length === 10 && 1 + this.state.stepNumber == history.length ){
status = 'Draw!';
}else{
status = 'Next Player: ' + (this.state.xIsNext ? 'X' : 'O' );
}
}
コレでええんかな?
Winnerと同じ場所に表示するのが自然だよね…?
一応ポイント、5行目の条件の後半が無いと着手履歴で戻った際に打つまでDraw!と表示されちゃいました!
後半の条件があれば、戻った時点で「Next Player:」と表示されます。
「後ろの方ほど難易度が上がります」って書いてたし難問かと思ったんだけど…??
全てのSquareが埋まっているかチェック!って感じの処理を書けって話なのかなぁ…?わからん。
まとめ
という事でチュートリアルの改良アイデアリストを試してみました。
楽しかったデス!
簡単に動くモノが作れるので初心者にオススメ出来ます!
でも、正攻法…というかスマートなやり方では無いと思うんですよね。
きっと本職さんはもっとスマートに実装するんだろうな。
まぁ、実装に拘ってチュートリアルが進まないよりも、とりあえずでも進めて動くモノを作るのが一番大事!って事でイキオイでやってみました。
あ、変数とかの命名規則どないなっとんねん!ってツッコミは無しでお願いします。
「Draw」すら「和英 引き分け」でググったレベルなんでね。
暇とヤル気があれば、今回の実装方法の改善点なども考えてみたいと思います!
いや、公式Docsを読み進めるのが先だね。
[追記]
同じ事に挑戦してる記事を見つけたので覗かせて貰いました。
https://qiita.com/exotic-toybox/items/798f9c2857bdf1ae7074
[3]のループ処理をmap()で実装してたり変数に入れずに書いていたり参考になりました!
シンプルに配列に含まれるかを判定するincludes()っていう関数の存在も知れましたw
indexOf()だと用途からすると富豪なんですね。
[6]もhistoryではなくcurrentを判定対象にすれば無駄な判定を入れる必要無かったんや、と目ウロでした。
あと…6が一番簡単じゃんって感想が同じで笑いましたw
こういう、同じ課題に対する他の人のアプローチとか凄く身になる気がしましたマル!
コメント