histogram width divide by mode, not total #2

Merged
christofsteel merged 47 commits from honigle into main 2022-02-02 13:59:36 +01:00
7 changed files with 70 additions and 19 deletions
Showing only changes of commit 60c6bc97c0 - Show all commits

View file

@ -1,9 +1,10 @@
import React from 'react' import React from 'react'
import { render, screen } from '@testing-library/react' import { render, screen } from '@testing-library/react'
import App from './App' import App from './App'
import { WORDLE_TITLE } from './constants/strings'
test('renders App component', () => { test('renders App component', () => {
render(<App />) render(<App />)
const linkElement = screen.getByText(/Not Wordle/) const linkElement = screen.getByText(WORDLE_TITLE)
expect(linkElement).toBeInTheDocument() expect(linkElement).toBeInTheDocument()
}) })

View file

@ -7,7 +7,15 @@ import { Keyboard } from './components/keyboard/Keyboard'
import { AboutModal } from './components/modals/AboutModal' import { AboutModal } from './components/modals/AboutModal'
import { InfoModal } from './components/modals/InfoModal' import { InfoModal } from './components/modals/InfoModal'
import { StatsModal } from './components/modals/StatsModal' import { StatsModal } from './components/modals/StatsModal'
import { WIN_MESSAGES } from './constants/strings' import {
WORDLE_TITLE,
WIN_MESSAGES,
GAME_COPIED_MESSAGE,
ABOUT_GAME_MESSAGE,
NOT_ENOUGH_LETTERS_MESSAGE,
WORD_NOT_FOUND_MESSAGE,
CORRECT_WORD_MESSAGE,
} from './constants/strings'
import { isWordInWordList, isWinningWord, solution } from './lib/words' import { isWordInWordList, isWinningWord, solution } from './lib/words'
import { addStatsForCompletedGame, loadStats } from './lib/stats' import { addStatsForCompletedGame, loadStats } from './lib/stats'
import { import {
@ -114,7 +122,7 @@ function App() {
return ( return (
<div className="py-8 max-w-7xl mx-auto sm:px-6 lg:px-8"> <div className="py-8 max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="flex w-80 mx-auto items-center mb-8"> <div className="flex w-80 mx-auto items-center mb-8">
<h1 className="text-xl grow font-bold">Not Wordle</h1> <h1 className="text-xl grow font-bold">{WORDLE_TITLE}</h1>
<InformationCircleIcon <InformationCircleIcon
className="h-6 w-6 cursor-pointer" className="h-6 w-6 cursor-pointer"
onClick={() => setIsInfoModalOpen(true)} onClick={() => setIsInfoModalOpen(true)}
@ -143,7 +151,7 @@ function App() {
isGameLost={isGameLost} isGameLost={isGameLost}
isGameWon={isGameWon} isGameWon={isGameWon}
handleShare={() => { handleShare={() => {
setSuccessAlert('Game copied to clipboard') setSuccessAlert(GAME_COPIED_MESSAGE)
return setTimeout(() => setSuccessAlert(''), ALERT_TIME_MS) return setTimeout(() => setSuccessAlert(''), ALERT_TIME_MS)
}} }}
/> />
@ -157,12 +165,15 @@ function App() {
className="mx-auto mt-8 flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 select-none" className="mx-auto mt-8 flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 select-none"
onClick={() => setIsAboutModalOpen(true)} onClick={() => setIsAboutModalOpen(true)}
> >
About this game {ABOUT_GAME_MESSAGE}
</button> </button>
<Alert message="Not enough letters" isOpen={isNotEnoughLetters} /> <Alert message={NOT_ENOUGH_LETTERS_MESSAGE} isOpen={isNotEnoughLetters} />
<Alert message="Word not found" isOpen={isWordNotFoundAlertOpen} /> <Alert
<Alert message={`The word was ${solution}`} isOpen={isGameLost} /> message={WORD_NOT_FOUND_MESSAGE}
isOpen={isWordNotFoundAlertOpen}
/>
<Alert message={CORRECT_WORD_MESSAGE(solution)} isOpen={isGameLost} />
<Alert <Alert
message={successAlert} message={successAlert}
isOpen={successAlert !== ''} isOpen={successAlert !== ''}

View file

@ -2,6 +2,7 @@ import { KeyValue } from '../../lib/keyboard'
import { getStatuses } from '../../lib/statuses' import { getStatuses } from '../../lib/statuses'
import { Key } from './Key' import { Key } from './Key'
import { useEffect } from 'react' import { useEffect } from 'react'
import { ENTER_TEXT, DELETE_TEXT } from '../../constants/strings'
type Props = { type Props = {
onChar: (value: string) => void onChar: (value: string) => void
@ -69,7 +70,7 @@ export const Keyboard = ({ onChar, onDelete, onEnter, guesses }: Props) => {
</div> </div>
<div className="flex justify-center"> <div className="flex justify-center">
<Key width={65.4} value="ENTER" onClick={onClick}> <Key width={65.4} value="ENTER" onClick={onClick}>
Enter {ENTER_TEXT}
</Key> </Key>
<Key value="Z" onClick={onClick} status={charStatuses['Z']} /> <Key value="Z" onClick={onClick} status={charStatuses['Z']} />
<Key value="X" onClick={onClick} status={charStatuses['X']} /> <Key value="X" onClick={onClick} status={charStatuses['X']} />
@ -79,7 +80,7 @@ export const Keyboard = ({ onChar, onDelete, onEnter, guesses }: Props) => {
<Key value="N" onClick={onClick} status={charStatuses['N']} /> <Key value="N" onClick={onClick} status={charStatuses['N']} />
<Key value="M" onClick={onClick} status={charStatuses['M']} /> <Key value="M" onClick={onClick} status={charStatuses['M']} />
<Key width={65.4} value="DELETE" onClick={onClick}> <Key width={65.4} value="DELETE" onClick={onClick}>
Delete {DELETE_TEXT}
</Key> </Key>
</div> </div>
</div> </div>

View file

@ -5,6 +5,11 @@ import { GameStats } from '../../lib/localStorage'
import { shareStatus } from '../../lib/share' import { shareStatus } from '../../lib/share'
import { tomorrow } from '../../lib/words' import { tomorrow } from '../../lib/words'
import { BaseModal } from './BaseModal' import { BaseModal } from './BaseModal'
import {
STATISTICS_TITLE,
GUESS_DISTRIBUTION_TEXT,
NEW_WORD_TEXT,
} from '../../constants/strings'
type Props = { type Props = {
isOpen: boolean isOpen: boolean
@ -27,22 +32,30 @@ export const StatsModal = ({
}: Props) => { }: Props) => {
if (gameStats.totalGames <= 0) { if (gameStats.totalGames <= 0) {
return ( return (
<BaseModal title="Statistics" isOpen={isOpen} handleClose={handleClose}> <BaseModal
title={STATISTICS_TITLE}
isOpen={isOpen}
handleClose={handleClose}
>
<StatBar gameStats={gameStats} /> <StatBar gameStats={gameStats} />
</BaseModal> </BaseModal>
) )
} }
return ( return (
<BaseModal title="Statistics" isOpen={isOpen} handleClose={handleClose}> <BaseModal
title={STATISTICS_TITLE}
isOpen={isOpen}
handleClose={handleClose}
>
<StatBar gameStats={gameStats} /> <StatBar gameStats={gameStats} />
<h4 className="text-lg leading-6 font-medium text-gray-900"> <h4 className="text-lg leading-6 font-medium text-gray-900">
Guess Distribution {GUESS_DISTRIBUTION_TEXT}
</h4> </h4>
<Histogram gameStats={gameStats} /> <Histogram gameStats={gameStats} />
{(isGameLost || isGameWon) && ( {(isGameLost || isGameWon) && (
<div className="mt-5 sm:mt-6 columns-2"> <div className="mt-5 sm:mt-6 columns-2">
<div> <div>
<h5>New word in</h5> <h5>{NEW_WORD_TEXT}</h5>
<Countdown <Countdown
className="text-lg font-medium text-gray-900" className="text-lg font-medium text-gray-900"
date={tomorrow} date={tomorrow}

View file

@ -1,4 +1,10 @@
import { GameStats } from '../../lib/localStorage' import { GameStats } from '../../lib/localStorage'
import {
TOTAL_TRIES_TEXT,
SUCCESS_RATE_TEXT,
CURRENT_STREAK_TEXT,
BEST_STREAK_TEXT,
} from '../../constants/strings'
type Props = { type Props = {
gameStats: GameStats gameStats: GameStats
@ -22,10 +28,10 @@ const StatItem = ({
export const StatBar = ({ gameStats }: Props) => { export const StatBar = ({ gameStats }: Props) => {
return ( return (
<div className="flex justify-center my-2"> <div className="flex justify-center my-2">
<StatItem label="Total tries" value={gameStats.totalGames} /> <StatItem label={TOTAL_TRIES_TEXT} value={gameStats.totalGames} />
<StatItem label="Success rate" value={`${gameStats.successRate}%`} /> <StatItem label={SUCCESS_RATE_TEXT} value={`${gameStats.successRate}%`} />
<StatItem label="Current streak" value={gameStats.currentStreak} /> <StatItem label={CURRENT_STREAK_TEXT} value={gameStats.currentStreak} />
<StatItem label="Best streak" value={gameStats.bestStreak} /> <StatItem label={BEST_STREAK_TEXT} value={gameStats.bestStreak} />
</div> </div>
) )
} }

View file

@ -1 +1,19 @@
export const WORDLE_TITLE = 'Not Wordle'
export const WIN_MESSAGES = ['Great Job!', 'Awesome', 'Well done!'] export const WIN_MESSAGES = ['Great Job!', 'Awesome', 'Well done!']
export const GAME_COPIED_MESSAGE = 'Game copied to clipboard'
export const ABOUT_GAME_MESSAGE = 'About this game'
export const NOT_ENOUGH_LETTERS_MESSAGE = 'Not enough letters'
export const WORD_NOT_FOUND_MESSAGE = 'Word not found'
export const CORRECT_WORD_MESSAGE = (solution: string) =>
`The word was ${solution}`
export const ENTER_TEXT = 'Enter'
export const DELETE_TEXT = 'Delete'
export const STATISTICS_TITLE = 'Statistics'
export const GUESS_DISTRIBUTION_TEXT = 'Guess Distribution'
export const NEW_WORD_TEXT = 'New word in'
export const TOTAL_TRIES_TEXT = 'Total tries'
export const SUCCESS_RATE_TEXT = 'Success rate'
export const CURRENT_STREAK_TEXT = 'Current streak'
export const BEST_STREAK_TEXT = 'Best streak'

View file

@ -1,9 +1,10 @@
import { getGuessStatuses } from './statuses' import { getGuessStatuses } from './statuses'
import { solutionIndex } from './words' import { solutionIndex } from './words'
import { WORDLE_TITLE } from '../constants/strings'
export const shareStatus = (guesses: string[], lost: boolean) => { export const shareStatus = (guesses: string[], lost: boolean) => {
navigator.clipboard.writeText( navigator.clipboard.writeText(
`Not Wordle ${solutionIndex} ${lost ? 'X' : guesses.length}/6\n\n` + `${WORDLE_TITLE} ${solutionIndex} ${lost ? 'X' : guesses.length}/6\n\n` +
generateEmojiGrid(guesses) generateEmojiGrid(guesses)
) )
} }