Skip to content

Instantly share code, notes, and snippets.

@todoa2c
Last active November 1, 2015 13:53
Show Gist options
  • Save todoa2c/f3601a21d0fcd24b3fcd to your computer and use it in GitHub Desktop.
Save todoa2c/f3601a21d0fcd24b3fcd to your computer and use it in GitHub Desktop.
15パズルのReact.js版
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>15 puzzle with React.js</title>
<style>
table {
empty-cells:show;
}
.front {
width: 40px;
height: 40px;
cursor: pointer;
text-align: center;
border-style: groove;
font-weight: bold;
font-size: 24px;
}
.placed {
background-color: #ffd9d6;
}
.number-0 {
color: green;
}
.number-1 {
color: gold;
}
.number-2 {
color: blue;
}
.number-3 {
color: red;
}
</style>
<script src="https://fb.me/react-0.14.1.js"></script>
<script src="https://fb.me/react-dom-0.14.1.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
</head>
<body>
<div id="main">
</div>
<script type="text/babel" src="puzzle.js"></script>
</body>
</html>
/**
* 15パズルのパリティ計算(偶奇性の計算)
* @param {Array.<number>} numbers
* @returns {number}
* @see http://www.geocities.jp/m_hiroi/puzzle/parity.html
*/
var parity = function(numbers) {
if (numbers.length === 1) {
return 0;
} else {
var head = numbers[0];
var tail = numbers.slice(1);
return parity(tail) + tail.filter(function(x) {return x < head;}).length;
}
};
/**
* Fisher-Yates shuffle (副作用あり版)
*/
var shuffle = function(values) {
var i = values.length;
while(i){
var j = Math.floor(Math.random()*i);
var t = values[--i];
values[i] = values[j];
values[j] = t;
}
return values;
};
/**
* パズルゲーム本体
*/
var Puzzle = React.createClass({
/**
* パズルの初期状態
* @returns {{matrix: *[], count: number}}
*/
getInitialState: function () {
return {
matrix: [[]],
count: 0
};
},
/**
* startボタンを押したときに行う、ゲームの初期化処理
*/
startGame: function () {
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
// パズルが正解になる並び順になるまで作りなおす
while (true) {
shuffle(numbers);
console.log(numbers);
if (parity(numbers) % 2 === 0) break;
}
// 4x4の配列に、パズルを挿入する
var matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, null]];
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
matrix[i][j] = numbers[4 * i + j];
}
}
matrix[4 - 1][4 - 1] = null;
// 状態を更新する。パズルは新規に作られ、カウントは0から開始。
this.setState({matrix: matrix, count: 0});
},
/**
* あるセルがクリックされたとき、クリックされたセルの前後左右にnullセルがあれば、そのセルと値を交換する
* @param {{row: number, col: number}} clickPosition
*/
handleClick: function(clickPosition) {
var row = clickPosition.row, col = clickPosition.col;
// クリックされた位置の前後左右を見て、前後左右のどこかにnullがあればnullと数字を入れ替える
var directions = [[row - 1, col], [row + 1, col], [row, col - 1], [row, col + 1]];
var matrix = this.state.matrix;
for (var i = 0; i < directions.length; i++) {
try {
if (this.state.matrix[directions[i][0]][directions[i][1]] === null) {
matrix[directions[i][0]][directions[i][1]] = matrix[row][col];
matrix[row][col] = null;
// 状態を更新する。matrixは入れ替えたあとのmatrix、countは+1した結果
this.setState({matrix: matrix, count: this.state.count + 1});
break;
}
} catch(ignore) {
}
}
},
/**
* 終了済みかを返す。
* @returns {boolean}
*/
isFinished: function() {
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
// matrix[i][j]が順番に並んでない状態になったとき、
// i, jが共に3(つまり右下を検証中)でかつmatrix[i][j] == nullの時、パズルが解けたと言える。
if (this.state.matrix[i][j] !== 4 * i + j + 1) {
return !!(i === 4 - 1 && j === 4 - 1
&& this.state.matrix[i][j] === null);
}
}
}
return true;
},
render: function () {
return (
<div>
<button onClick={this.startGame} value="Start">Start</button>
<table border="1" cellSpacing="0">
<Matrix matrix={this.state.matrix} onClick={this.handleClick}/>
</table>
<div>Clicked {this.state.count} times.</div>
{this.isFinished() && <div>Congratulations!</div>}
</div>
);
}
});
/**
* パズルの4x4マトリックス
*/
var Matrix = React.createClass({
/** クリック時の処理は、上コンポーネントに下コンポーネントから渡された値を流すだけ */
handleClick: function(clickPosition) {
this.props.onClick(clickPosition);
},
/**
* 4x4のtableを作る
* @returns {XML}
*/
render: function () {
var self = this;
return (
<tbody>
{this.props.matrix.map(function (row, i) {
return (
<tr key={i}>
{row.map(function(cell, j) {
return <Cell key={cell} row={i} col={j} number={cell} onClick={self.handleClick} />
})}
</tr>
);
})}
</tbody>
)
}
});
/**
* 15パズルの1つ1つのセル
*/
var Cell = React.createClass({
/**
* セルがクリックされたら、クリックされた位置を渡す。
* @param e
*/
handleClick: function(e) {
// 上位コンポーネント(つまりMatrixから渡されたonClick(つまりMatrixのhandleClick)を実行する
this.props.onClick({row: this.props.row, col: this.props.col})
},
render: function() {
var lineNumber = "number-" + Math.floor((this.props.number - 1) / 4);
var placed = this.props.number === (this.props.row * 4 + this.props.col + 1) && 'placed';
var className = ['front', lineNumber, placed].join(' ');
return <td className={className} onClick={this.handleClick}>{this.props.number}</td>
}
});
// #main に対してReact.jsで描画する
ReactDOM.render(
<Puzzle />,
document.getElementById('main')
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment