#app
View Compiled
// Change the value to change the size of screen
$scale: 1;

$letter-list: "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "select", "space";

body {
	background-color: #000000;
	width: 100vw;
	height: 100vh;
	display: flex;
	justify-content: center;
	align-items: center;
}

.game-screen {
	background-image: url("https://assets.codepen.io/430361/cat-dog-background.png");
	background-size: cover;
	image-rendering: crisp-edges;
	width: 320px;
	height: 320px;
	position: relative;
	overflow: hidden;
	transform: scale($scale);
}

.cover-screen {
	background-color: #000000;
	width: 320px;
	height: 320px;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
	position: relative;
}

.letter-container {
	$size: 8;
	$count: 38;
	@mixin letter($pos) {
		background-image: url("https://assets.codepen.io/430361/cat-dog-letters.png");
		background-size: #{$size * $count}px #{$size}px;
		background-position: -#{$pos}px 0px;
		image-rendering: crisp-edges;
		width: #{$size}px;
		height: #{$size}px;
	}
	padding-bottom: #{$size * 2}px;
	display: flex;
	position: relative;
	&.letter-title {
		padding-bottom: #{$size * 4}px;
		transform: scale(2);
		&:last-child {
			padding-bottom: 0;
		}
	}
	&:last-child {
		padding-bottom: 0;
	}
	&.letter-clickable:hover:before {
		@include letter(36 * $size);
		content: "";
		position: absolute;
		top: 0;
		left: -#{$size * 2}px;
	}
	@for $index from 1 through length($letter-list) {
		$letter: nth($letter-list, $index);
		.letter-#{$letter} {
			@include letter(($index - 1) * $size);
			@if ($letter != "space") {
				margin-right: 0.5vmin;
			}
			&.last-child {
				margin-right: 0;
			}
		}
	}
}

.character {
	--left: 0px;
	width: 32px;
	height: 32px;
	position: absolute;
	left: var(--left);
	transition: left 1000ms ease-out;
	&.cat {
		background-image: url("https://assets.codepen.io/430361/cat-dog-cat.png");
		&.animated {
			background-image: url("https://assets.codepen.io/430361/cat-dog-cat.gif");
		}
	}
	&.dog {
		background-image: url("https://assets.codepen.io/430361/cat-dog-dog.png");
		&.animated {
			background-image: url("https://assets.codepen.io/430361/cat-dog-dog.gif");
		}
	}
	&.player {
		bottom: 72px;
	}
	&.computer {
		bottom: 112px;
	}
}
View Compiled
import React, { useState } from "https://cdn.skypack.dev/react@17.0.1";
import ReactDOM from "https://cdn.skypack.dev/react-dom@17.0.1";

/**
 * Speed of cat or dog each movement
 */
const MOVE_SPEED = 16;

/**
 * Position to win the game
 */
const WIN_POSITION = 288;

/**
 * Screen names
 */
const Screen = {
	Start: 0,
	Instructions: 1,
	Select: 2,
	Difficulty: 3,
	Game: 4,
	GameOver: 5,
};

/**
 * Playeble choice of characters
 */
const Character = {
	Cat: 0,
	Dog: 1,
};

/**
 * Value is the miss rate
 * [miss rate / 100]
 */
const Difficulty = {
	Easy: 70,
	Medium: 48,
	Hard: 12,
};

/**
 * Generate random number
 */
function random(min, max) {
	return Math.random() * (max - min) + min;
}

/**
 * Create a static component of letters
 */
function DisplayLetter(props) {
	const letters = props.letters.split("");
	
	if (letters === null || typeof letters === "undefined") {
		return null;
	}
	
	let lettersElm = [];
	
	for (let letter of letters) {
		if (letter === ">") {
			letter = "select";
		} else if (letter === " ") {
			letter = "space";
		}
		
		const lettersClassName = `letter-${letter}`;
		
		lettersElm = [...lettersElm, <div className={lettersClassName}></div>];
	}
	
	return lettersElm;
}

/**
 * Component of the game
 */
