Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div id="root"></div>

              
            
!

CSS

              
                a {
  color: #03a9f4;
}

a:hover,
a:focus {
  color: #0288d1;
}

#root {
  min-height: 100vh;
  display:-webkit-box;
  display:-ms-flexbox;
  display: flex;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  /*font-family: 'Short Stack', cursive;*/
  font-family: 'Walter Turncoat', cursive;
  color: #555;
}

.c-game-mode-view,
.c-marker-choice-view,
.c-board {
  width: 210px;
  display:-webkit-box;
  display:-ms-flexbox;
  display:flex;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
  -ms-flex-direction: column;
  flex-direction: column;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
}

.c-game-mode-view__mode--2 {
  margin-top: 0;
  margin-bottom: 0;
}

.c-board__header-bottom {
  display:-webkit-box;
  display:-ms-flexbox;
  display: flex;
  -webkit-box-pack: justify;
  -ms-flex-pack: justify;
  justify-content: space-between;
  -webkit-box-align: start;
  -ms-flex-align: start;
  align-items: flex-start;
  margin-bottom: 1em;
}

.c-board__header-bottom-scores-heading {
  margin-top: 0;
  margin-bottom: 1em;
}

.c-board__header-bottom-score {
  margin-top: 0;
  margin-bottom: 0;
}

.c-board__row {
  display:-webkit-box;
  display:-ms-flexbox;
  display:flex;
}

.c-board__square {
  display:-webkit-box;
  display:-ms-flexbox;
  display: flex;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  width: 70px;
  height: 70px;
  padding: 0;
  margin: 0;
  border-top: 2px solid #888;
  border-right: 2px solid #888;
  border-bottom: none;
  border-left: none;
  background: transparent;
  box-shadow: none;
  text-align: center;
  font-size: 30px;
  cursor: pointer;
}

.c-board__square--x {
  color: #3f51b5;
}

.c-board__square--o {
  color: #ffc107;
}

.c-board__row--1 > .c-board__square {
  border-top: none;
}

.c-board__square:last-child {
  border-right: none;
}

.c-board__square:hover,
.c-board__square:focus {
  border-left: none;
}

.c-board__square-marker {
  display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  width: 66px;
  height: 66px;
  padding: 0;
  margin: 0;
  text-align: center;
}

.c-board__message {
  margin-top: 1em;
}

.is-blinking {
  -webkit-animation-duration: 2s;
          animation-duration: 2s;
  -webkit-animation-name: blink;
          animation-name: blink;
}

@-webkit-keyframes blink {
  from, 50%, to {
    opacity: 1;
  }
  
  25%, 75% {
    opacity: 0;
  }
}

@keyframes blink {
  from, 50%, to {
    opacity: 1;
  }
  
  25%, 75% {
    opacity: 0;
  }
}

              
            
!

