|
@@ -0,0 +1,198 @@
|
|
|
|
+// (c) alaah
|
|
|
|
+
|
|
|
|
+import React from 'react';
|
|
|
|
+import ReactDOM from 'react-dom';
|
|
|
|
+import './index.css';
|
|
|
|
+
|
|
|
|
+const BOARD_SIZE = 15;
|
|
|
|
+const MINE_COUNT = 30;
|
|
|
|
+
|
|
|
|
+const Square = props => {
|
|
|
|
+ let className = "square";
|
|
|
|
+ let disabled = props.value.uncovered || props.win;
|
|
|
|
+ let style={};
|
|
|
|
+
|
|
|
|
+ if(props.win !== 1) {
|
|
|
|
+ if(props.value.uncovered) {
|
|
|
|
+ className += " uncovered";
|
|
|
|
+ } else {
|
|
|
|
+ className += " covered";
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(props.win === 2 && props.value.bad && !props.value.flagged) {
|
|
|
|
+ style = {backgroundImage: 'url("' + props.url + '")'};
|
|
|
|
+ }
|
|
|
|
+ if(props.win === 2 && !props.value.bad && props.value.flagged) {
|
|
|
|
+ className += " nun";
|
|
|
|
+ } else if(props.value.flagged) {
|
|
|
|
+ className += " cross";
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ if(props.value.uncovered) {
|
|
|
|
+ className += " uncovered-win";
|
|
|
|
+ } else {
|
|
|
|
+ className += " covered-win";
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return (
|
|
|
|
+ <button className={className}
|
|
|
|
+ disabled={disabled}
|
|
|
|
+ onClick={props.onClick}
|
|
|
|
+ style={style}
|
|
|
|
+ onContextMenu={e => {props.onContextMenu(); e.preventDefault();}}>
|
|
|
|
+ {props.value.value > 0 ? props.value.value : null}
|
|
|
|
+ </button>
|
|
|
|
+ );
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+class Board extends React.Component {
|
|
|
|
+ renderSquare(i, url) {
|
|
|
|
+ return <Square url={url} win={this.props.win} value={this.props.squares[i]} onClick={() => this.props.onClick(i)} onContextMenu={() => this.props.onContextMenu(i)} />;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ render() {
|
|
|
|
+ const s = [];
|
|
|
|
+ let bad = 0;
|
|
|
|
+ for(let i = 0; i < BOARD_SIZE; ++i) {
|
|
|
|
+ const items = [];
|
|
|
|
+
|
|
|
|
+ for(let j = 0; j < BOARD_SIZE; ++j) {
|
|
|
|
+ if(this.props.squares[i * BOARD_SIZE + j].bad && this.props.win === 2) {
|
|
|
|
+ items.push(this.renderSquare(i * BOARD_SIZE + j, this.props.anime[bad]));
|
|
|
|
+ bad++;
|
|
|
|
+ } else {
|
|
|
|
+ items.push(this.renderSquare(i * BOARD_SIZE + j), '');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ s.push(<div>{items}</div>);
|
|
|
|
+ }
|
|
|
|
+ return (
|
|
|
|
+ <div>
|
|
|
|
+ {s}
|
|
|
|
+ </div>
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+class Game extends React.Component {
|
|
|
|
+ constructor(props) {
|
|
|
|
+ super(props);
|
|
|
|
+ let squares = new Array(BOARD_SIZE * BOARD_SIZE);
|
|
|
|
+ for(let i = 0; i < BOARD_SIZE * BOARD_SIZE; ++i) {
|
|
|
|
+ squares[i] = {
|
|
|
|
+ bad: false,
|
|
|
|
+ flagged: false,
|
|
|
|
+ uncovered: false,
|
|
|
|
+ value: 0,
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+ for(let i = 0; i < MINE_COUNT; ++i) {
|
|
|
|
+ let n;
|
|
|
|
+ do {
|
|
|
|
+ n = parseInt(Math.random() * BOARD_SIZE * BOARD_SIZE);
|
|
|
|
+ } while(squares[n].bad);
|
|
|
|
+ squares[n].bad = true;
|
|
|
|
+ }
|
|
|
|
+ this.state = {
|
|
|
|
+ squares: squares,
|
|
|
|
+ win: 0,
|
|
|
|
+ };
|
|
|
|
+ this.count = BOARD_SIZE * BOARD_SIZE;
|
|
|
|
+ this.anime = [];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ componentDidMount() {
|
|
|
|
+ fetch('https://alaah.xyz/api/gel/' + MINE_COUNT).then(res => res.json()).then(res => {
|
|
|
|
+ if(Array.isArray(res)) {
|
|
|
|
+ this.anime = res;
|
|
|
|
+ } else {
|
|
|
|
+ console.error('Unable to load gelbooru');
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ clear(squares, x, y, force) {
|
|
|
|
+ if(x < 0 || y < 0 || x >= BOARD_SIZE || y >= BOARD_SIZE) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const i = y * BOARD_SIZE + x;
|
|
|
|
+
|
|
|
|
+ if(!squares[i].flagged && !squares[i].bad && !squares[i].uncovered) {
|
|
|
|
+ let count = 0;
|
|
|
|
+ squares[i].uncovered = true;
|
|
|
|
+ for(let j = -1; j <= 1; ++j) {
|
|
|
|
+ for(let k = -1; k <= 1; ++k) {
|
|
|
|
+ if((j !== 0 || k !== 0) && y + k >= 0 && y + k < BOARD_SIZE && x + j >= 0 && x + j < BOARD_SIZE) {
|
|
|
|
+ if(squares[(y + k) * BOARD_SIZE + (x + j)].bad) {
|
|
|
|
+ count++;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ squares[i].value = count;
|
|
|
|
+ this.count--;
|
|
|
|
+
|
|
|
|
+ if(count === 0 || force) {
|
|
|
|
+ this.clear(squares, x , y - 1);
|
|
|
|
+ this.clear(squares, x - 1, y );
|
|
|
|
+ this.clear(squares, x + 1, y );
|
|
|
|
+ this.clear(squares, x , y + 1);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ onLeftClick(i) {
|
|
|
|
+ let squares = this.state.squares.slice();
|
|
|
|
+
|
|
|
|
+ if(squares[i].flagged) {
|
|
|
|
+ // nada
|
|
|
|
+ } else if(squares[i].bad) {
|
|
|
|
+ this.setState({
|
|
|
|
+ win: 2,
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ this.clear(squares, i % BOARD_SIZE, parseInt(i / BOARD_SIZE), true);
|
|
|
|
+ if(this.count === MINE_COUNT) {
|
|
|
|
+ this.setState({
|
|
|
|
+ win: 1,
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.setState({
|
|
|
|
+ squares: squares,
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ onRightClick(i) {
|
|
|
|
+ let squares = this.state.squares.slice();
|
|
|
|
+
|
|
|
|
+ if(!squares[i].uncovered) {
|
|
|
|
+ squares[i].flagged = !squares[i].flagged;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.setState({
|
|
|
|
+ squares: squares,
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ render() {
|
|
|
|
+ return (
|
|
|
|
+ <div className="game">
|
|
|
|
+ <div className={"board" + (this.state.win === 1 ? " jesus" : "")}>
|
|
|
|
+ <Board win={this.state.win} anime={this.anime} squares={this.state.squares} onClick={i => this.onLeftClick(i)} onContextMenu={i => this.onRightClick(i)} />
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+ReactDOM.render(
|
|
|
|
+ <div className="main">
|
|
|
|
+ <Game />
|
|
|
|
+ </div>,
|
|
|
|
+ document.getElementById('root')
|
|
|
|
+);
|