function CatDogGame() {
	// Current displayed screen
	const [screen, setScreen] = useState(Screen.Start);
	
	// Selected character
	const [character, setCharacter] = useState(null);
	
	// Difficulty of the game
	const [difficulty, setDifficulty] = useState(null);
	
	// Winner of the game
	const [winner, setWinner] = useState(null);
	
	// Position of the cat
	const [catX, setCatX] = useState(0);
	
	// Position of the dog
	const [dogX, setDogX] = useState(0);
	
	// Animated status of the cat
	const [catAnimated, setCatAnimated] = useState(false);
	
	// Animated status of the dog
	const [dogAnimated, setDogAnimated] = useState(false);
	
	// Rendering of displayed screen
	let DisplayScreen = null;
	
	if (screen === Screen.Start) {
		// 'Select' screen
		
		DisplayScreen = () => {
			return (
				<div className="cover-screen">
					<div className="letter-container letter-title">
						<DisplayLetter letters="cat dog race" />
					</div>
					<div
						className="letter-container letter-clickable"
						onClick={() => {
							setScreen(Screen.Instructions);
						}}
					>
						<DisplayLetter letters="tap here to start" />
					</div>
				</div>
			);
		};
	}
	else if (screen === Screen.Instructions) {
		return (
			<div className="cover-screen">
				<div className="letter-container letter-title">
					<DisplayLetter letters="cat dog race" />
				</div>
				<div className="letter-container">
					<DisplayLetter letters="instructions" />
				</div>
				<div className="letter-container">
					<DisplayLetter letters="1 just tap on the screen to move" />
				</div>
				<div className="letter-container">
					<DisplayLetter letters="2 if you move your opponent will also move" />
				</div>
				<div
					className="letter-container letter-clickable"
					onClick={() => {
						setScreen(Screen.Select);
					}}
				>
					<DisplayLetter letters="tap here to start" />
				</div>
			</div>
		);
	}
	else if (screen === Screen.Select) {
		return (
			<div className="cover-screen">
				<div className="letter-container letter-title">
					<DisplayLetter letters="cat dog race" />
				</div>
				<div className="letter-container">
					<DisplayLetter letters="select your character" />
				</div>
				<div
					className="letter-container letter-clickable"
					onClick={() => {
						setCharacter(Character.Cat);
						setScreen(Screen.Difficulty);
					}}
				>
					<DisplayLetter letters="cat" />
				</div>
				<div
					className="letter-container letter-clickable"
					onClick={() => {
						setCharacter(Character.Dog);
						setScreen(Screen.Difficulty);
					}}
				>
					<DisplayLetter letters="dog" />
				</div>
			</div>
		);
	}
	else if (screen === Screen.Difficulty) {
		return (
			<div className="cover-screen">
				<div className="letter-container letter-title">
					<DisplayLetter letters="cat dog race" />
				</div>
				<div className="letter-container">
					<DisplayLetter letters="select difficulty" />
				</div>
				<div
					className="letter-container letter-clickable"
					onClick={() => {
						setDifficulty(Difficulty.Easy);
						setScreen(Screen.Game);
					}}
				>
					<DisplayLetter letters="easy" />
				</div>
				<div
					className="letter-container letter-clickable"
					onClick={() => {
						setDifficulty(Difficulty.Medium);
						setScreen(Screen.Game);
					}}
				>
					<DisplayLetter letters="medium" />
				</div>
				<div
					className="letter-container letter-clickable"
					onClick={() => {
						setDifficulty(Difficulty.Hard);
						setScreen(Screen.Game);
					}}
				>
					<DisplayLetter letters="hard" />
				</div>
			</div>
		);
	}
	else if (screen === Screen.Game) {
		let catClassName = "character cat";
		let dogClassName = "character dog";
		
		// Rendering of Y Position of characters
		if (character === Character.Cat) {
			catClassName += " player";
			dogClassName += " computer";
		}
		else if (character === Character.Dog) {
			catClassName += " computer";
			dogClassName += " player";
		}
		
		// Animation of the characters
		if (catAnimated === true) {
			catClassName += " animated";
		}
		
		if (dogAnimated === true) {
			dogClassName += " animated";
		}
		
		// Rendering
		return (
			<div
				className="game-screen"
				onClick={() => {
					if (catAnimated === true || dogAnimated === true) {
						// Cannot tap if cat or dog is animated
						return;
					}
					
					// Movement of the computer
					let computerMove = MOVE_SPEED * random(1, 2);
					
					// Random generated number to determine computer movement
					const computerMoveRNG = random(0, 100);
					
					if (computerMoveRNG < difficulty) {
						computerMove = 0;
					}
					
					if (character === Character.Cat) {
						setCatAnimated(true);
						setCatX(catX + MOVE_SPEED);
						
						if (computerMove > 0) {
							setDogAnimated(true);
						}
						setDogX(dogX + computerMove);
					}
					else if (character === Character.Dog) {
						setDogAnimated(true);
						setDogX(dogX + MOVE_SPEED);
						
						if (computerMove > 0) {
							setCatAnimated(true);
						}
						setCatX(catX + computerMove);
					}
				}}
			>
				<div
					className={catClassName}
					style={{
						"--left": `${catX}px`
					}}
					onTransitionEnd={evt => {
						if (evt.propertyName.toLowerCase() !== "left") {
							return;
						}
						
						if (catX >= WIN_POSITION) {
							setWinner(Character.Cat);
							setScreen(Screen.GameOver);
						}
						
						setCatAnimated(false);
					}}
				></div>
				<div
					className={dogClassName}
					style={{
						"--left": `${dogX}px`
					}}
					onTransitionEnd={evt => {
						if (evt.propertyName.toLowerCase() !== "left") {
							return;
						}
						
						if (dogX >= WIN_POSITION) {
							setWinner(Character.Dog);
							setScreen(Screen.GameOver);
						}
						
						setDogAnimated(false);
					}}
				></div>
			</div>
		);
	}
	else if (screen === Screen.GameOver) {
		let winnerCharacter = null;
		
		if (winner === Character.Cat) {
			winnerCharacter = "cat";
		}
		else if (winner === Character.Dog) {
			winnerCharacter = "dog";
		}
		
		// Letters shown for the winner
		const winnerText = `${winnerCharacter} won the game`;
		return (
			<div className="cover-screen">
				<div className="letter-container letter-title">
					<DisplayLetter letters="cat dog race" />
				</div>
				<div className="letter-container">
					<DisplayLetter letters="game over" />
				</div>
				<div className="letter-container">
					<DisplayLetter letters={winnerText} />
				</div>
				<div
					className="letter-container letter-clickable"
					onClick={() => {
						setScreen(Screen.Select);
						setCharacter(null);
						setDifficulty(null);
						setWinner(null);
						setCatX(0);
						setDogX(0);
						setCatAnimated(false);
						setDogAnimated(false);
					}}
				>
					<DisplayLetter letters="tap here to play again" />
				</div>
			</div>
		);
	}
	
	// Rendering
	return <DisplayScreen />;
}

ReactDOM.render(
	<CatDogGame />,
	document.querySelector("#app"),
);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.