JS

              
                class Game extends React.Component {
  constructor(props) {
    super(props);
    this.allWinningMoves = [
      [0, 1, 2],
      [3, 4, 5],
      [6, 7, 8],
      [0, 3, 6],
      [1, 4, 7],
      [2, 5, 8],
      [0, 4, 8],
      [2, 4, 6]
    ];
    
    this.state = this.getInitialState();
  }
  
  getNewRoundState = () => {
    const newRoundState = {
      history: [{
        squares: Array(9).fill(null)
      }],
    };
    
    return newRoundState;
  }
  
  getInitialState = () => {
    const initialState = {
      gameMode: '',
      playerOneMarker: '',
      playerOneStarts: true,
      round: 1,
      ...this.getNewRoundState(),
      xIsNext: true,
      playerOneScore: 0,
      playerTwoScore: 0
    };
    
    return initialState;
  }
  
  componentDidUpdate(prevProps, prevState) {
    if (prevState !== this.state) {
      const squares = this.getCurrentSquares();
      const winner = this.determineWinner(squares).winner;
      if (winner !== null || this.state.history.length === 10) {
        setTimeout(() => {
          this.resetThisGame(winner);
        }, 3000);
      } else if (this.state.gameMode === 'singlePlayer' && ((this.state.playerOneMarker === 'X' && !this.state.xIsNext) || (this.state.playerOneMarker === 'O' && this.state.xIsNext))) {
        this.makeMove(squares);
      }
    }
  }
  
  getCurrentSquares = () => {
    const history = this.state.history;
    const current = history[history.length - 1];
    const squares = current.squares;
    return squares;
  }
  
  resetThisGame = winner => {
    const winIncrement = winner === 'Player One' ?
      {playerOneScore: this.state.playerOneScore + 1} :
      (winner === 'Player Two' ?
      {playerTwoScore: this.state.playerTwoScore + 1} :
      {});
    const playerOneMarker = this.state.playerOneMarker;
    const playerOneStarts = !this.state.playerOneStarts;
    const xIsNext = playerOneMarker === 'X' && playerOneStarts || playerOneMarker === 'O' && !playerOneStarts;
    
    this.setState({
      ...this.getNewRoundState(),
      ...winIncrement,
      playerOneStarts: playerOneStarts,
      xIsNext,
      round: this.state.round + 1
    });
  }
  
  makeMove = squares => {
    const madeFirstMove = this.makeFirstMove(squares);
    if (madeFirstMove) {
      return;
    }
    
    const madeSecondMove = this.makeSecondMove(squares);
    if (madeSecondMove) {
      return;
    }
    
    const madeThirdMove = this.makeThirdMove(squares);
    if (madeThirdMove) {
      return;
    }
    
    const wonOrSaved = this.winOrSave(squares);
    if (wonOrSaved) {
      return;
    }
    
    for (let i = 0; i < squares.length; i++) {
      if (!squares[i]) {
        this.handleSquareClick(i);
        return;
      }
    }
  }
  
  makeFirstMove = squares => {
    if (squares[4] === null) {
      this.handleSquareClick(4);
      return true;
    } else if (this.state.playerOneStarts && this.state.history.length === 1) {
      this.handleSquareClick(0);
      return true;
    }
    
    return false;
  }
  
  makeSecondMove = squares => {
    if (!this.state.playerOneStarts && this.state.history.length === 3) {
      const movesArr = [
        [0, 8],
        [1, 0],
        [2, 6],
        [3, 0],
        [5, 0],
        [6, 2],
        [7, 0],
        [8, 0]
      ];
      
      for (let i = 0; i < movesArr.length; i++) {
        const playersMove = movesArr[i][0];
        const computersMove = movesArr[i][1];
        if (squares[playersMove] !== null) {
          this.handleSquareClick(computersMove);
          return true;
        }
      }
    } 
    
    if (this.state.playerOneStarts && this.state.history.length === 4) {
      const movesArr = [
        [4, 8, 2],
        [0, 8, 1],
        [2, 6, 1],
        [1, 3, 0],
        [1, 5, 2],
        [1, 6, 0],
        [1, 8, 2],
        [3, 2, 0],
        [3, 7, 6],
        [3, 8, 6],
        [5, 0, 2],
        [5, 6, 8],
        [5, 7, 8]
      ];
      
      for (let i = 0; i < movesArr.length; i++) {
        const moves = movesArr[i];
        const playersMove1 = moves[0];
        const playersMove2 = moves[1];
        const computersMove2 = moves[2];
        if (squares[playersMove1] === this.state.playerOneMarker && squares[playersMove2] === this.state.playerOneMarker) {
          this.handleSquareClick(computersMove2);
          return true;
        }
      }
    }
    
    return false;
  }
  
  makeThirdMove = squares => {
    if (!this.state.playerOneStarts && this.state.history.length === 5) {
      const movesArr = [
        [0, 7, 5],
        [0, 5, 7],
        [1, 8, 3],
        [2, 3, 7],
        [2, 7, 3],
        [3, 8, 1],
        [6, 1, 5],
        [6, 5, 1]
      ];
      
      for (let i = 0; i < movesArr.length; i++) {
        const moves = movesArr[i];
        const playersMove1 = moves[0];
        const playersMove2 = moves[1];
        const computersMove3 = moves[2];
        if (squares[playersMove1] === this.state.playerOneMarker && squares[playersMove2] === this.state.playerOneMarker) {
          this.handleSquareClick(computersMove3);
          return true;
        }
      }
    }
    
    return false;
  }
  
  winOrSave = squares => {
    const allWinningMoves = this.allWinningMoves;
    const computerMarker = this.state.playerOneMarker === 'X' ? 'O' : 'X';
    let savingSquareClickIndex;
    for (let i = 0; i < allWinningMoves.length; i++) {
      const [a, b, c] = allWinningMoves[i];
      if (squares[a] !== null && squares[a] === squares[b] && squares[c] === null) {
          if (squares[a] === computerMarker) {
            this.handleSquareClick(c);
      return true;
          }
        
        savingSquareClickIndex = c;
      }
          
      if (squares[a] !== null && squares[a] === squares[c] && squares[b] === null) {
        if (squares[a] === computerMarker) {
          this.handleSquareClick(b);
          return true;
        }
            
        savingSquareClickIndex = b;
      }
    
      if (squares[b] !== null && squares[b] === squares[c] && squares[a] === null) {
        if (squares[b] === computerMarker) {
          this.handleSquareClick(a);
          return true;
        }
        savingSquareClickIndex = a;
      }
    }
    
    if (Number.isInteger(savingSquareClickIndex)) {
      this.handleSquareClick(savingSquareClickIndex);
      return true;
    }
    
    return false;
  }
    
  handleGameModeClick = event => {
    event.preventDefault();
    this.setState({
      gameMode: event.target.hash.slice(1)
    });
  }
  
  handleMarkerChoiceClick = event => {
    event.preventDefault();
    const playerOneMarker = event.target.hash.slice(1);
    this.setState({
      playerOneMarker: playerOneMarker,
      xIsNext: playerOneMarker === 'X'
    });
  }
  
  determineWinner = squares => {
    const allWinningMoves = this.allWinningMoves;
    for (let i = 0; i < allWinningMoves.length; i++) {
      const [a, b, c] = allWinningMoves[i];
      if (squares[a] && squares[a] === squares[b] && squares[b] === squares[c]) {
        const winner = squares[a] === this.state.playerOneMarker ? 'Player One' : 'Player Two';
        return {
          winner,
          winningMoves: allWinningMoves[i]
        };
      }
    }
    
    return {
      winner: null,
      winningMoves: []
    };
  }
    
  handleSquareClick = i => {
    const history = this.state.history;
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    if (this.determineWinner(squares).winner !== null || squares[i] !== null) {
      return;
    }
    
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      history: history.concat([{
        squares: squares
      }]),
      xIsNext: !this.state.xIsNext
    });
  }
  
  handleResetLinkClick = event => {
    event.preventDefault();
    this.setState(this.getInitialState());
  }
  
  renderMarkerChoice = () => {
    return (
      <div className="c-marker-choice-view">
        <h3 className="c-marker-choice-view__heading">{this.state.gameMode === 'twoPlayers' ?
          'Player 1: ' :
          ''}Would you like to be X or O?</h3>
        <div className="c-marker-choice-view__choices">
          <p className="c-marker-choice-view__choice c-marker-choice-view__choice--x"><a className="c-marker-choice-view-choice-link c-marker-choice-view-choice-link--x" href="#X" onClick={this.handleMarkerChoiceClick}>X</a></p>
          <p className="c-marker-choice-view__choice c-marker-choice-view__choice--x"><a className="c-marker-choice-view-choice-link c-marker-choice-view-choice-link--o" href="#O" onClick={this.handleMarkerChoiceClick}>O</a></p>
        </div>
      </div>
    );
  }
  
  renderBoard = () => {
    const history = this.state.history;
    const current = history[history.length - 1];
    const winner = this.determineWinner(current.squares).winner;
    return (
      <Board
        renderHeader={this.renderHeader}
        renderSquare={this.renderSquare}
        renderMessage={this.renderMessage}
        winner={winner} />
    );
  }
  
  renderHeader = () => {
    return (
      <Header
        gameMode = {this.state.gameMode}
        round={this.state.round}
        playerOneScore={this.state.playerOneScore}
        playerTwoScore={this.state.playerTwoScore}
        handleResetLinkClick={this.handleResetLinkClick}
      />
    );
  }
  
  renderSquare = i => {
    const history = this.state.history;
    const current = history[history.length - 1];
    const winningMoves = this.determineWinner(current.squares).winningMoves;
    return (
      <Square 
        isBlinking={winningMoves.indexOf(i) > -1}
        marker={current.squares[i] || ''}
        onClick={(event) => this.handleSquareClick(i)} />
    );
  }
  
  renderMessage = props => {
    let message = '';
    if (props.winner !== null && this.state.gameMode === 'twoPlayers') {
      message = `${props.winner} wins!`;
    } else if (props.winner === 'Player One') {
      message = 'You win!';
    } else if (props.winner === 'Player Two') {
      message = 'Computer wins!';
    } else if (this.state.history.length === 10) {
      message = 'It was a draw.';
    } else if (this.state.gameMode === 'singlePlayer') {
      message = `${((this.state.xIsNext && this.state.playerOneMarker === 'X') || (!this.state.xIsNext && this.state.playerOneMarker === 'O')) ? 'Your' : 'Computer’s'} turn!`;
    } else {
      message = `Go Player ${((this.state.xIsNext && this.state.playerOneMarker === 'X') || (!this.state.xIsNext && this.state.playerOneMarker === 'O')) ? 'One' : 'Two'}!`; 
    }
      
    return <div className="c-board__message">{message}</div>
  }
  
  render() {
    return (
      this.state.gameMode === '' ?
        <GameMode handleClick={this.handleGameModeClick} /> :
        (this.state.playerOneMarker === '' ?
          this.renderMarkerChoice() :
          this.renderBoard())
    );
  }
}
      
