Reactチュートリアル『まるばつゲーム(三目並べ)』の追加課題をやってみる

React

Reactチュートリアル『まるばつゲーム』には追加課題が6問あるので勉強がてらやってみた。内容はかなり雑な気がするので、あくまで参考程度にとどめていただければ。

チュートリアル本題

チュートリアル本題はこちらにて。

Reactチュートリアル『まるばつゲーム(三目並べ)』をcreate-react-appを土台にしてやってみる
Reactチュートリアルを、create react appで生成した上に乗せていくならどんな感じになるか試してみました...

追加課題

完成するとこんな感じになると思われる。

要件は下記。

  1. それぞれの遷移に(col,row)の表示をする
  2. 遷移リストの現在選択されているものを太字に
  3. Boardをloop2回つかって書いてみる
  4. 遷移リストを昇順/降順にする
  5. 誰かが勝った時に、3つ揃ったセルをハイライトする
  6. 引き分けの時にはdrawを表示

書いてみた

少し強引ですが書いてみました。classを増やしてハイライトに色付けしたりしたのですが、書き方がこれでいいのか悩みます。あくまで参考程度に。

<span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_end"></span>
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import './Game.css';

// 課題3 -追記ここから-
const row_size = 3;
const col_size = 3;
// 課題3 -追記ここまで-

function Square(props) {
  return (
    <button className={`square ${props.highlight ? 'highlight' : ''}`} onClick={props.onClick}>
      {props.value}
    </button>
  );
}

function calculateWinner(squares){
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];
  
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares) {
      return squares[a];
    }
  }
  return null;
}

function getCellsCausedWin(squares){
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];
  
  for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares) { return lines[i]; } } return []; } // 課題6 -追記ここから- function calculateGameEnd(history_length) { if (history_length > row_size * col_size) {
    return true;
  }
  return false;
}
// 課題6 -追記ここまで-

class Board extends Component {
  
  // 課題5 -追記ここから-
  renderSquare(i, highlight = false) {
    return (
      <Square value={this.props.squares[i]} highlight={highlight} onClick={() => this.props.onClick(i)}
      />
    );
  }
  // 課題5 -追記ここまで-

  render() {
    let cellsCausedWin = getCellsCausedWin(this.props.squares);
    
    // 課題3 -追記ここから-
    let board = [];
    let board_row = [];
    for (var row=0; row<row_size; row++) {
      board_row = [];
      for (var col=0; col<col_size; col++) {
        let i = col + row * row_size;
        // 課題5 -追記ここから-
        let highlight = cellsCausedWin.indexOf(i) !== -1 ? true : false;
        board_row.push(this.renderSquare(i, highlight));
        // board_row.push(this.renderSquare(i)); //課題3では←のように書いていた
        // 課題5 -追記ここまで-
      }
      board.push(


<div className="board-row">{board_row}</div>



);
    }
    return (
      


<div>
        {board}
      </div>



    );
    // 課題3 -追記ここまで-
  }
}

class Game extends Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [{
        squares: Array(9).fill(null),
        col: null,
        row: null,
      }],
      stepNumber: 0,
      xIsNext: true,
      // 課題4 -追記ここから-
      asc: true,
      // 課題4 -追記ここまで-
    };
  }
  
  handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1]
    const squares = current.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      history: history.concat([{
        squares: squares,
        // 課題1 -追記ここから-
        col: (i % 3) + 1,
        row: Math.floor(i / 3) + 1,
        // 課題1 -追記ここまで-
      }]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext
    });
  }
  
  jumpTo(step) {
    this.setState({
      stepNumber: step,
      xIsNext: (step % 2) === 0,
    });
  }
  
  /* 課題4 -追記ここから- */
  toggle_sort() {
    this.setState({
      asc: !this.state.asc,
    });
  }
  /* 課題4 -追記ここまで- */
  
  render() {
    const history = this.state.history;
    const current = history[this.state.stepNumber]
    const winner = calculateWinner(current.squares);
    // 課題6 -追記ここから-
    const is_end = calculateGameEnd(history.length)
    // 課題6 -追記ここまで-
    
    const moves = history.map((step, move) => {
      const desc = move ?
        // 課題1 -追記ここから-
        'Go to move #' + move + ` (${step.col}, ${step.row})`:
        // 課題1 -追記ここまで-
        'Go to game start';
      return (
        


<li key={move}>
          <button onClick={() => this.jumpTo(move)}
            // 課題2 -追記ここから-
            class={this.state.stepNumber === move ? 'bold' : '' }
            // 課題2 -追記ここまで-
          >
            {desc}
          </button>
        </li>



      );
    })
    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    // 課題6 -追記ここから-
    } else if (is_end) {
      status = 'Draw';
    // 課題6 -追記ここまで-
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
    }

    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>



          {/* 課題4 -追記ここから- */}
          


<div><button onClick={() => this.toggle_sort()}>ASC⇔DESC</button></div>



          


<ol>{this.state.asc ? moves : moves.reverse()}</ol>



          {/* 課題4 -追記ここまで- */}
        </div>



      </div>



    );
  }
}


class App extends Component {
  render() {
    return (
      


<div className="App">
        


<header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          


<h1 className="App-title">Welcome to React</h1>



        </header>



        <Game />
      </div>



    );
  }
}

export default App;

 

@charset "UTF-8";
ol, ul {
  padding-left: 30px; }

.board-row:after {
  clear: both;
  content: "";
  display: table; }

.status {
  margin-bottom: 10px; }

.square {
  background: #fff;
  border: 1px solid #999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px; }

.square:focus {
  outline: none; }

.kbd-navigation .square:focus {
  background: #ddd; }

.game {
  display: flex;
  flex-direction: row;
  padding: 20px; }

.game-info {
  margin-left: 20px; }

/* 課題2 -追記ここから- */
.bold {
  font-weight: bold; }

/* 課題2 -追記ここまで- */
/* 課題5 -追記ここから- */
.highlight {
  background-color: #ffe80085; }

/* 課題5 -追記ここまで- */

 

コメント