Build a typing game with Next.js and Appwrite

Appwrite Hashnode Hackathon - 2023

Build a typing game with Next.js and Appwrite

For the 2023 Appwrite + Hashnode Hackathon, I decided to build a daily typing challenge where users can sign in and attempt to accurately type out the sentence for the day as quickly as they can. Here's how I built it.

Tech Stack

  • Frontend - Next.js (using app-router to try out React server components)

  • Styling - Tailwind CSS (by go-to for building fast)

  • Database (to store user scores and daily challenges) - Appwrite cloud

  • Authentication - Clerk (out-of-the-box UI components for auth)

  • Deployment/hosting - Vercel

Challenges Encountered

The main challenge for an app like this is visually showing the user how much of the sentence they've accurately typed to provide a good UX as shown in the screenshot:

Here's how this effect was accomplished using Regular expressions:

  // sentenceChallenge =  <daily challenge fetched from the DB>

  const regex = new RegExp("^" + userInput, ""); // create a RegEx from the user's input in the text area

  // replace the matching portion of the challenge with the highlighted text
  const highlightedSentence = sentenceChallenge.replace(regex, function (str) {
    return "<span class='text-pink-600 font-semibold'>" + str + "</span>";
  });

And, that's it!

The other task is properly calculating and storing the user's typing speed averages after every test.

To accomplish this, if the user was signed in when they land on the /daily-challenge route, I fetch the user's typing data from the Appwrite database. This is used to calculate the new average after the new test is completed.

Here's how the new typing speed and average are calculated thereafter:

const completeChallenge = async () => {
    const newTypingSpeed = parseFloat(
      (sentenceChallenge.length / 5 / (timer / 60)).toFixed(2)
    );

    if (!user) // exit early

    try {
      const averageScore =
        (userInfo.average_score * userInfo.number_of_challenges +
          newTypingSpeed) /
        (userInfo.number_of_challenges + 1);

      await databases.updateDocument(
        process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID!,
        process.env.NEXT_PUBLIC_APPWRITE_SCORES_COLLECTION_ID!,
        userInfo.$id,
        {
          average_score: averageScore,
          last_challenge_day: today,
          number_of_challenges: userInfo.number_of_challenges + 1,
          last_challenge_time: timer,
        }
      );
    } catch (error) {
      // deal with error
    }
};

Conclusion

Overall, this was a fun project to hack on. Feel free to clone the Github repo (linked below) and see if you can make any improvements or modifications!

Github repo

Visit website

Demo recording