const GameMode = props => (
  <div className="c-game-mode-view">
    <h3 className="c-game-mode-view__heading">How do you want to play?</h3>
    <p className="c-game-mode-view__mode c-game-mode-view__mode--1"><a className="c-game-mode-view__mode-link c-game-mode-view__mode-link--1" href="#singlePlayer" onClick={props.handleClick}>One Player</a></p>
    <p className="c-game-mode-view__mode c-game-mode-view__mode--2"><a className="c-game-mode-view__mode-link c-game-mode-view__mode-link--2" href="#twoPlayers" onClick={props.handleClick}>Two Players</a></p>
  </div>
);

GameMode.propTypes = {
  handleClick: PropTypes.func.isRequired
};

const Board = props => (
  <div className="c-board">
    {props.renderHeader()}
    {Array(3).fill(null).map((val, i) => (
      <div className={`c-board__row c-board__row--${i + 1}`}>{Array(3).fill(null).map((val2, j) => (
          props.renderSquare(i * 3 + j)
        )
      )}</div>
    ))}
    {props.renderMessage(props)}
  </div>
);

Board.propTypes = {
  renderHeader: PropTypes.func.isRequired,
  renderSquare: PropTypes.func.isRequired,
  renderMessage: PropTypes.func.isRequired,
  winner: PropTypes.string
};

const Header = props => (
  <div className="c-board__header">
    <h2>{`Round ${props.round}`}</h2>
    <div className="c-board__header-bottom"><div className="c-board__header-bottom-scores">
        <h4 className="c-board__header-bottom-scores-heading">Scores</h4>
        <p className="c-board__header-bottom-score c-board__header-bottom-score--player-1">{`${props.gameMode === 'singlePlayer' ? 'You' : 'Player 1'}: ${props.playerOneScore}`}</p>
        <p className="c-board__header-bottom-score c-board__header-bottom-score--player-2">{`${props.gameMode === 'singlePlayer' ? 'Computer' : 'Player 2'}: ${props.playerTwoScore}`}</p>
      </div>
      <a className="c-board__header-reset-link" href="#" onClick={props.handleResetLinkClick}>Reset All</a>
    </div>
  </div>
);

Header.propTypes = {
  round: PropTypes.number.isRequired,
  gameMode: PropTypes.string.isRequired,
  playerOneScre: PropTypes.number.isRequired,
  playerTwoScore: PropTypes.number.isRequired,
  handleRestLinkClick: PropTypes.func.isRequired
};

const Square = props => (
  <button 
    className={"c-board__square c-board__square--" + (props.marker).toLowerCase()}
    onClick={props.onClick}><span className={'c-board__square-marker' + (props.isBlinking ? ' is-blinking' : '')}>{props.marker}</span></button>
);

Square.propTypes = {
  isBlinking: PropTypes.bool.isRequired,
  onClick: PropTypes.func.isRequired,
  marker: PropTypes.string
};

ReactDOM.render(
  <Game />,
  document.getElementById('root')
);

// Some of code organization for this project was borrowed from Facebook Intro to React tutorial (https://reactjs.org/tutorial/tutorial.html)
              
            
!
999px

Console