Skip to content

Instantly share code, notes, and snippets.

@chrisrzhou
Last active August 29, 2015 14:11
Show Gist options
  • Save chrisrzhou/b76c8e69f56f3f29cdc5 to your computer and use it in GitHub Desktop.
Save chrisrzhou/b76c8e69f56f3f29cdc5 to your computer and use it in GitHub Desktop.
ngQuotes

ngQuotes

bl.ocks.org link

Master typing through quotes, learn quotes through typing.

ngQuotes is an AngularJS typing game built using the Javascript regex engine. This is my personal quote repository of my inspirational heroes.


Other Notes

  • typeMatcher is written using a Javascript Regex engine

  • Regex might fail on special regex characters such as \[](){}

  • Quotes taken from wikiquotes

  • Get a sexy mechanical keyboard, seriously! :)


Analytics

(function() {
angular
.module("ngQuotes", [])
.factory("quoteFactory", quoteFactory)
.filter("percentage", percentage)
.directive("quote", quote)
.controller("quoteCtrl", quoteCtrl);
// Factory
function quoteFactory() {
var authors = [{
"name": "Albert Einstein",
"value": "einstein"
}, {
"name": "Jeff Bezos",
"value": "bezos"
}, {
"name": "Steve Jobs",
"value": "jobs"
}, {
"name": "Elon Musk",
"value": "musk"
}];
var quotes = {
"einstein": [
"Any intelligent fool can make things bigger, more complex, and more violent. It takes a touch of genius -- and a lot of courage -- to move in the opposite direction.",
"Imagination is more important than knowledge.",
"Gravitation is not responsible for people falling in love.",
"I want to know God's thoughts; the rest are details.",
"The hardest thing in the world to understand is the income tax.",
"Reality is merely an illusion, albeit a very persistent one.",
"The only real valuable thing is intuition.",
"A person starts to live when he can live outside himself."
],
"jobs": [
"We don't get a chance to do that many things, and every one should be really excellent. Because this is our life. Life is brief, and then you die, you know? And we've all chosen to do this with our lives. So it better be damn good. It better be worth it.",
"Quality is much better than quantity. One home run is much better than two doubles.",
"Innovation distinguishes between a leader and a follower.",
"That's been one of my mantras -- focus and simplicity. Simple can be harder than complex; you have to work hard to get your thinking clean to make it simple.",
"Your time is limited, don't waste it living someone else's life. Don't be trapped by dogma, which is living the result of other people's thinking. Don't let the noise of other opinions drown your own inner voice. And most important, have the courage to follow your heart and intuition, they somehow already know what you truly want to become. Everything else is secondary.",
"I think if you do something and it turns out pretty good, then you should go do something else wonderful, not dwell on it for too long. Just figure out what's next.",
"Sometimes when you innovate, you make mistakes. It is best to admit them quickly, and get on with improving your other innovations.",
"Do you want to spend the rest of your life selling sugared water or do you want a chance to change the world?",
"Being the richest man in the cemetery doesn't matter to me -- Going to bed at night saying we've done something wonderful -- that's what matters to me.",
"You can't connect the dots looking forward; you can only connect them looking backwards. So you have to trust that the dots will somehow connect in your future. You have to trust in something -- your gut, destiny, life, karma, whatever. This approach hsa never let me down, and it has made all the difference in my life.",
"No one wants do die. Even people who want to go to heaven don't want to die to get there. And yet death is the destination we all share. No one has ever escaped it. And that is as it should be, because Death is very likely the single best invention of Life. It is Life's change agent. It clears out the old to make way for the new. Right now the new is you, but someday not too long from now, you will gradually become the old and be cleared away. Sorry to be so dramatic, but it is quite true.",
"I think if you do something and it turns out pretty good, then you should go do something else wonderful, not dwell on it for too long. Just figure out what's next."
],
"bezos": [
"A brand for a company is like a reputation for a person. You earn reputation by trying to do hard things well.",
"What's dangerous is not to evolve.",
"Life's too short to hang out with people who aren't resourceful.",
"It's not an experiment if you know it's going to work.",
"There are two kinds of companies, those that work to try to charge more and those that work to charge less. We will be the second.",
"I believe you have to be willing to be misunderstood if you're going to innovate.",
"What consumerism really is, at its worst is getting people to buy things that don't actually improve their lives.",
"I think frugality drives innovation, just like other constraints do. One of the only ways to get out of a tight box is to invent your way out.",
"If you're competitor-focused, you have to wait until there is a competitor doing something. Being customer-focused allows you to be more pioneering.",
"Part of company culture is path-dependent -- it's the lessons you learn along the way.",
"If you never want to be criticized, for goodness' sake don't do anything new",
"The one thing that offends me the most is when I walk by a bank and see ads trying to convince people to take out second mortgages on their home so they can go on vacation. That's approaching evil.",
"We've had three big ideas at Amazon that we've stuck with for 18 years, and they're the reason we're successful: Put the customer first. Invent. And by patient.",
"In the old world, you devoted 30% of your time to building a great service and 70% of your time shouting about it. In the new world, that inverts.",
],
"musk": [
"Failure is an option here. If things are not failing, you are not innovating enough.",
"We have essentially no patents in SpaceX. Our primary long-term competition is in China. If we published patents, it would be farcical, because the Chinese would just use them as a recipe book.",
"Physics is a good framework for thinking. Boil things down to their fundamental truths and reason up from there.",
"If something is important enough, even if the odds are against you, you should still do it.",
"I could either watch it happen, or be part of it.",
"When Henry Ford made cheap, reliable cars people said, 'Nah, what's wrong with a horse?' That was a huge bet he made, and it worked.",
"Tuition costs are outrageous. Fortunately, they gave me a scholarship, so I only had to cover living expenses, books, etc., by working.",
"One was the Internet, one was clean energy and one was space."
]
};
var service = {
authors: authors,
getQuote: getQuote
};
return service;
function getQuote(author) {
var quoteString = randomChoice(quotes[author.value]);
var quote = {
base: quoteString,
input: "",
match: "",
mismatch: "",
rest: quoteString,
characters: quoteString.length,
words: 0
};
return (quote);
}
}
// Controller
function quoteCtrl($interval, quoteFactory) {
var ctrl = this;
// Assign controller functions
ctrl.init = init;
ctrl.reset = reset;
ctrl.nextQuote = nextQuote;
ctrl.randomQuote = randomQuote;
ctrl.textMatcher = textMatcher;
ctrl.startTime = startTime;
ctrl.stopTime = stopTime;
// Initialize quote and author
ctrl.init();
ctrl.randomQuote();
var timer; // create timer promise
// Controller Functions
function init() {
/* Initialize */
reset();
ctrl.authors = quoteFactory.authors;
}
function reset() {
/* Reset score and quote */
ctrl.time = 0;
ctrl.start = false;
ctrl.completed = false;
ctrl.summary = {
score: 0,
wpm: 0,
accuracy: 1,
mistakes: 0
};
}
function startTime() {
/* Start timer */
ctrl.start = true;
if (angular.isDefined(timer)) return; // Don't start new timer if timer is already defined
timer = $interval(function() {
if (ctrl.start) {
ctrl.time += 1 / 10;
}
}, 100);
}
function stopTime() {
/* Stop timer */
ctrl.start = false;
$interval.cancel(ctrl.timer);
}
function nextQuote() {
/* Reset to random quote of same author */
ctrl.reset();
ctrl.quote = quoteFactory.getQuote(ctrl.author);
}
function randomQuote() {
/* Reset to random quote of random author */
ctrl.reset();
ctrl.author = randomChoice(ctrl.authors);
ctrl.quote = quoteFactory.getQuote(ctrl.author);
}
function endQuote() {
/* Trigger for completion of quote typing */
ctrl.completed = !ctrl.completed;
ctrl.stopTime();
}
function textMatcher() {
/* Primary function for regex text matching */
var reInput = RegExp("^(" + regexString(ctrl.quote.input) + ")(.*)"); // build regex for quote.input
var reMatch = RegExp("^(" + ctrl.quote.match + ")(.*)"); // build regex for quote.match
var quoteInput = ctrl.quote.base.match(reInput);
if (ctrl.quote.input != ctrl.quote.base) {
ctrl.quote.mismatch = ""; // clear quote.mismatch
ctrl.summary.accuracy = (ctrl.quote.characters - ctrl.summary.mistakes) / ctrl.quote.characters; // update accuracy
ctrl.summary.wpm = ctrl.quote.words / (ctrl.time / 60); // update wpm
if (quoteInput) {
ctrl.quote.match = quoteInput[1]; // update quote.match capture group
ctrl.quote.rest = quoteInput[2]; // update quote.rest capture group
ctrl.quote.words = ctrl.quote.input.match(/\S+/g).length;
ctrl.summary.score += 1; // update score
} else {
quoteMatch = ctrl.quote.base.match(reMatch);
ctrl.quote.mismatch = quoteMatch[2][0]; // update quote.mismatch
ctrl.quote.rest = shiftString(quoteMatch[2], 1); // update quote.rest
if (ctrl.quote.input > ctrl.quote.match) {
ctrl.summary.mistakes += 1; // update mistakes
}
}
} else {
endQuote();
}
}
}
// Filters
function percentage($filter) {
/* Return number formatted as a percentage to given decimal place*/
return function(input, decimals) {
return $filter("number")(input * 100, decimals) + "%";
};
}
// Directives
function quote() {
/* Return directive quote*/
return {
restrict: "E",
controller: "quoteCtrl",
templateUrl: "quote.tpl.html",
replace: true
};
}
// Helper Functions
function randomChoice(arr) {
/* Return random choice in an array */
return arr[Math.floor(arr.length * Math.random())];
}
function shiftString(text, n) {
/* Shifts a string by n amount of characters */
return text.slice(n, text.length);
}
function regexString(text) {
/* Return a text with escaped backslashes on regex reserved characters. */
regex = [ // add escapes on regex reserved characters.
{
"key": /\?/g,
"value": "\\?"
}, {
"key": /\./g,
"value": "\\."
}, {
"key": /\+/g,
"value": "\\+"
}, {
"key": /\*/g,
"value": "\\*"
}, {
"key": /\^/g,
"value": "\\^"
}, {
"key": /\$/g,
"value": "\\$"
}
];
for (r = 0; r < regex.length; r++) {
text = text.replace(regex[r].key, regex[r].value);
}
return text;
}
})();
<!DOCTYPE html>
<html ng-app="ngQuotes">
<head>
<meta charset="utf-8" />
<title>AngularJS Quotes</title>
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" />
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/css/font-awesome.min.css" />
<link rel="stylesheet" href="style.css" />
</head>
<body ng-controller="quoteCtrl as quoteCtrl">
<div class="container">
<header>
<h1>ngQuotes</h1>
<p><i>Master typing through quotes, learn quotes through typing</i>
</p>
<hr />
</header>
<!-- form -->
<form class="form-horizontal">
<div class="form-group">
<label class="control-label col-xs-3">Select Author</label>
<div class="col-xs-4">
<select class="form-control" ng-model="quoteCtrl.author" ng-options="author.name for author in quoteCtrl.authors" ng-change="quoteCtrl.nextQuote()"></select>
</div>
<div class="btn-group">
<a class="btn btn-default" title="Next Quote" ng-click="quoteCtrl.nextQuote()"><span class="glyphicon glyphicon-forward"></span></a>
<a class="btn btn-default" title="Random Quote" ng-click="quoteCtrl.randomQuote()"><span class="glyphicon glyphicon-random"></span></a>
</div>
</div>
</form>
<hr />
<!-- quote -->
<div class="col-xs-offset-2 col-xs-8">
<quote></quote>
</div>
</div>
<!-- footer -->
<footer>
<p><a href="https://gist.github.com/chrisrzhou/b76c8e69f56f3f29cdc5" target="_blank">ngQuotes</a> by chrisrzhou, 2014-12-16
<br />
<a href="http://github.com/chrisrzhou" target="_blank"><i class="fa fa-github"></i></a> |
<a href="http://bl.ocks.org/chrisrzhou" target="_blank"><i class="fa fa-cubes"></i></a> |
<a href="http://www.linkedin.com/in/chrisrzhou" target="_blank"><i class="fa fa-linkedin"></i></a>
</p>
</footer>
<!-- scripts -->
<script src="https://code.angularjs.org/1.2.25/angular.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.4.13/d3.min.js"></script>
<script src="app.js"></script>
<script type="text/javascript">
// Hack to make this example display correctly in an iframe on bl.ocks.org
d3.select(self.frameElement).style("height", "1000px");
</script>
</body>
</html>
<div>
<!-- Quotes -->
<blockquote class="row row-padding">
<p><span class="match">{{ quoteCtrl.quote.match }}</span><span class="mismatch">{{ quoteCtrl.quote.mismatch }}</span><span>{{ quoteCtrl.quote.rest }}</span>
</p>
<footer>{{ quoteCtrl.author.name }}</footer>
</blockquote>
<!-- Typer -->
<div class="row row-padding" ng-hide="quoteCtrl.completed">
<textarea class="panel-primary" rows=6 ng-model="quoteCtrl.quote.input" ng-change="quoteCtrl.textMatcher()" ng-keypress="quoteCtrl.startTime()" placeholder="Start typing..."></textarea>
<p class="p-small pull-right">({{ quoteCtrl.quote.words }} words, {{ quoteCtrl.time | number: 1}} seconds)</p>
</div>
<!-- Scoreboard -->
<div class="row row-padding" ng-show="quoteCtrl.completed">
<div class="panel panel-success">
<div class="panel-heading">
Scoreboard
</div>
<div class="panel-body">
<p><strong>Score:</strong> {{ quoteCtrl.summary.score }}</p>
<p><strong>WPM:</strong> {{ quoteCtrl.summary.wpm | number: 1 }} <small>({{ quoteCtrl.quote.words }} words, {{ quoteCtrl.time | number: 1}} seconds)</small></p>
<p><strong>Accuracy:</strong> {{ quoteCtrl.summary.accuracy | percentage: 1}}</p>
<p><strong>Mistakes/Characters:</strong> {{ quoteCtrl.summary.mistakes }}/{{ quoteCtrl.quote.characters }}</p>
<div class="btn-group pull-right">
<a class="btn btn-default btn-sm" title="Next Quote" ng-click="quoteCtrl.nextQuote()"><span class="glyphicon glyphicon-forward"></span></a>
<a class="btn btn-default btn-sm" title="Random Quote" ng-click="quoteCtrl.randomQuote()"><span class="glyphicon glyphicon-random"></span></a>
</div>
</div>
</div>
</div>
</div>
body {
padding-bottom: 50px;
}
a,
a:hover, a:visited {
color: #D2A000;
}
textarea {
width: 100%;
}
.page-header {
padding-bottom: 25px;
}
.page-header p {
font-style: italic;
}
.match,
.mismatch {
text-decoration: underline;
}
.match {
color: blue;
}
.mismatch {
color: white;
background-color: red;
}
.p-small {
font-size: 12px;
font-style: italic;
}
.row-padding {
padding-top: 10px;
padding-bottom: 10px;
}
.footer-padding {
text-align: center;
}
footer {
color: white;
padding-top: 5px;
border-top: 1px solid gray;
font-size: 12px;
position: fixed;
left: 0;
bottom: 0;
height: 50px;
width: 100%;
background: black;
text-align: center;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment