4
\$\begingroup\$

The task is to build a quiz game which has the following sections:

  • Displaying a question

  • Answer options

  • Status of correct and incorrect answers so far

Expected functionality:

A question is displayed. The user clicks on one of the four shown answer options. Afterwards are either the correct or the incorrect answers incremented. The next question is shown.

Screenshot of the GUI

The game has to have at least 10 questions.

Here's my implementation:

class QuestionPanel extends React.Component {
  constructor(props) {
    super(props);
  }
 
  render() {
  
    if (this.props.activeGame) {
      return ( <div className="text-center">
                  <p className="question-panel">{ this.props.question }</p>
               </div> )
    } else {
      return ( <div className="text-center">
                 <p className="question-panel message">Game Over</p>
                 <div className="hyperlink-button">
                   <a onClick={ this.props.resetGame } href="#">Restart</a>
                 </div>
               </div>);
    }
  }
}

class Option extends React.Component { 
  render() {
    return <div><a href="#"
              onClick={ this.props.triggerProcess } 
              className="option-card">
            { this.props.optionValue }
           </a></div>;
  }
}

class StatusDisplay extends React.Component {
  render() {
    return (<div className="status-display text-center">
                  <p>Correct: { this.props.correct }</p>
                  <p>Incorrect: { this.props.incorrect }</p>
            </div>)
  }
}

class GameBoard extends React.Component {
  constructor(props) {
    super(props); 
    
    this.processGuess = this.processGuess.bind(this);
    this.resetGame = this.resetGame.bind(this);
    
    this.state = {
      currentIndex: 0,
      correct: 0,
      incorrect: 0,
      activeGame: true,
      questionsAnswer: [
        { // 1
          question: 'What is 8 x 1 ?',
          options: [ 2, 7, 8, 9],
          answer: 8
        },
        { // 2
          question: 'What is 3 + 4 ?',
          options: [ 5, 7, 9, 34],
          answer: 7
        },
        { // 3
          question: 'What is 10 - 6 ?',
          options: [ 2, 4, 10, 7],
          answer: 4
        },
        { // 4
          question: 'What is 12 / 3 ?',
          options: [ 4, 6, 3, 8],
          answer: 4
        },
        { // 5
          question: 'What is 11 + 0 ?',
          options: [ 15, 11, 12, 10],
          answer: 11
        },
        { // 6
          question: 'What is 13 + 2 ?',
          options: [ 4, 17, 9, 15],
          answer: 15
        },
        { // 7
          question: 'What is 33 / 11 ?',
          options: [ 2, 3, 4, 1],
          answer: 3
        },
        { // 8
          question: 'What is 2 + 5 ?',
          options: [ 10, 7, 8, 6],
          answer: 7
        },
        { // 9
          question: 'What is 9 - 4 ?',
          options: [ 5, 7, 6, 9],
          answer: 5
        },
        { // 10
          question: 'What is 11 * 2 ?',
          options: [ 20, 22, 33, 34],
          answer: 22
        }
      ]
    };
  }
  
  resetGame() {
    this.setState({
      currentIndex: 0,
      correct: 0,
      incorrect: 0,
      activeGame: true
    });
  }
  
  processGuess(e) { 
    
   if (this.state.currentIndex + 1 === this.state.questionsAnswer.length) { 
       this.setState( { activeGame: false } );
     
       return;
   }
   // For accomplishing a better readible equality-check.
   let answer = parseInt(e.target.textContent);
   let correctAnswer = 
       parseInt(
          this.state.questionsAnswer[this.state.currentIndex]['answer']
        );
    
   if ( answer === correctAnswer ) {
       this.setState( {correct: this.state.correct + 1} );
   } else {
       this.setState( {incorrect: this.state.incorrect + 1} );
   } 
    
   this.setState( {currentIndex: this.state.currentIndex + 1} );
  }
  
  render() {
    var options = [];
    
    if (this.state.activeGame) {
      for (let i = 0; i < this.state
                              .questionsAnswer[0]
                              .options
                              .length; i++) {
        options.push( <Option optionValue={
                this
                  .state
                  .questionsAnswer[this.state.currentIndex]
                  .options[i] }
              triggerProcess = { this.processGuess } 
              activeGame = { this.state.activeGame } /> );
      }
    }
    
    return (  <div>
                <div className="row">
                  <div className="offset-lg-2 col-lg-7 col-md-12">
                      <QuestionPanel activeGame={ this.state.activeGame } 
                                     question={
                                        this
                                          .state
                                          .questionsAnswer[this.state.currentIndex]
                                          .question }
                                     resetGame = { this.resetGame } />
                      <div className={ this.state.activeGame 
                                        ? 'visible'
                                        : 'hidden' } >
                         { options }
                      </div>
                  </div>
                  <div className="col-lg-3 col-md-12">
                      <StatusDisplay 
                        correct={ this.state.correct } 
                        incorrect={ this.state.incorrect } />
                  </div>
              </div>
            </div> );
  }
}

ReactDOM.render( <GameBoard className="game-board" />, 
                  document.getElementById('board') );
body {
  font-family: verdana, sans-serif;
  background-color: rgba(150, 150, 150, .6);
}

.container {
  margin-top: 15px;
}

.option-card {
  height: 50px;
  background-color: rgba(240, 240, 240, 1.0);
  border: 1px solid;
  text-align: center;
  line-height: 50px;
  display: block;
  text-decoration: none;
  border-radius: 6px;
  margin-bottom: 3px;
  box-shadow: 3px 3px 3px rgba(50, 50, 50, 0.5);
  color: rgba(20, 20, 20, 1.0);
}

.option-card:hover {
  text-decoration: none;
  opacity: 0.7;
  color: rgba(0, 0, 0, 1.0);
}

.option-card:active {
  text-decoration: none;
  box-shadow: 1px 1px 2px rgba(50, 50, 50, 0.5);
  color: rgba(20, 20, 20, 1.0);
}

.question-panel {
  font-size: 1.5rem;
  color: crimson;
  font-weight: 800;
  font-size: 1.6rem;
  text-align: center;
}

.question-panel.message {
  color: blue;
}

.status-display {
  margin-top: 52px;
  font-weight: 900;
}

.hyperlink-button {
  text-align: center;
}

.hyperlink-button a {
  text-decoration: none;
  padding: 10px 15px;
  border: 2px solid;
  color: #000;
  margin: 10px 0 15px;
  border-radius: 15px;
  display: inline-block;
}

.hyperlink-button a:hover {
  color: #fff;
}

.hyperlink-button a:active {
  opacity: 0.7;
}

.hidden {
  display: none;
}
<div id="board" class="container"></div>

Working live-demo on CodePen !

For accomplishing responsive behavior I used Twitter Bootstrap.

It looks rather fine to me. But it has become a lot of code. I have seen solutions of others which were shorter.

Therefore my questions:

  • Is my implementation done in a good way and fashion?

  • Where could it be improved? Especially concerning brevity.

Looking forward to reading your hints and suggestions.

\$\endgroup\$
  • \$\begingroup\$ It's not exactly a guessing game though, is it? It's a quiz. \$\endgroup\$ – Mast Sep 3 '17 at 12:01
  • \$\begingroup\$ @Mast Well, yep. Quiz might have been better. Indeed. \$\endgroup\$ – michael.zech Sep 3 '17 at 12:10
1
\$\begingroup\$

Here's a couple of things you could simplify:

  • Most of your components have no state, so they can be functional components, which are shorter and cleaner:

    function StatusDisplay(props) {
      return (
          <div> ...
      )
    }
    
  • The GameBoard constructor can call this.resetGame() to avoid duplicating the setup logic.

  • This:

    var options = [];
    
    if (this.state.activeGame) {
      for (let i = 0; i < this.state
                              .questionsAnswer[0]
                              .options
                              .length; i++) {
        options.push( <Option optionValue={
                this
                  .state
                  .questionsAnswer[this.state.currentIndex]
                  .options[i] }
              triggerProcess = { this.processGuess } 
              activeGame = { this.state.activeGame } /> );
      }
    }
    

    can use map:

    let options = [];
    
    if (this.state.activeGame) {
        options = this.state.questionsAnswer[this.state.currentIndex].options.map(option => (
          <Option optionValue={ option }
                  triggerProcess={ this.processGuess }
                  activeGame={ this.state.activeGame } />
        ))
    }
    
  • You could forgo one of {correct, incorrect, currentIndex}, since they're related by addition, but I think it's clearest to store all three as you currently do.
  • Not a simplification, but: have you considered dynamically generating the questions and answers?
\$\endgroup\$
  • \$\begingroup\$ Thanks a lot. :) Implementing dynamic questions / answers would be an awesome enhancement indeed. \$\endgroup\$ – michael.zech Sep 5 '17 at 9:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.