From 27a49076a0af48a206e0132bdb0b8ad3468f4e0f Mon Sep 17 00:00:00 2001 From: Phantop Date: Thu, 20 Jan 2022 19:59:37 -0500 Subject: [PATCH] nothing at all was added in this commit --- _config.yml | 1 - readme.md | 2 - reads/l.md | 259 ++ reads/trans.md | 39 + .../default_100_percent/100-disabled.png | Bin 382 -> 0 bytes .../default_100_percent/100-error-offline.png | Bin 196 -> 0 bytes .../100-offline-sprite.png | Bin 9413 -> 0 bytes .../default_200_percent/200-disabled.png | Bin 479 -> 0 bytes .../default_200_percent/200-error-offline.png | Bin 269 -> 0 bytes .../200-offline-sprite.png | Bin 10107 -> 0 bytes trex/index.css | 158 - trex/index.html | 44 - trex/index.js | 2739 ----------------- trex/manifest.json | 24 - 14 files changed, 298 insertions(+), 2968 deletions(-) delete mode 100644 _config.yml create mode 100644 reads/l.md create mode 100644 reads/trans.md delete mode 100644 trex/assets/default_100_percent/100-disabled.png delete mode 100644 trex/assets/default_100_percent/100-error-offline.png delete mode 100644 trex/assets/default_100_percent/100-offline-sprite.png delete mode 100644 trex/assets/default_200_percent/200-disabled.png delete mode 100644 trex/assets/default_200_percent/200-error-offline.png delete mode 100644 trex/assets/default_200_percent/200-offline-sprite.png delete mode 100644 trex/index.css delete mode 100644 trex/index.html delete mode 100644 trex/index.js delete mode 100644 trex/manifest.json diff --git a/_config.yml b/_config.yml deleted file mode 100644 index fc24e7a..0000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-hacker \ No newline at end of file diff --git a/readme.md b/readme.md index 0e7c4e5..0857674 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,5 @@ # Some pages with some stuff in them: -[trex](trex) - [guides](reads/guides) [food](reads/food) diff --git a/reads/l.md b/reads/l.md new file mode 100644 index 0000000..97bbd5b --- /dev/null +++ b/reads/l.md @@ -0,0 +1,259 @@ +**L's Voice Training Guide** + +# Introduction + +Hey, friend. Want to change your voice? Well... you _can_ \- that's an option. + +Your voice is much more malleable than you may realize. There are at least a dozen different dimensions that you can learn to control independently to change the sound of your voice. The way you talk right now is not your one "true" voice, it is a _habit_. A habit that can be changed, if you so choose. + +I like to come at this challenge from two angles. One is to break down all the muscle movements involved and learn to manipulate them individually, through simple _drills_. The other is to listen to carefully selected example voices and learn to _imitate_ them, intuitively. You will get results much faster by using both, as you do in this guide. + +My approach to voice training is not a process of feminization. It is a journey in flexibility, to full _fluency_ over the entire possibility space of your voice. This guide is aimed at those of you who are already comfortable in the masculine end of the spectrum. With the exercises I've gathered here, you will familiarize yourself with the feminine end - and everything in between. + +Once you have gained complete freedom across your vocal range, you can _choose_ exactly where you'd like to live - whether that is within one voice, or two. Or three. Masculine, feminine, or androgynous, young or old, human or cartoon. Any or all. It's up to you. + +So now, **listen to [this clip](https://clyp.it/5eq3io3u)** to hear me demonstrate the eight most important elements for vocal feminization, gradually transforming a masculine voice into a feminine one. Then, **listen to [this clip](https://clyp.it/ggetrab2)** to hear how they sound one at a time, in isolation. With practice, you can learn to do this too! + +Most of the material in this guide can be traced back to the pioneering work of [Zheanna Erose](https://www.youtube.com/c/ZheannaErose) of [TransVoiceLessons](https://www.youtube.com/channel/UCBYlEnfAUbrYSwF0VujcmHA), as well as the excellent free tutorial videos at [New York Vocal Coaching](https://www.youtube.com/user/NewYorkVocalCoaching). Many thanks, also, to the [Scinguistics](https://cramdvoicelessons.blog/about/) community, and of course, all of you lovely people here on [r/transvoice](/r/transvoice)! + +I've organized this guide into four levels, from Foundations to Mastery, each split into three subsections. You could easily spend a month on each level - a week or two per subsection. But you don't have to master each subsection before moving on to the next. Just give yourself enough time to digest the material and get a feel for it, and then start the next subsection as soon as you no longer feel overwhelmed. + +Ready? Let's begin. + +~L + +# Level 1 - Foundations + +## 1\. Inspiration + +Start by watching [this video](https://youtu.be/dZKzuVfUv3E) for a really quick overview of the voice feminization process (and optionally, [this video](https://youtu.be/ynFqjE2AEGk) to learn more about the acoustic theory involved). Then watch [this video](https://youtu.be/xVAVi11kzbM?t=133) for a breakdown of the vocal anatomy involved. + +**Your homework is** to find a recording of a female speaking voice that you'd like to be able to imitate, that can serve as an _inspiration_ and a point of reference. It doesn't have to be the one perfect, ultimate voice - just find one or two examples that seem pleasant and relatable. Think of female actresses or characters with nice voices, or YouTubers or podcast hosts you enjoy (search for "female youtubers" or "female podcast hosts" if you need some ideas). Mine is [this podcast interview with Keon Saghari](https://drive.google.com/open?id=1pIUvhv59np_tDRK7w0s75IQ3ByIoG6bs). Go ahead and use that if you can't decide on one right now! + +Then start listening to it, at least a little bit every day. This will help you internalize the sounds and speech patterns of the voice that you like. And be on the lookout for new voices - if you find one you like better, start listening to that one instead! + +## 2\. Vocal Tract Length + +Next you want to start strengthening and learning to control the muscles that raise your _larynx_ (or voice box). This is how you shorten the _length of your vocal tract_, from your larynx to your lips, to match the proportions of a typical female vocal tract. Building these muscles will take a while, so we'll start with this first. + +Watch [this video](https://youtu.be/aWWevU4A5mU) and try the _swallow-and-hold_ exercise. You want to touch your larynx (Adam's apple) lightly with your finger, and then yawn and feel it move down, and then swallow and feel it move up. + +Once you have felt this a few times, watch [this video](https://youtu.be/mx4dPWKVt9o) and try the _"big dog, small dog"_ exercise. If you're having trouble with the small dog, it can help to start yawning, to bring the larynx down, and then start to swallow to bring the larynx up, and then stick your tongue out like a dog panting and say "ahh" in a whisper to make sure you're not closing off your throat. Then watch [this video](https://youtu.be/F6Noi2qERus) and try the _whisper siren_ exercise. You want to smoothly slide from a big dog "uhh" to a small dog "ehh" as your larynx slides upward. Again, place a finger or two lightly on your throat to feel your larynx move up and down. + +**Your homework is** to practice the whisper siren for few minutes whenever you remember, throughout the day - say, whenever you go to the bathroom. It's almost silent, so you can do it anywhere and practice holding your muscles in place at the top (the high end of the siren, or the small dog) to build strength. Eventually, you want to learn to lift your larynx easily, without straining the muscles in your jaw and neck. As you get more comfortable with it, try to relax your neck a little bit more each time, until you can do it without tension. + +## 3\. Pitch + +Keep doing the previous exercises every day, but when you're ready for something more, you can start working on your _pitch_, or how high or low your voice is. Pitch is just one of many elements, and not even the most important, but it's probably the most well-known difference between the average male and female voice. + +In addition to pitch, there are several _registers_ that your voice will lock into at different points along your range, each with a different sound quality. Watch [this video](https://youtu.be/CYEsGuldIQY) to hear the differences between a _chest voice_ and a falsetto (and a _mix voice_, which is technically the same register as your chest voice, your _modal register_). Follow along with the warmups in [this video](https://youtu.be/9-1Padxsmio?t=281), and then try switching between the registers a few times, both singing and speaking. + +Then download the Android app [Vocal Pitch Monitor](https://play.google.com/store/apps/details?id=com.tadaoyamaoka.vocalpitchmonitor&hl=en_US) (or [Vocal Pitch Monitor](https://apps.apple.com/us/app/vocal-pitch-monitor/id842218231) on iOS) and in the settings, change the _Scale_ to _F Major_ and check the box to _Display frequency in Hz_. With the app running, talk in your starting voice and see where your pitch falls, naturally. A typical male speaking voice will stay between F2 and F3 (which are marked by horizontal lines, since we set the Scale to F Major). Now try talking higher and higher in pitch, until your voice is in the female range, around F3 and above. Don't go higher than F4, though, or you'll sound like a cartoon character! + +You might find that you start in your modal register when you're in the male range, but flip over into falsetto at some point in order to get into the female range. Or you might find that you have to strain and shout to get that high. If that happens, just go back down to the pitch where you can still speak comfortably in your modal register and don't worry about going higher for now. + +**Your homework is** to set aside some time every day (say, half an hour) to warm up with the video above and then practice speaking in the female range (between F3 and F4) or as close as you can get without straining or going into a falsetto. You can just say random things that pop into your head, recite lines from memory, or read a book or reddit comments out loud, while keeping an eye on your pitch in Vocal Pitch Monitor. + +It might sound terrible, but that's okay - the important thing is to get used to speaking in that range. Drink water throughout and take a break if you feel your voice getting strained or hoarse. + +# Level 2 - Intermediate + +## 4\. Resonance + +Once you are comfortable with manipulating your larynx and your pitch, and you'd like an additional challenge, you can try doing them at the same time. That means, while you are trying to talk in the female range, you also raise your larynx to reduce your vocal tract length. Start by following along with the exercises in [this video](https://youtu.be/iTViDd0QPEI). + +It will probably sound pretty bad at first, but that's fine! Your goal at this stage is not to sound feminine, but to keep your pitch between F3 and F4 (with Vocal Pitch Monitor) and keep your larynx raised while talking (which you can feel by holding a finger lightly to your throat). + +Your voice should sound more buzzy and brassy, which you'd call a _bright resonance_ (or _bright timbre_), as opposed to the dark, hollow resonance of more masculine voices - and that's a good thing! Watch [this video](https://youtu.be/21ZfGPp-Ves) to hear a great demonstration of this effect - you want your voice to be in the upper-right quadrant of the diagram. + +Then, on top of that, you want to learn to arch your _tongue_ up and push it forward to reduce the amount of space in your mouth where sound can resonate. To get the feeling, whisper "kee" (as in "key") and keep pushing the middle of your tongue up high for the "ee" - just below where it touches the roof of your mouth to make the "k" sound. Say it a few times, while keeping your tongue clenched, pushing it a little higher each time. This is the smallest space you can make inside your mouth, the bright extreme of your _oral resonance_, opposite a yawn. + +That's great for saying an "ee" sound, but when feminizing the other vowels, your tongue will be lower than it is for the "ee" but still higher and more forward than it would be in your masculine voice. And you still want to feel a bit of tension in your tongue, that clench, throughout. Essentially, you want to talk with a small space at the front of your mouth. That makes it sound like you have a smaller mouth than you actually do, which makes you sound more feminine. + +Gaining mastery over your tongue is one of the trickiest skills of voice feminization, but it's arguably one of the most important. Get started on it by practicing the exercises in [this video](https://youtu.be/yFot-l2iVHw). Then watch [this video](https://youtu.be/biZN6zcBpVo) and try some _sirens_ and _trills_ across your range while raising your larynx and tongue. + +**Your homework is** to take your daily speaking practice, where you try to keep your pitch between F3 and F4, and spend at least half that time talking with your larynx raised as well, for a bright, buzzy sound. Then, as best you can, try to add in the tongue clench too, pushing it up and forward to brighten the sound even more. See how it sounds with your larynx raised or lowered, your tongue arched or relaxed, and your pitch high or low, as well as in a whisper. + +This is likely to cause a lot of tension in the muscles of your neck and throat at first, so do trills and yawn every so often to help them relax again. You can even try lying on your back while practicing, to force your body to relax. And of course, sip water throughout your practice session and take a break when your voice gets too tired or hoarse. + +Also, keep practicing your whisper sirens multiple times a day, but add a whispered "kee" at the end of each one to bring your tongue up. This will allow you to go even higher with the siren and make a really tiny dog sound! Again, hold those muscles in place at the top and really clench your tongue. At the same time, try to relax as much tension as you can in your jaw and neck while still holding the same shape. + +## 5\. Open Quotient + +Go ahead and breathe a sigh of relief, because it's time for something a little easier! + +Feminine voices generally sound _softer_ and more _breathy_ than masculine voices. Marilyn Monroe is an iconic example of this, as you can hear in [this video](https://youtu.be/ikUjhv4iT58). + +When you try to speak in the upper range of your modal register without going into a falsetto, the natural tendency is to strain to reach those higher notes, which makes your voice sound harder, not softer, and not particularly feminine. This is because you put a lot of _compression_ on your vocal folds (vocal cords), squeezing them together more tightly. You want to learn to use less compression for a softer sound, where your vocal folds stay open more ( _open quotient_) while vibrating. With high compression (closed quotient), adding breathiness will just result in a strained sound like Ash Ketchum from Pokemon, as in [this video](https://youtu.be/s-0DuYcWeBE). + +To learn to control the compression in your voice, start by watching [this video](https://youtu.be/J9K74QEzntA?t=240) and trying the _"ah-ha"_ exercise and the _vowel slides_. Then download the Android app [Spectroid](https://play.google.com/store/apps/details?id=org.intoorbit.spectrum&hl=en_US) (or [Spectrogram Pro](https://apps.apple.com/us/app/spectrogram-pro-with-super-smooth-60hz-update/id415301721) on iOS), and in the audio settings, change the _Desired transform interval_ to _10 ms (100 Hz)_ and check the box to _Stay awake_ in the display settings. + +With the app running, start by saying "ahh" for a few seconds in your normal speaking voice. In the scrolling display, you should see a bunch of bright yellow lines showing up against the purple and pink background noise. Then whisper "ahh" for a few seconds, just with your breath. You should see some faint pink smudges, but no yellow lines. Now, heave a big, breathy sigh while saying "ahh..." in a soft, relaxed voice. Ideally, you will see faint yellow lines melding into a background of pink smudges. This is what it looks like when your voice has a high open quotient. It's somewhere in between a normal voice and a whisper. + +**Your homework is** to spend a few minutes before your other voice exercises, to slide between a whisper and your normal speaking voice. It's a good warmup! You can start with one long, whispered "ahh" that you gradually turn into a spoken "ahh" and then back to a whisper, just by changing the compression. Do this with the Spectroid app running, so you can see the change as well as hear it. For a bit more of a challenge, try smoothly changing from a whisper, to a soft voice, to a normal voice while speaking or reading out loud. + +Of course, you can also play with this during your daily speaking practice. See if you can make your voice a little softer, or really breathy, or changing from one extreme to another while still maintaining the feminine aspects of pitch and resonance that you've been working on. + +## 6\. Intonation + +All right. It's time to start imitating some voices! + +_Intonation_ is the rise and fall of pitch as you speak. Masculine voices tend to be very monotone, where the pitch changes very slightly and infrequently from word to word, and important words are spoken louder for emphasis. Feminine voices tend to vary a lot in pitch, across a wider range, and big pitch changes are used to draw attention to the important words. Oftentimes, every word is spoken at a different pitch than the one before, and sometimes the pitch will change multiple times within a single word! + +The clearest example of this can be found in that great figurehead of exaggerated femininity, the Disney princess. Watch [this video](https://youtu.be/O5zntdPGd00?t=223) for a virtuosic vocal tour through a diversity of Disney princess voices, and try closing your eyes and listening to the rise and fall of pitch in each one. You can even pull out your Vocal Pitch Monitor app and watch the pitch rise and fall on the screen! + +**Your homework is** to spend some time every day trying to talk like a Disney princess, in addition to all your other exercises. Listen to [this clip](https://soundcloud.com/princessvoiceover/about-princess-voice-over) with Vocal Pitch Monitor open, watching the pitch rise and fall, and pause every sentence to try parroting back what you just heard, with the same rise and fall in your pitch. Don't worry about sounding good, and don't worry about your larynx or resonance either. Just focus on the pitch, and go ahead and use your falsetto to go high if you can. It will sound fake and silly, and that's okay - enjoy it! + +The only thing that you should try to do, other than match the pitch, is to _smile_ while you speak, stretching your _lips_ across your teeth, and make your mouth opening a little smaller, like you're saying "ooh" (just pretend you're a dainty princess). This will also brighten your resonance a tiny bit, and make your voice sound that much more feminine. Use this for your princess voice practice, but also for your resonance practice as well, tightening your lips in addition to raising your larynx and tongue. + +If you get bored of using that clip or just want to find something in your own accent, feel free to practice with other example voices, like in [this video](https://youtu.be/G4pLz-_CImE). Or make up your own princess voice if you can - the sillier the better. Delight in the ridiculousness of it all, and just have fun with it! + +## 7\. Articulation + +Ready to refine your resonance with another round of voice impressions? Like, totally! + +So far, you've been working to brighten the timbre of your voice by raising your larynx and tongue to shrink the size of your vocal tract. But that doesn't always translate into sounding more feminine. If you want to sound feminine, you're going to have to get really specific with the way your tongue moves inside your mouth, to hone your oral resonance. That is, you'll have to fine-tune the way you say all your vowels and consonants - your _articulation_. + +Fortunately, there's a fun way to do this! To put it simply, you want to talk like a Valley girl. Watch [this video](https://youtu.be/XPv5vradEw8) to hear what a stereotypical Valley girl sounds like. Not only do they rival Disney princesses in terms of their extreme intonation, they also brighten their vowels to a much greater degree than you'll hear in any other American accent (and if you have a British accent, try imitating the brighter vowels of an Australian accent instead, as in [this video](https://youtu.be/mIBg-w6TNLE)). + +Some vowels are naturally dark in resonance (like "uh") because your tongue is low and there is more space in your mouth. Some vowels are naturally bright (like "ee") because your tongue is high, leaving a small amount of space in the back of your mouth. What Valley girls do, and what you want to learn to do yourself, is to replace each vowel sound in your speech with a slightly brighter vowel. Your "oh" becomes "uh", your "uh" becomes "eh", your "eh" becomes "ih", your "ih" becomes "ee" and so on, as in [this video](https://youtu.be/dZKzuVfUv3E?t=62). These are called _vowel mods_. + +For a Valley girl, "ummm..." becomes "emmm..." and "lah-eek, toh-duh-lee" becomes "lih-eek, teh-dih-lee" - try saying each one! Then see if you can say them with vowels that are in between the two extremes - that's the sweet spot. Whatever your accent, the key is to push your tongue up and forward and just use the front for articulation. It helps to close your _jaw_ so your teeth are almost touching, to make it impossible for your tongue to drop too low. + +There are also _consonant mods_, because consonants can be brighter or darker too. Consonants interrupt the flow of air from your lungs, by temporarily blocking your vocal tract with your lips, tongue, or soft palate. A heavier, more intense interruption will generally come across as more masculine, while a lighter, more subtle articulation will come across as more feminine. + +For example, if you explosively say "bah!" in disgust, it will sound more masculine. If you quickly say "bababababa" it will sound more feminine, especially if you tighten your lips together to minimize the movement. If you say "arr!" like a pirate, it will sound more masculine. If you stop your tongue halfway through saying that heavy "r" sound, it will sound more feminine, especially if you also raise the back of your tongue at the same time to shrink the space in your mouth. Watch [this video](https://youtu.be/lGHoIjPJ7nc?t=164) to hear the difference between a masculine and feminine "r" and "s" sound. + +**Your homework is** to spend some time every day trying to talk like a Valley girl, in addition to all your other exercises! Start with Lumpy Space Princess in [this video](https://youtu.be/hnFcLGwgNSc?t=86). Pause after each line she says and try to repeat it with the same vowel mods - "to get it" becomes "teh get eht" in her accent. Then try it again with a whisper. Do the same with [this video](https://www.youtube.com/watch?v=fETaoJBhY9g), [this video](https://youtu.be/l2OhyLfrogI), and the Valley girl video above. Don't worry too much about pitch, but pay close attention to the sounds of each vowel and consonant. + +When you start to feel comfortable with these vowel and consonant mods, try incorporating them into your other voice exercises as well. You don't have to go to such an extreme that you sound like a Valley girl, of course, but play around with them to see how they change the sound! At the same time, focus on clenching your tongue (like "key") to brighten each vowel and consonant as much as possible, while keeping your lips tight and your jaw almost closed to minimize the space inside. + +## 8\. Vocal Twang + +Now, if you want to be heard above a lot of background noise, without relying on the booming strength of a masculine voice, you'll need to add a piercing brightness that will give your voice more power and clarity while also making it more feminine at the same time. + +The secret is a singing technique known as _vocal twang_. It sounds almost like nasal resonance, where the sound goes through your nose, but it's not. Instead, vocal twang is created by squeezing what's known as the _aryepiglottic sphincter_ (or _AES_), which is right at the top of your larynx, above the vocal folds. You can see and hear the difference dramatically in [this video](https://youtu.be/ERAFQic5A-4), showing the throat of a female singer as she first hums (nasal resonance), then sings normally, then sings with a very pronounced twang. + +The best way to learn this is through imitation. Quack like a duck, cry like a newborn baby, or say "I AM A ROBOT" in your best robot voice. They may not be pretty, much less feminine, but all of these are great examples of vocal twang pushed to an extreme. And you want to learn the extreme and then dial it back from there - it's a lot easier that way. So take a moment to try a few quacks, to cry like a newborn, and talk like a robot. Then watch [this video](https://youtu.be/1BLVrYKmwvc) and follow along with the singing exercise at the end. + +Next, open up the Spectroid app, and say "uhh" in a relaxed voice. Notice where the yellow lines appear on the scrolling spectrograph. Then try saying "quack" like a duck or "I AM A ROBOT" in a robot voice - something with a lot of vocal twang. If you've done it correctly, you'll see a bright yellow band of lines appear past the 1000 Hz mark. The more you constrict the AES, the brighter the yellow band will be and the more you'll hear a harsh edge to your sound. Watch [this video](https://youtu.be/xAvCrxaLRvI) for a great example of this, starting with a dark "uhh" and gradually raising the larynx, raising the tongue, and then adding the vocal twang for maximum brightness. + +**Your homework is** to spend a few minutes every day practicing your vocal twang, perhaps right after your open quotient warmup. With Spectroid running, try to imitate the video and say "uhh" in a relaxed voice and then gradually slide into a harsh, twangy "ehh" like a duck quacking, and back down to a relaxed voice again. Watch the spectrograph and try to get that yellow band as bright as possible when you add the vocal twang. Then pinch your nose shut too, so you don't accidentally cheat by using nasal resonance instead of twang! You should be able to do this just as easily with your nose pinched shut - otherwise you'll end up sounding like Squidward, as in [this video](https://youtu.be/E5BzhG_sKUw?t=43). + +Then try adding different degrees of twang to your speaking practice, to see how it feels and sounds. You could go all the way and sound like SpongeBob SquarePants, take it out completely like his friend Patrick, or add just a hint of it and sound, well, more feminine. See what you like! + +Vocal twang is also really great for feminizing your singing, if you're into that. Skillful use of vocal twang can turn a weak falsetto into a powerful head voice, and increase your upper range by an octave or more. It's also the secret behind CeeLo Green's distinctive singing voice, combining a dark oral resonance with a lot of bright vocal twang and a bit of nasal resonance, as you can hear in [this video](https://youtu.be/bKxodgpyGec). If you start there and then brighten your oral resonance with a raised larynx and tongue, you can sound like a female singer. Seriously. + +## 9\. Throat Closure + +So far you've learned almost all of the little levers you can use to change the sound of your voice. But there's one (or two!) more to get familiar with, at the back of the throat. You want to learn to move your _soft palate_ and your _pharynx_ to shrink the space in your throat, just like you've used your tongue and lips to shrink the space in your mouth. The effect is subtle, but it can often make the difference between an authentically feminine voice and one that just sounds... off, somehow. + +You can see a very brief overview of the technique in [this video](https://youtu.be/lGHoIjPJ7nc?t=68). But to make it easier, you'll want to start with just the soft palate. Look in a mirror and say "ahh" and then "ung" and watch how the back of your tongue comes up, while the very back of the roof of your mouth (the soft palate) comes down slightly to meet it. When they touch, the air is blocked off from your mouth and forced through your nose instead, creating a hypernasal sound. Try the exercises in [this video](https://youtu.be/phuaeXjWpSQ) to learn to control your soft palate, and by extension, your nasal resonance. Generally, you want to reduce this nasality for a more feminine sound. + +The next thing you want to learn is how to constrict your pharynx, or close your _pharyngeal wall_. Doing this will bring in the sides of your throat just below the soft palate ( _oropharynx_), pushing your tonsils against the back of your tongue. How do you do it? Gargling. Say "ahh" while you tilt your head back and gargle, then tilt your head upright again and try to keep the sound going. Or say "ahh" from the top of a swallow-and-hold, or even a whisper siren. Eventually, you want to be able to say "ung" while sticking your tongue way out - the only way you can do that is to push in the walls of your oropharynx, because your soft palate can't reach the back of your tongue while it's stretched out of your mouth. If you talk at the same time, you will sound like Meatwad, as in [this video](https://youtu.be/8GjFmjBMizc?t=71). + +If you start with a Meatwad voice, and you bring your pitch up into a falsetto with a fair amount of nasal resonance and vocal twang and a lot of open quotient, what do you get? You get an Elmo voice, as in [this video](https://youtu.be/l2ppLtHbag4?t=23s). The reduced space in the back of the mouth and throat is what gives Elmo's voice its child-like quality. Of course, if you take that Elmo voice and bring the pitch down while constricting your pharynx as much as possible, you get a Smeagol or Gollum voice, as in [this video](https://www.youtube.com/watch?v=64mWOoj68qo&t=252). For Smeagol, you want to constrict not only the oropharynx but also the _hypopharynx_, further down the throat, to create a dampened sound like a sob or an old man. + +You don't have to take it that far, but if you add just a hint of oropharyngeal throat closure, you can make your voice sound younger and more feminine. This is most clearly demonstrated with a "loli" voice, as in [this video](https://youtu.be/pzgqke3uNxE). If you whisper with a feminine vocal posture and then squeeze in the back of your mouth and throat a little, you can sound like a cute anime girl. Try it! + +**Your homework is** to spend some time every day trying to talk like Meatwad and the other characters, by playing with your soft palate and pharynx position. And be sure to try whispering too, especially for the loli voice. You can download the Android app [Echo](https://play.google.com/store/apps/details?id=uk.co.projectneon.echo&hl=en) (or [Voice Back](https://apps.apple.com/us/app/voice-back-sing-speak/id1071730240) on iOS) and use it to record and play back your character voices, to hear how they actually sound. Don't worry about whether they match a specific character exactly, just see what sort of funny voices you can make! + +Then try adding a hint of throat closure to your speaking practice, to see if you can use it to sound more feminine. If you're feeling brave, you can even record and play back your feminine voice experiments to help guide your practice. Good luck! + +These are also important elements for singing feminization, especially for a voice like Britney Spears, which combines a fair amount of oropharyngeal constriction with a very _hyponasal_ sound, as if from a stuffy nose. You can hear it exaggerated in [this video](https://youtu.be/je-UHCniqnk). Imitating that sound is a great way to learn to achieve throat closure while simultaneously eliminating nasal resonance. + +## 10\. Exploration + +Congratulations. You have learned to manipulate every single element involved in vocal feminization! Now for the fun part - you get to play around with all these knobs and dials on the control panel of your voice, and find the configuration that you like best. You get to _explore_ the possibility space of your voice! + +A great way to do this is with a _mantra phrase_. The idea is to say the same phrase over and over again while tweaking different aspects of your voice, so you can easily compare the sound without thinking about what you're going to say. Watch [this video](https://youtu.be/XNPPSd3AVwM) for an overview of the concept and some great example phrases that you can use yourself. + +Now, try one of my own mantra phrases, which I designed specifically to help you practice and get a feel for your brightest, most feminine sound: + +_"Keep it cutesy!"_ + +Try saying it a few times in a feminine voice, then whisper it a few times. If you break it down and stretch it out, it sounds like "keee-p ihhh-t keee-yooo-tsss-seee!" Try it. + +When you say "k" the top of your tongue presses against the roof of your mouth to block off the air, and when you say "ee" your tongue drops down to let the air through. Try whispering "kee" while keeping your tongue pushed up toward the "k" position, so it moves as little as possible to get to the "ee" instead of dropping down so much. That will create your brightest possible "ee" sound. + +Try saying "keep" with that bright sound - your tongue should hardly move at all. Then try saying "cute" by starting with that "kee" sound and then pushing your tongue slightly down into a "yoo" sound. Whisper it first, and then say it, moving your tongue as little as possible, so the brightness of the "kee" carries into the "yoo" because your tongue is still pushing up toward that "k" position. + +Then whisper "tsss" and try to move your tongue as little as possible from the "t" to the "s" sound. That will create a brighter, sharper "s" which will come across as more feminine. At the same time, keep pushing the back of your tongue up toward the "k" position to brighten it further. This applies to all your sibilants, by the way - "s", "z", "sh", "zh", "ch", "j" and so on! Then try whispering "see" - sliding from the "s" back to the brightest possible "ee" that you can make. + +Now put it together. Say "cutesy" - stretch it out like "keee-yooo-tsss-seee" - first in a whisper and then spoken in a feminine voice. Say "keep" and for the "p" sound, really tighten your lips into a small but narrow opening, and keep them tight as you touch your lips together briefly for a subtle, unobtrusive "p" sound. This applies to all your labial consonants - "p", "b", "m", "w", "f", "v" and so on - and you want to keep your lips somewhat tight even for your other consonants and vowels, because it will make them brighter. So use the word "keep" as a reminder of the posture of your tongue and lips that will produce a bright, feminine oral resonance. + +Whisper and then say the whole thing, at a pitch that is about as high as you can comfortably speak without going into your falsetto. "Keep it cutesy!" You can stretch out all the sounds to really get a feel for them and find that brightest position. As you say the vowels, practice pushing the brightness to a buzzy, brassy extreme, raising your larynx, pushing the back of your tongue up, and layering on the vocal twang to find that harsh edge. The only thing you want to avoid is nasal resonance. So also try saying it while pinching your nose shut - it should sound almost the same, whether you pinch your nose or not. Practicing this will help you access and lock in your brightest, _cutest_ vocal posture and pitch, just by saying the phrase! + +Once you get comfortable with that, you can also try some of my other mantra phrases: + +_"Keep calm and carry on."_ + +This mantra phrase is similar, but with some darker vowel sounds like "ahh" in "calm" which you can practice saying while still clenching your tongue up toward that initial "k" position. There are also "r" and "l" sounds to practice brightening, again, by pushing your tongue up at the same time. Practice whispering it, stretching it out, and saying it with the brightest sound you can make. + +_"We're beautiful creatures."_ + +This is my favorite mantra phrase. It starts with a "w" to remind you to keep your lips small and tight, then an "ee" sound to remind you to keep your tongue up, then dips into an "r" sound to help you practice saying "r" without dropping your tongue. Then the "b" tests your lips again, and the "bee" to the "yoo" helps you practice keeping that "ee" position with your tongue even while saying the darker vowels. And there is a great diversity of consonants and vowels to practice throughout the rest of the phrase, but no nasal sounds, so you can easily test for nasal resonance by pinching your nose shut. Plus, it's true - and don't you forget it! + +_"Breathe and smile, smile and breathe."_ + +This is another one of my favorite mantra phrases, both for its message and for its phonetic properties. It starts with a bright "ee" vowel for the first word, but has a number of more complex combinations of vowels and consonants that are helpful to practice. And it's a good reminder! + +Positive psychology and neuroscience tell us that the easiest way to feel good is simply to smile - the neural connections that make you smile when you're happy also make you happier when you smile! And a slow, gentle, deep breath through your nose is the easiest way to calm down your nervous system. Whenever you say this mantra phrase, take a moment afterward to make a subtle, almost imperceptible smile with your lips and then slowly fill your lungs as you inhale through your nose, spreading that smile throughout your whole body. Savor the feeling of peace. Then gently let the breath go, exhaling through your nose as you slowly relax your smile. Do this whenever you remember, as many times as you want. It's free! + +**Your homework is** to practice these mantra phrases, and any other mantra phrases you like, to lock in your most feminine voice before your regular speaking practice every day, after warming up. You can also say them throughout your practice as a reminder, or even throughout your day! Try whispering them, too - not only so you can stealthily practice even with other people around, but so you can focus on the resonance, without worrying about pitch! Ideally you want to get in the habit of whispering to yourself in different voices, whenever you can get away with it - this will really accelerate your progress. + +Also, use these mantra phrases to experiment with different feminine voices, to see what you like - bringing the pitch higher or lower, adjusting your larynx and tongue position, using more or less open quotient, or vocal twang, or throat closure, and playing with different patterns of intonation and articulation. Record and play back each experiment with the Echo app, and see what sounds better to you and what doesn't. The goal is not perfection, it's exploration! Then try practicing those voices while speaking for longer amounts of time - reading out loud or just saying whatever comes into your head. And whenever you get discouraged or frustrated, take a moment to _smile and breathe!_ + +## 11\. Polishing + +Once you feel comfortable playing with the many possibilities of your voice, you can start honing in on the feminine voice that you actually want! It's time to _polish_ your voice. + +Remember that voice clip you chose in the beginning as the female voice you'd like to imitate? Hopefully you've been listening to it regularly this whole time, but if not, now is the time to start! + +Find that clip and play back a sentence or two. Then use the Echo app to record yourself saying the same sentence. Listen to the reference clip and then your own recording, and without judging it as good or bad, try going through each element of your voice and noticing where it is similar or different. How does the pitch compare? The resonance? The intonation? And so on. + +If an element is different, try playing around with it a few times and then record yourself again, doing your best to match the reference clip. Do that for each element. It's not going to be perfect in one pass, but it's the polishing process that counts. And of course, you don't have to sound exactly like your inspiration voice - it's just a beacon that can help you find your way to the voice you want. And this is just another exercise to practice. + +Still, you might find that it's difficult to deconstruct the differences in a voice clip just by listening to it. But don't worry! It's a skill you can practice, just like anything else. And fortunately, there's a great way to practice it... + +Every day, people post voice clips on [r/transvoice](/r/transvoice), looking for feedback. And you are going to train your ear by listening to those clips, analyzing them, and responding with your kind and honest feedback. When you are ready, you will join them by posting a clip of your own voice! But first, you will need to create an account on [reddit.com](https://reddit.com), if you haven't already. Do it now - it's free! + +Then, go to [r/transvoice](/r/transvoice) and find a voice clip that someone has recently posted. First, listen to the clip and just think about pitch. Does it sound to you like the pitch is in the male range, or the female range, or somewhere in between? Open up Vocal Pitch Monitor and play the clip again, and see where the pitch actually falls. Then listen to the clip while just focusing on the resonance, the timbre of the voice. Does it sound hollow and masculine, or bright and feminine, or more ambiguously androgynous? You can watch [this video](https://youtu.be/21ZfGPp-Ves) for reference - where would you place the sound on the diagram? + +Once you've established the pitch and resonance, you can listen again for the other elements of the voice. Listen for the intonation - the way the pitch rises and falls. Is it monotone or musical? Listen for open quotient - is the voice soft, or hard and strained? Listen for vocal twang - is there a bright, piercing edge to the sound? And is it too little or too much? How is the articulation? Does the resonance drop or sound fake on certain vowels or consonants? Is there too much nasal resonance? Would it sound better with a little pharyngeal constriction? + +As you analyze the clip, make note of all your observations in a comment on the post. You don't have to say whether the voice sounds _good_ or _bad_ \- just describe what you hear, starting with pitch and resonance, and then any other details that you notice. If you're not sure about something, you don't have to say anything about it. Then, if the person who posted the clip is actively soliciting feedback on voice feminization, and there are one or two elements that you think they could work on next, share a link to this guide and tell them which sections they should check out. Don't worry about whether your feedback is perfect! Just be kind and honest, and the community as a whole will benefit. + +**Your homework is** to find a new voice clip on [r/transvoice](/r/transvoice) every day (ideally one with no comments yet, or very few) and leave a comment on it with your analysis and suggestions. If you do this daily, not only will you develop a very discerning ear over time, you will also help the community thrive! + +Also, spend some time every day listening to and imitating your female voice reference, along with all your other exercises. Use Echo to record and play back your attempts, and analyze them just like you analyze the clips on [r/transvoice](/r/transvoice). You may find that a voice that seems too exaggerated in your head actually sounds naturally feminine in a recording - and to other people. Be kind to yourself, and trust the process. Polishing takes time. + +When you are ready to share a clip of your own voice with the world, create a free account on [clyp.it](https://clyp.it) and record yourself speaking at least a few sentences in your feminine voice, and perhaps a mantra phrase or two. If you're up for it, record the same thing in your starting voice as well, for comparison. Make sure to set the clip to _Public_, then post the link on [r/transvoice](/r/transvoice) and ask for feedback! + +Regardless of what people say, the important thing is to craft a voice that _you_ are happy with. And you are the only one who gets to decide what that means for you. + +## 12\. Performance + +Eventually, there will come a time when you are pretty happy with your voice, and you want to be able to start using it in front of other people. It might not be perfect, but you feel compelled to tackle what may be the most challenging step of all: going from practice to _performance_. + +There are two parts to this. One is learning to launch into your feminine voice whenever you choose, even when you haven't warmed up. And the other is getting over the performance anxiety of using your voice in front of other people - which is totally normal! You just have to take it one step at a time. + +To start with, you want to train your brain to experience your feminine voice as the default, not the exception. One way to do this is by _bookending_ your day with your voice, by practicing a few mantra phrases in your feminine voice immediately after waking up, before using your voice for anything else, and then again as the last thing you do before going to sleep. The morning is often the hardest time to feminize your voice, so this will allow you to practice that worst-case scenario right away and prime the rest of your day with the voice you want. Also, whatever you practice just before sleeping will be given higher priority when your experiences are consolidated into long-term memory. + +Then, take some time every day to practice _alternating_ between your masculine starting voice and your feminine voice. Read a book out loud and switch voices on every paragraph, or read reddit comment threads and use one voice for the original poster and another for the replies. If you tend to drift from your feminine voice into a more androgynous voice over time, you can also practice alternating between your feminine and androgynous voices to help differentiate them in your mind. + +To make it easier to shift into your feminine voice, you can start with a mantra phrase to help you lock in the sound. This is something you can even do in front of other people, if you whisper it or practice a _stealth mantra phrase_, like "ummm, let's see..." or "ummm, so..." You can use the "ummm" to find the pitch, sliding upward until you reach the right range, and then use the "let's see..." or "so..." to find your resonance. Sneaky! + +**Your homework is** to practice these exercises every day, in addition to any of the previous exercises that you still find helpful. Then, you want to slowly start pushing your comfort zone when it comes to performing your voice in front of other people. + +First, find a supportive friend or two, and tell them that you'd like their help in practicing your voice. If you don't feel comfortable asking anyone you know, you can try finding an anonymous friend on the [TransVoice Discord](https://discord.gg/FAmXNEJ) or the [Scinguistics Discord](https://discord.gg/RjatswF). Tell them that you would like to be able to call them up and have a conversation in your feminine voice, without having to explain yourself, and without them commenting on whether your voice is good or bad. You just need them to listen and be patient with you. + +Then, when you have warmed up by practicing your voice in private, call your friend and speak to them in your feminine voice. If all you can do is say a mantra phrase before lapsing into your starting voice, that's awesome! You've overcome the biggest hurdle. Next time you feel up for it, you can try speaking a little longer, whether you're just reading out loud, or having an entire conversation. And don't worry about whether your voice sounds perfect or not - you can work on that by yourself. Just focus on getting comfortable using your voice with another person. + +Once you feel comfortable calling your friend from the middle of a practice session, start challenging yourself a little more. Try meeting up with your friend in person and then, with your backs turned to each other, say a mantra phrase in your feminine voice. Then try doing it while facing each other. Eventually, you can work your way up to having an entire conversation in person, not only in private, but in a public place like a restaurant! + +Of course, when you are out in the real world, it's often difficult to be heard over all the background noise. The important thing is to use more vocal twang instead of straining, while keeping your voice soft with open quotient. You can practice this on your own by playing [this video](https://youtu.be/jAg6tyC9Xxc) and trying to speak over it. + +As you get more confident, you can start using your feminine voice more and more throughout your life - even while coughing and laughing, as in [this video](https://youtu.be/_DP6KB5j2qQ) or [this video](https://youtu.be/IP-mRByleSc). Love yourself, and enjoy the journey! + +~L diff --git a/reads/trans.md b/reads/trans.md new file mode 100644 index 0000000..3b8d12a --- /dev/null +++ b/reads/trans.md @@ -0,0 +1,39 @@ +# [Ellie's Shaving Guide for Body Hair](https://teddit.net/r/MtF/comments/f1y4be) + +# [How To Un-Fuck Your Legs: The Complete Guide](https://teddit.net/r/SkincareAddiction/comments/d81uyx) + +# [Welcome to Build a Trans Child's Parentâ„¢!](https://teddit.net/r/transgendercirclejerk/comments/khm1va) + +# [9 steps for a more typically female voice (MtF notes)](https://teddit.net/r/transvoice/comments/7krqov) + +Hum to find resonance and note the position of your larynx. I try to make my upper teeth vibrate. + +Lift the back of your tongue to make your mouth smaller. You want to be talking largely with the front of your mouth. + +Tighten your throat/airway. Relax into position, so you don't strain or sound especially pinched. + +Add some airiness to your voice to compensate for the tight throat. Bring your pitch up and stay out of falsetto. + +Pull back your lips and smile slightly. This slightly shortens your airway and has a minor but noticable effect on the quality of your voice. + +Emphasize with pitch instead of volume. It helps to pretend you're kind of excited, sometimes. Enunciate. + +# [ntr4ctr voice training](https://teddit.net/r/transgendercirclejerk/comments/iu6gal) + +First, you're going to want to put your hand on your throat, and feel your larynx. Then, you're going to swallow. You should be able to feel your larynx move up and back. This is happening because of the tensing of a series of muscles that automatically happens when you swallow, but that you probably aren't used to using consciously. Repeat this until you get a feel for the muscles that you're using. + +Next, you're going to put your hand on your throat and swallow again, but this time, instead of releasing those muscles, you're going to want to keep them tightened even after your swallow. + +What's you're going to do next, now that you've got a feel for those muscles, is to try to consciously tighten them to pull your larynx up and back, but without swallowing. + +Then, you're going to try talking like that. Your voice should come out with a higher pitch than normal. + +That's all there is to the technique, but there are some other important things you need to know if you want to get good results from voice training. + +Voice training is like learning to ride a bike. When you first start, you'll be able to go about 10 feet before you lose your balance, and you won't be able to steer because you need all your mental energy to just keep from falling over. Your voice will sound terrible, unnatural, and monotone because you need to devote all your focus to just raising the pitch so you won't have the natural flow of normal speech, and you'll only be able to do it for like 5 seconds at a time. The idea of speaking like this normally and for hours at a time will be unthinkable, and you'll wonder why your voice sounds nothing like passing trans people's. But this is normal. As you practice, you'll be able to do it for longer and longer, and it'll become more automatic that you don't even have to think about while you're doing it. As it becomes less conscious, it'll start to sound more natural, and lose the stunted, deliberate quality that it has at first. You'll also be able to more finely control it, and think about other things while you do it as it fades into the background. Like riding a bike. + +All you need to solve these problems is practice. Which brings me to the biggest obstacle of voice training. Voice training practice isn't difficult or anything, it's just talking. But what it is is slow. I've been voice training for two years now. It takes months before you start seeing any meaningful results, and even longer before it starts being passable. If you're listening to your own voice and judging yourself, or if you need to see significant improvement after just a few weeks to not get discouraged, then voice training will feel like an arduous, painful task, and even if you're able to force yourself to stick with it for a week or two, you won't be able to endure it for a whole year and hundreds of hours without burning out. The necessary first step to voice training is learning to see your shitty voice as the normal first stage, accept that it doesn't make you "masculine" or anything because that's how every MtF's voice starts out, and that it's going to stay that way for months and months and maybe even a year, and that's okay, because you will get better eventually. If you're constantly monitoring and evaluating yourself and your progress and thinking about how far you have left to go, the hundreds of hours of practice it takes to pass will feel like hell. But if you don't worry about where you are or how long it will take, then those hundreds of hours will fly by like nothing and one day you'll just have a better voice. I never noticed my voice improving, there wasn't any one point or even any short period where it suddenly got better, I just remember that it used to be terrible and know that right now it sounds pretty good. All you have to do is turn your brain off, and accept that you'll get there when you get there, instead of watching your progress like it's the microwave timer on a frozen burrito. + +So, now that we've gotten that out of the way, what's a good way to voice train? I'd advise using a voice chat program like discord. This is a good idea because you have to talk to someone, talking to yourself for 500 hours would be way too boring, and you'd probably feel self-conscious talking to people irl when your voice doesn't pass. Plus, the people you talk to online can be other trans people, so that you don't have to worry about being judged. Talk about tranny stuff, talk about politics, talk about video games, talk about what you had for lunch, it doesn't matter, just keep talking. Don't worry too much about listening to clips of your own voice of deliberately improving at specific things. You can do some of that stuff too but what's most important is to just dump raw hours into using your voice with an elevated pitch until you can do it for as long as you want without getting tired and you don't have to devote your whole brain to it. Once you've gotten to the point where you can do that on autopilot and you can direct your focus to monitoring and tweaking specific things instead of just keeping your voice in the proper pitch range, THEN you can start making finer adjustments. There's no point in studying how to do a wheelie when you're still learning to pedal. + +[badambulist](https://nitter.net/badambulist/status/1481741328123846662) diff --git a/trex/assets/default_100_percent/100-disabled.png b/trex/assets/default_100_percent/100-disabled.png deleted file mode 100644 index 526075e6d55359bb31f0ba3b1154524a04dea4cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 382 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 Bn3n(m diff --git a/trex/assets/default_200_percent/200-disabled.png b/trex/assets/default_200_percent/200-disabled.png deleted file mode 100644 index ea2b90ec9fadda7658a4424ada04e9dc4a3279e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 479 zcmV<50U-W~P)XE93}*ZoG=xD3Y6i8}ol?I@7(T*c2q}D2UW(2M(d@ktVMvHX z5TY?sln-P`h^lu&q!u!)dk7J2D?~S6X2nC(wq?B?58>+Esx=P{l_MEo8eh|?rVsQjldm$Pk=s56E9eMx}e9Zr}0M)Su4{S z3AJ}ZgxPx*zRl2002ovPDHLkV1hR&z=Qw* diff --git a/trex/assets/default_200_percent/200-error-offline.png b/trex/assets/default_200_percent/200-error-offline.png deleted file mode 100644 index 79cae8411bbc1a0cc7e867e945b082dca37009a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^6F``S8Ays|{O<-*5&=FTu0T3CIM~riKhsM25i$LfsD(<_aPAt2kH^Q*)zxhCOR53jehw0tU5{I28NJ}-*HRl_Ik zwll{oc_tMXMkU3CTYft*d=2o)@V9mAkXy`Sdw;RO+H}YDjlKVEIT){4&E-F;G$ov$ z@ecDN57i?Y2N_gF9ZU?rXvXXeb^K}U-m$=YA#b1Ov;Q2kq@;lkW$<+Mb6Mw<&;$U4 CSzM?9 diff --git a/trex/assets/default_200_percent/200-offline-sprite.png b/trex/assets/default_200_percent/200-offline-sprite.png deleted file mode 100644 index 19fcfe801fb670dc9ec6832ec47f17219a870691..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10107 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 UVNn3T5;2(=S{U5db35~Y0MW+Z7XSbN diff --git a/trex/index.css b/trex/index.css deleted file mode 100644 index 11e8b39..0000000 --- a/trex/index.css +++ /dev/null @@ -1,158 +0,0 @@ -/* Copyright 2013 The Chromium Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. */ - -html, body { - padding: 0; - margin: 0; - width: 100%; - height: 100%; - background-color:transparent; -} - -.icon { - -webkit-user-select: none; - user-select: none; - display: inline-block; -} - -.icon-offline { - content: -webkit-image-set( url(assets/default_100_percent/100-error-offline.png) 1x, url(assets/default_200_percent/200-error-offline.png) 2x); - position: relative; -} - -.hidden { - display: none; -} - -h1 { - font-weight: normal; -} - -a { - color: #bf9dde; -} - -#grid { - width: 100%; - height: 100%; - position: fixed; - left: 0px; - bottom: 0px; - z-index: -1; -} - -.stretch { - width:100%; - height:100%; -} -/* Offline page */ - -.offline .interstitial-wrapper { - color: #b5e853; - font-size: 1em; - line-height: 1.55; - margin: 0 auto; - max-width: 600px; - padding-top: 100px; - width: 100%; - text-shadow: #bf9dde 0 0 10px; -} - -.offline .runner-container { - height: 150px; - max-width: 600px; - overflow: hidden; - position: absolute; - top: 35px; - width: 44px; -} - -.offline .runner-canvas { - height: 150px; - max-width: 600px; - opacity: 1; - overflow: hidden; - position: absolute; - top: 0; - z-index: 2; -} - -.offline .controller { - background: rgba(247, 247, 247, .1); - height: 100vh; - left: 0; - position: absolute; - top: 0; - width: 100vw; - z-index: 1; -} - -#offline-resources { - display: none; -} - -@media (max-width: 420px) { - .suggested-left > #control-buttons, .suggested-right > #control-buttons { - float: none; - } - .snackbar { - left: 0; - bottom: 0; - width: 100%; - border-radius: 0; - } -} - -@media (max-height: 350px) { - h1 { - margin: 0 0 15px; - } - .icon-offline { - margin: 0 0 10px; - } - .interstitial-wrapper { - margin-top: 5%; - } - .nav-wrapper { - margin-top: 30px; - } -} - -@media (min-width: 600px) and (max-width: 736px) and (orientation: landscape) { - .offline .interstitial-wrapper { - margin-left: 0; - margin-right: 0; - } -} - -@media (min-width: 420px) and (max-width: 736px) and (min-height: 240px) and (max-height: 420px) and (orientation:landscape) { - .interstitial-wrapper { - margin-bottom: 100px; - } -} - -@media (min-height: 240px) and (orientation: landscape) { - .offline .interstitial-wrapper { - margin-bottom: 90px; - } - .icon-offline { - margin-bottom: 20px; - } -} - -@media (max-height: 320px) and (orientation: landscape) { - .icon-offline { - margin-bottom: 0; - } - .offline .runner-container { - top: 10px; - } -} - -@media (max-width: 240px) { - .interstitial-wrapper { - overflow: inherit; - padding: 0 8px; - } -} diff --git a/trex/index.html b/trex/index.html deleted file mode 100644 index 6750384..0000000 --- a/trex/index.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - trex - - - - - -
-
-
-
-

There is no Internet connection

-
-

Try:

-
    -
  • Checking the network cables, modem, and router
  • Reconnecting to Wi-Fi
  • -
-
-
DNS_PROBE_FINISHED_NO_INTERNET
- -
-
-
- - - - -
-
-
- -
- - - diff --git a/trex/index.js b/trex/index.js deleted file mode 100644 index 3599195..0000000 --- a/trex/index.js +++ /dev/null @@ -1,2739 +0,0 @@ -// Copyright (c) 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// extract from chromium source code by @liuwayong -(function () { - 'use strict'; - /** - * T-Rex runner. - * @param {string} outerContainerId Outer containing element id. - * @param {Object} opt_config - * @constructor - * @export - */ - function Runner(outerContainerId, opt_config) { - // Singleton - if (Runner.instance_) { - return Runner.instance_; - } - Runner.instance_ = this; - - this.outerContainerEl = document.querySelector(outerContainerId); - this.containerEl = null; - this.snackbarEl = null; - this.detailsButton = this.outerContainerEl.querySelector('#details-button'); - - this.config = opt_config || Runner.config; - - this.dimensions = Runner.defaultDimensions; - - this.canvas = null; - this.canvasCtx = null; - - this.tRex = null; - - this.distanceMeter = null; - this.distanceRan = 0; - - this.highestScore = 0; - - this.time = 0; - this.runningTime = 0; - this.msPerFrame = 1000 / FPS; - this.currentSpeed = this.config.SPEED; - - this.obstacles = []; - - this.activated = false; // Whether the easter egg has been activated. - this.playing = false; // Whether the game is currently in play state. - this.crashed = false; - this.paused = false; - this.inverted = false; - this.invertTimer = 0; - this.resizeTimerId_ = null; - - this.playCount = 0; - - // Sound FX. - this.audioBuffer = null; - this.soundFx = {}; - - // Global web audio context for playing sounds. - this.audioContext = null; - - // Images. - this.images = {}; - this.imagesLoaded = 0; - - if (this.isDisabled()) { - this.setupDisabledRunner(); - } else { - this.loadImages(); - } - } - window['Runner'] = Runner; - - - /** - * Default game width. - * @const - */ - var DEFAULT_WIDTH = 600; - - /** - * Frames per second. - * @const - */ - var FPS = 60; - - /** @const */ - var IS_HIDPI = window.devicePixelRatio > 1; - - /** @const */ - var IS_IOS = /iPad|iPhone|iPod/.test(window.navigator.platform); - - /** @const */ - var IS_MOBILE = /Android/.test(window.navigator.userAgent) || IS_IOS; - - /** @const */ - var IS_TOUCH_ENABLED = 'ontouchstart' in window; - - /** - * Default game configuration. - * @enum {number} - */ - Runner.config = { - ACCELERATION: 0.001, - BG_CLOUD_SPEED: 0.2, - BOTTOM_PAD: 10, - CLEAR_TIME: 3000, - CLOUD_FREQUENCY: 0.5, - GAMEOVER_CLEAR_TIME: 750, - GAP_COEFFICIENT: 0.6, - GRAVITY: 0.6, - INITIAL_JUMP_VELOCITY: 12, - INVERT_FADE_DURATION: 12000, - INVERT_DISTANCE: 700, - MAX_BLINK_COUNT: 3, - MAX_CLOUDS: 6, - MAX_OBSTACLE_LENGTH: 3, - MAX_OBSTACLE_DUPLICATION: 2, - MAX_SPEED: 13, - MIN_JUMP_HEIGHT: 35, - MOBILE_SPEED_COEFFICIENT: 1.2, - RESOURCE_TEMPLATE_ID: 'audio-resources', - SPEED: 6, - SPEED_DROP_COEFFICIENT: 3 - }; - - - /** - * Default dimensions. - * @enum {string} - */ - Runner.defaultDimensions = { - WIDTH: DEFAULT_WIDTH, - HEIGHT: 150 - }; - - - /** - * CSS class names. - * @enum {string} - */ - Runner.classes = { - CANVAS: 'runner-canvas', - CONTAINER: 'runner-container', - CRASHED: 'crashed', - ICON: 'icon-offline', - INVERTED: 'inverted', - SNACKBAR: 'snackbar', - SNACKBAR_SHOW: 'snackbar-show', - TOUCH_CONTROLLER: 'controller' - }; - - - /** - * Sprite definition layout of the spritesheet. - * @enum {Object} - */ - Runner.spriteDefinition = { - LDPI: { - CACTUS_LARGE: { x: 332, y: 2 }, - CACTUS_SMALL: { x: 228, y: 2 }, - CLOUD: { x: 86, y: 2 }, - HORIZON: { x: 2, y: 54 }, - MOON: { x: 484, y: 2 }, - PTERODACTYL: { x: 134, y: 2 }, - RESTART: { x: 2, y: 2 }, - TEXT_SPRITE: { x: 655, y: 2 }, - TREX: { x: 848, y: 2 }, - STAR: { x: 645, y: 2 } - }, - HDPI: { - CACTUS_LARGE: { x: 652, y: 2 }, - CACTUS_SMALL: { x: 446, y: 2 }, - CLOUD: { x: 166, y: 2 }, - HORIZON: { x: 2, y: 104 }, - MOON: { x: 954, y: 2 }, - PTERODACTYL: { x: 260, y: 2 }, - RESTART: { x: 2, y: 2 }, - TEXT_SPRITE: { x: 1294, y: 2 }, - TREX: { x: 1678, y: 2 }, - STAR: { x: 1276, y: 2 } - } - }; - - - /** - * Sound FX. Reference to the ID of the audio tag on interstitial page. - * @enum {string} - */ - Runner.sounds = { - BUTTON_PRESS: 'offline-sound-press', - HIT: 'offline-sound-hit', - SCORE: 'offline-sound-reached' - }; - - - /** - * Key code mapping. - * @enum {Object} - */ - Runner.keycodes = { - JUMP: { '38': 1, '32': 1 }, // Up, spacebar - DUCK: { '40': 1 }, // Down - RESTART: { '13': 1 } // Enter - }; - - - /** - * Runner event names. - * @enum {string} - */ - Runner.events = { - ANIM_END: 'webkitAnimationEnd', - CLICK: 'click', - KEYDOWN: 'keydown', - KEYUP: 'keyup', - MOUSEDOWN: 'mousedown', - MOUSEUP: 'mouseup', - RESIZE: 'resize', - TOUCHEND: 'touchend', - TOUCHSTART: 'touchstart', - VISIBILITY: 'visibilitychange', - BLUR: 'blur', - FOCUS: 'focus', - LOAD: 'load' - }; - - - Runner.prototype = { - /** - * Whether the easter egg has been disabled. CrOS enterprise enrolled devices. - * @return {boolean} - */ - isDisabled: function () { - // return loadTimeData && loadTimeData.valueExists('disabledEasterEgg'); - return false; - }, - - /** - * For disabled instances, set up a snackbar with the disabled message. - */ - setupDisabledRunner: function () { - this.containerEl = document.createElement('div'); - this.containerEl.className = Runner.classes.SNACKBAR; - this.containerEl.textContent = loadTimeData.getValue('disabledEasterEgg'); - this.outerContainerEl.appendChild(this.containerEl); - - // Show notification when the activation key is pressed. - document.addEventListener(Runner.events.KEYDOWN, function (e) { - if (Runner.keycodes.JUMP[e.keyCode]) { - this.containerEl.classList.add(Runner.classes.SNACKBAR_SHOW); - document.querySelector('.icon').classList.add('icon-disabled'); - } - }.bind(this)); - }, - - /** - * Setting individual settings for debugging. - * @param {string} setting - * @param {*} value - */ - updateConfigSetting: function (setting, value) { - if (setting in this.config && value != undefined) { - this.config[setting] = value; - - switch (setting) { - case 'GRAVITY': - case 'MIN_JUMP_HEIGHT': - case 'SPEED_DROP_COEFFICIENT': - this.tRex.config[setting] = value; - break; - case 'INITIAL_JUMP_VELOCITY': - this.tRex.setJumpVelocity(value); - break; - case 'SPEED': - this.setSpeed(value); - break; - } - } - }, - - /** - * Cache the appropriate image sprite from the page and get the sprite sheet - * definition. - */ - loadImages: function () { - Sun.imageSprite = document.getElementById('sun'); - - if (IS_HIDPI) { - Runner.imageSprite = document.getElementById('offline-resources-2x'); - this.spriteDef = Runner.spriteDefinition.HDPI; - } else { - Runner.imageSprite = document.getElementById('offline-resources-1x'); - this.spriteDef = Runner.spriteDefinition.LDPI; - } - - if (Runner.imageSprite.complete) { - this.init(); - } else { - // If the images are not yet loaded, add a listener. - Runner.imageSprite.addEventListener(Runner.events.LOAD, - this.init.bind(this)); - } - }, - - /** - * Load and decode base 64 encoded sounds. - */ - loadSounds: function () { - if (!IS_IOS) { - this.audioContext = new AudioContext(); - - var resourceTemplate = - document.getElementById(this.config.RESOURCE_TEMPLATE_ID).content; - - for (var sound in Runner.sounds) { - var soundSrc = - resourceTemplate.getElementById(Runner.sounds[sound]).src; - soundSrc = soundSrc.substr(soundSrc.indexOf(',') + 1); - var buffer = decodeBase64ToArrayBuffer(soundSrc); - - // Async, so no guarantee of order in array. - this.audioContext.decodeAudioData(buffer, function (index, audioData) { - this.soundFx[index] = audioData; - }.bind(this, sound)); - } - } - }, - - /** - * Sets the game speed. Adjust the speed accordingly if on a smaller screen. - * @param {number} opt_speed - */ - setSpeed: function (opt_speed) { - var speed = opt_speed || this.currentSpeed; - - // Reduce the speed on smaller mobile screens. - if (this.dimensions.WIDTH < DEFAULT_WIDTH) { - var mobileSpeed = speed * this.dimensions.WIDTH / DEFAULT_WIDTH * - this.config.MOBILE_SPEED_COEFFICIENT; - this.currentSpeed = mobileSpeed > speed ? speed : mobileSpeed; - } else if (opt_speed) { - this.currentSpeed = opt_speed; - } - }, - - /** - * Game initialiser. - */ - init: function () { - // Hide the static icon. - document.querySelector('.' + Runner.classes.ICON).style.visibility = - 'hidden'; - - this.adjustDimensions(); - this.setSpeed(); - - this.containerEl = document.createElement('div'); - this.containerEl.className = Runner.classes.CONTAINER; - - // Player canvas container. - this.canvas = createCanvas(this.containerEl, this.dimensions.WIDTH, - this.dimensions.HEIGHT, Runner.classes.PLAYER); - - this.canvasCtx = this.canvas.getContext('2d'); - this.canvasCtx.fillStyle = '#f7f7f7'; - this.canvasCtx.fill(); - Runner.updateCanvasScaling(this.canvas); - - // Horizon contains clouds, obstacles and the ground. - this.horizon = new Horizon(this.canvas, this.spriteDef, this.dimensions, - this.config.GAP_COEFFICIENT); - - // Distance meter - this.distanceMeter = new DistanceMeter(this.canvas, - this.spriteDef.TEXT_SPRITE, this.dimensions.WIDTH); - - // Draw t-rex - this.tRex = new Trex(this.canvas, this.spriteDef.TREX); - - this.outerContainerEl.appendChild(this.containerEl); - - if (IS_MOBILE) { - this.createTouchController(); - } - - this.startListening(); - this.update(); - - window.addEventListener(Runner.events.RESIZE, - this.debounceResize.bind(this)); - }, - - /** - * Create the touch controller. A div that covers whole screen. - */ - createTouchController: function () { - this.touchController = document.createElement('div'); - this.touchController.className = Runner.classes.TOUCH_CONTROLLER; - this.outerContainerEl.appendChild(this.touchController); - }, - - /** - * Debounce the resize event. - */ - debounceResize: function () { - if (!this.resizeTimerId_) { - this.resizeTimerId_ = - setInterval(this.adjustDimensions.bind(this), 250); - } - }, - - /** - * Adjust game space dimensions on resize. - */ - adjustDimensions: function () { - clearInterval(this.resizeTimerId_); - this.resizeTimerId_ = null; - - var boxStyles = window.getComputedStyle(this.outerContainerEl); - var padding = Number(boxStyles.paddingLeft.substr(0, - boxStyles.paddingLeft.length - 2)); - - this.dimensions.WIDTH = this.outerContainerEl.offsetWidth - padding * 2; - - // Redraw the elements back onto the canvas. - if (this.canvas) { - this.canvas.width = this.dimensions.WIDTH; - this.canvas.height = this.dimensions.HEIGHT; - - Runner.updateCanvasScaling(this.canvas); - - this.distanceMeter.calcXPos(this.dimensions.WIDTH); - this.clearCanvas(); - this.horizon.update(0, 0, true); - this.tRex.update(0); - - // Outer container and distance meter. - if (this.playing || this.crashed || this.paused) { - this.containerEl.style.width = this.dimensions.WIDTH + 'px'; - this.containerEl.style.height = this.dimensions.HEIGHT + 'px'; - this.distanceMeter.update(0, Math.ceil(this.distanceRan)); - this.stop(); - } else { - this.tRex.draw(0, 0); - } - - // Game over panel. - if (this.crashed && this.gameOverPanel) { - this.gameOverPanel.updateDimensions(this.dimensions.WIDTH); - this.gameOverPanel.draw(); - } - } - }, - - /** - * Play the game intro. - * Canvas container width expands out to the full width. - */ - playIntro: function () { - if (!this.activated && !this.crashed) { - this.playingIntro = true; - this.tRex.playingIntro = true; - - // CSS animation definition. - var keyframes = '@-webkit-keyframes intro { ' + - 'from { width:' + Trex.config.WIDTH + 'px }' + - 'to { width: ' + this.dimensions.WIDTH + 'px }' + - '}'; - document.styleSheets[0].insertRule(keyframes, 0); - - this.containerEl.addEventListener(Runner.events.ANIM_END, - this.startGame.bind(this)); - - this.containerEl.style.webkitAnimation = 'intro .4s ease-out 1 both'; - this.containerEl.style.width = this.dimensions.WIDTH + 'px'; - - // if (this.touchController) { - // this.outerContainerEl.appendChild(this.touchController); - // } - this.playing = true; - this.activated = true; - } else if (this.crashed) { - this.restart(); - } - }, - - - /** - * Update the game status to started. - */ - startGame: function () { - this.runningTime = 0; - this.playingIntro = false; - this.tRex.playingIntro = false; - this.containerEl.style.webkitAnimation = ''; - this.playCount++; - - // Handle tabbing off the page. Pause the current game. - document.addEventListener(Runner.events.VISIBILITY, - this.onVisibilityChange.bind(this)); - - window.addEventListener(Runner.events.BLUR, - this.onVisibilityChange.bind(this)); - - window.addEventListener(Runner.events.FOCUS, - this.onVisibilityChange.bind(this)); - }, - - clearCanvas: function () { - this.canvasCtx.clearRect(0, 0, this.dimensions.WIDTH, - this.dimensions.HEIGHT); - }, - - /** - * Update the game frame and schedules the next one. - */ - update: function () { - this.updatePending = false; - - var now = getTimeStamp(); - var deltaTime = now - (this.time || now); - this.time = now; - - if (this.playing) { - this.clearCanvas(); - - if (this.tRex.jumping) { - this.tRex.updateJump(deltaTime); - } - - this.runningTime += deltaTime; - var hasObstacles = this.runningTime > this.config.CLEAR_TIME; - - // First jump triggers the intro. - if (this.tRex.jumpCount == 1 && !this.playingIntro) { - this.playIntro(); - } - - // The horizon doesn't move until the intro is over. - if (this.playingIntro) { - this.horizon.update(0, this.currentSpeed, hasObstacles); - } else { - deltaTime = !this.activated ? 0 : deltaTime; - this.horizon.update(deltaTime, this.currentSpeed, hasObstacles, - this.inverted); - } - - // Check for collisions. - var collision = hasObstacles && - checkForCollision(this.horizon.obstacles[0], this.tRex); - - if (!collision) { - this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame; - - if (this.currentSpeed < this.config.MAX_SPEED) { - this.currentSpeed += this.config.ACCELERATION; - } - } else { - this.gameOver(); - } - - var playAchievementSound = this.distanceMeter.update(deltaTime, - Math.ceil(this.distanceRan)); - - if (playAchievementSound) { - this.playSound(this.soundFx.SCORE); - } - - // Night mode. - if (this.invertTimer > this.config.INVERT_FADE_DURATION) { - this.invertTimer = 0; - this.invertTrigger = false; - this.invert(); - } else if (this.invertTimer) { - this.invertTimer += deltaTime; - } else { - var actualDistance = - this.distanceMeter.getActualDistance(Math.ceil(this.distanceRan)); - - if (actualDistance > 0) { - this.invertTrigger = !(actualDistance % - this.config.INVERT_DISTANCE); - - if (this.invertTrigger && this.invertTimer === 0) { - this.invertTimer += deltaTime; - this.invert(); - } - } - } - } - - if (this.playing || (!this.activated && - this.tRex.blinkCount < Runner.config.MAX_BLINK_COUNT)) { - this.tRex.update(deltaTime); - this.scheduleNextUpdate(); - } - }, - - /** - * Event handler. - */ - handleEvent: function (e) { - return (function (evtType, events) { - switch (evtType) { - case events.KEYDOWN: - case events.TOUCHSTART: - case events.MOUSEDOWN: - this.onKeyDown(e); - break; - case events.KEYUP: - case events.TOUCHEND: - case events.MOUSEUP: - this.onKeyUp(e); - break; - } - }.bind(this))(e.type, Runner.events); - }, - - /** - * Bind relevant key / mouse / touch listeners. - */ - startListening: function () { - // Keys. - document.addEventListener(Runner.events.KEYDOWN, this); - document.addEventListener(Runner.events.KEYUP, this); - - if (IS_MOBILE) { - // Mobile only touch devices. - this.touchController.addEventListener(Runner.events.TOUCHSTART, this); - this.touchController.addEventListener(Runner.events.TOUCHEND, this); - this.containerEl.addEventListener(Runner.events.TOUCHSTART, this); - } else { - // Mouse. - document.addEventListener(Runner.events.MOUSEDOWN, this); - document.addEventListener(Runner.events.MOUSEUP, this); - } - }, - - /** - * Remove all listeners. - */ - stopListening: function () { - document.removeEventListener(Runner.events.KEYDOWN, this); - document.removeEventListener(Runner.events.KEYUP, this); - - if (IS_MOBILE) { - this.touchController.removeEventListener(Runner.events.TOUCHSTART, this); - this.touchController.removeEventListener(Runner.events.TOUCHEND, this); - this.containerEl.removeEventListener(Runner.events.TOUCHSTART, this); - } else { - document.removeEventListener(Runner.events.MOUSEDOWN, this); - document.removeEventListener(Runner.events.MOUSEUP, this); - } - }, - - /** - * Process keydown. - * @param {Event} e - */ - onKeyDown: function (e) { - // Prevent native page scrolling whilst tapping on mobile. - if (IS_MOBILE && this.playing) { - e.preventDefault(); - } - - if (e.target != this.detailsButton) { - if (!this.crashed && (Runner.keycodes.JUMP[e.keyCode] || - e.type == Runner.events.TOUCHSTART)) { - if (!this.playing) { - this.loadSounds(); - this.playing = true; - this.update(); - if (window.errorPageController) { - errorPageController.trackEasterEgg(); - } - } - // Play sound effect and jump on starting the game for the first time. - if (!this.tRex.jumping && !this.tRex.ducking) { - this.playSound(this.soundFx.BUTTON_PRESS); - this.tRex.startJump(this.currentSpeed); - } - } - - if (this.crashed && e.type == Runner.events.TOUCHSTART && - e.currentTarget == this.containerEl) { - this.restart(); - } - } - - if (this.playing && !this.crashed && Runner.keycodes.DUCK[e.keyCode]) { - e.preventDefault(); - if (this.tRex.jumping) { - // Speed drop, activated only when jump key is not pressed. - this.tRex.setSpeedDrop(); - } else if (!this.tRex.jumping && !this.tRex.ducking) { - // Duck. - this.tRex.setDuck(true); - } - } - }, - - - /** - * Process key up. - * @param {Event} e - */ - onKeyUp: function (e) { - var keyCode = String(e.keyCode); - var isjumpKey = Runner.keycodes.JUMP[keyCode] || - e.type == Runner.events.TOUCHEND || - e.type == Runner.events.MOUSEDOWN; - - if (this.isRunning() && isjumpKey) { - this.tRex.endJump(); - } else if (Runner.keycodes.DUCK[keyCode]) { - this.tRex.speedDrop = false; - this.tRex.setDuck(false); - } else if (this.crashed) { - // Check that enough time has elapsed before allowing jump key to restart. - var deltaTime = getTimeStamp() - this.time; - - if (Runner.keycodes.RESTART[keyCode] || this.isLeftClickOnCanvas(e) || - (deltaTime >= this.config.GAMEOVER_CLEAR_TIME && - Runner.keycodes.JUMP[keyCode])) { - this.restart(); - } - } else if (this.paused && isjumpKey) { - // Reset the jump state - this.tRex.reset(); - this.play(); - } - }, - - /** - * Returns whether the event was a left click on canvas. - * On Windows right click is registered as a click. - * @param {Event} e - * @return {boolean} - */ - isLeftClickOnCanvas: function (e) { - return e.button != null && e.button < 2 && - e.type == Runner.events.MOUSEUP && e.target == this.canvas; - }, - - /** - * RequestAnimationFrame wrapper. - */ - scheduleNextUpdate: function () { - if (!this.updatePending) { - this.updatePending = true; - this.raqId = requestAnimationFrame(this.update.bind(this)); - } - }, - - /** - * Whether the game is running. - * @return {boolean} - */ - isRunning: function () { - return !!this.raqId; - }, - - /** - * Game over state. - */ - gameOver: function () { - this.playSound(this.soundFx.HIT); - vibrate(200); - - this.stop(); - this.crashed = true; - this.distanceMeter.acheivement = false; - - this.tRex.update(100, Trex.status.CRASHED); - - // Game over panel. - if (!this.gameOverPanel) { - this.gameOverPanel = new GameOverPanel(this.canvas, - this.spriteDef.TEXT_SPRITE, this.spriteDef.RESTART, - this.dimensions); - } else { - this.gameOverPanel.draw(); - } - - // Update the high score. - if (this.distanceRan > this.highestScore) { - this.highestScore = Math.ceil(this.distanceRan); - this.distanceMeter.setHighScore(this.highestScore); - } - - // Reset the time clock. - this.time = getTimeStamp(); - }, - - stop: function () { - this.playing = false; - this.paused = true; - cancelAnimationFrame(this.raqId); - this.raqId = 0; - }, - - play: function () { - if (!this.crashed) { - this.playing = true; - this.paused = false; - this.tRex.update(0, Trex.status.RUNNING); - this.time = getTimeStamp(); - this.update(); - } - }, - - restart: function () { - if (!this.raqId) { - this.playCount++; - this.runningTime = 0; - this.playing = true; - this.crashed = false; - this.distanceRan = 0; - this.setSpeed(this.config.SPEED); - this.time = getTimeStamp(); - this.containerEl.classList.remove(Runner.classes.CRASHED); - this.clearCanvas(); - this.distanceMeter.reset(this.highestScore); - this.horizon.reset(); - this.tRex.reset(); - this.playSound(this.soundFx.BUTTON_PRESS); - this.invert(true); - this.update(); - } - }, - - /** - * Pause the game if the tab is not in focus. - */ - onVisibilityChange: function (e) { - if (document.hidden || document.webkitHidden || e.type == 'blur' || - document.visibilityState != 'visible') { - this.stop(); - } else if (!this.crashed) { - this.tRex.reset(); - this.play(); - } - }, - - /** - * Play a sound. - * @param {SoundBuffer} soundBuffer - */ - playSound: function (soundBuffer) { - if (soundBuffer) { - var sourceNode = this.audioContext.createBufferSource(); - sourceNode.buffer = soundBuffer; - sourceNode.connect(this.audioContext.destination); - sourceNode.start(0); - } - }, - - /** - * Inverts the current page / canvas colors. - * @param {boolean} Whether to reset colors. - */ - invert: function (reset) { - if (reset) { - document.body.classList.toggle(Runner.classes.INVERTED, false); - this.invertTimer = 0; - this.inverted = false; - } else { - this.inverted = document.body.classList.toggle(Runner.classes.INVERTED, - this.invertTrigger); - } - } - }; - - - /** - * Updates the canvas size taking into - * account the backing store pixel ratio and - * the device pixel ratio. - * - * See article by Paul Lewis: - * http://www.html5rocks.com/en/tutorials/canvas/hidpi/ - * - * @param {HTMLCanvasElement} canvas - * @param {number} opt_width - * @param {number} opt_height - * @return {boolean} Whether the canvas was scaled. - */ - Runner.updateCanvasScaling = function (canvas, opt_width, opt_height) { - var context = canvas.getContext('2d'); - - // Query the various pixel ratios - var devicePixelRatio = Math.floor(window.devicePixelRatio) || 1; - var backingStoreRatio = Math.floor(context.webkitBackingStorePixelRatio) || 1; - var ratio = devicePixelRatio / backingStoreRatio; - - // Upscale the canvas if the two ratios don't match - if (devicePixelRatio !== backingStoreRatio) { - var oldWidth = opt_width || canvas.width; - var oldHeight = opt_height || canvas.height; - - canvas.width = oldWidth * ratio; - canvas.height = oldHeight * ratio; - - canvas.style.width = oldWidth + 'px'; - canvas.style.height = oldHeight + 'px'; - - // Scale the context to counter the fact that we've manually scaled - // our canvas element. - context.scale(ratio, ratio); - return true; - } else if (devicePixelRatio == 1) { - // Reset the canvas width / height. Fixes scaling bug when the page is - // zoomed and the devicePixelRatio changes accordingly. - canvas.style.width = canvas.width + 'px'; - canvas.style.height = canvas.height + 'px'; - } - return false; - }; - - - /** - * Get random number. - * @param {number} min - * @param {number} max - * @param {number} - */ - function getRandomNum(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; - } - - - /** - * Vibrate on mobile devices. - * @param {number} duration Duration of the vibration in milliseconds. - */ - function vibrate(duration) { - if (IS_MOBILE && window.navigator.vibrate) { - window.navigator.vibrate(duration); - } - } - - - /** - * Create canvas element. - * @param {HTMLElement} container Element to append canvas to. - * @param {number} width - * @param {number} height - * @param {string} opt_classname - * @return {HTMLCanvasElement} - */ - function createCanvas(container, width, height, opt_classname) { - var canvas = document.createElement('canvas'); - canvas.className = opt_classname ? Runner.classes.CANVAS + ' ' + - opt_classname : Runner.classes.CANVAS; - canvas.width = width; - canvas.height = height; - container.appendChild(canvas); - - return canvas; - } - - - /** - * Decodes the base 64 audio to ArrayBuffer used by Web Audio. - * @param {string} base64String - */ - function decodeBase64ToArrayBuffer(base64String) { - var len = (base64String.length / 4) * 3; - var str = atob(base64String); - var arrayBuffer = new ArrayBuffer(len); - var bytes = new Uint8Array(arrayBuffer); - - for (var i = 0; i < len; i++) { - bytes[i] = str.charCodeAt(i); - } - return bytes.buffer; - } - - - /** - * Return the current timestamp. - * @return {number} - */ - function getTimeStamp() { - return IS_IOS ? new Date().getTime() : performance.now(); - } - - - //****************************************************************************** - - - /** - * Game over panel. - * @param {!HTMLCanvasElement} canvas - * @param {Object} textImgPos - * @param {Object} restartImgPos - * @param {!Object} dimensions Canvas dimensions. - * @constructor - */ - function GameOverPanel(canvas, textImgPos, restartImgPos, dimensions) { - this.canvas = canvas; - this.canvasCtx = canvas.getContext('2d'); - this.canvasDimensions = dimensions; - this.textImgPos = textImgPos; - this.restartImgPos = restartImgPos; - this.draw(); - }; - - - /** - * Dimensions used in the panel. - * @enum {number} - */ - GameOverPanel.dimensions = { - TEXT_X: 0, - TEXT_Y: 13, - TEXT_WIDTH: 191, - TEXT_HEIGHT: 11, - RESTART_WIDTH: 36, - RESTART_HEIGHT: 32 - }; - - - GameOverPanel.prototype = { - /** - * Update the panel dimensions. - * @param {number} width New canvas width. - * @param {number} opt_height Optional new canvas height. - */ - updateDimensions: function (width, opt_height) { - this.canvasDimensions.WIDTH = width; - if (opt_height) { - this.canvasDimensions.HEIGHT = opt_height; - } - }, - - /** - * Draw the panel. - */ - draw: function () { - var dimensions = GameOverPanel.dimensions; - - var centerX = this.canvasDimensions.WIDTH / 2; - - // Game over text. - var textSourceX = dimensions.TEXT_X; - var textSourceY = dimensions.TEXT_Y; - var textSourceWidth = dimensions.TEXT_WIDTH; - var textSourceHeight = dimensions.TEXT_HEIGHT; - - var textTargetX = Math.round(centerX - (dimensions.TEXT_WIDTH / 2)); - var textTargetY = Math.round((this.canvasDimensions.HEIGHT - 25) / 3); - var textTargetWidth = dimensions.TEXT_WIDTH; - var textTargetHeight = dimensions.TEXT_HEIGHT; - - var restartSourceWidth = dimensions.RESTART_WIDTH; - var restartSourceHeight = dimensions.RESTART_HEIGHT; - var restartTargetX = centerX - (dimensions.RESTART_WIDTH / 2); - var restartTargetY = this.canvasDimensions.HEIGHT / 2; - - if (IS_HIDPI) { - textSourceY *= 2; - textSourceX *= 2; - textSourceWidth *= 2; - textSourceHeight *= 2; - restartSourceWidth *= 2; - restartSourceHeight *= 2; - } - - textSourceX += this.textImgPos.x; - textSourceY += this.textImgPos.y; - - // Game over text from sprite. - this.canvasCtx.drawImage(Runner.imageSprite, - textSourceX, textSourceY, textSourceWidth, textSourceHeight, - textTargetX, textTargetY, textTargetWidth, textTargetHeight); - - // Restart button. - this.canvasCtx.drawImage(Runner.imageSprite, - this.restartImgPos.x, this.restartImgPos.y, - restartSourceWidth, restartSourceHeight, - restartTargetX, restartTargetY, dimensions.RESTART_WIDTH, - dimensions.RESTART_HEIGHT); - } - }; - - - //****************************************************************************** - - /** - * Check for a collision. - * @param {!Obstacle} obstacle - * @param {!Trex} tRex T-rex object. - * @param {HTMLCanvasContext} opt_canvasCtx Optional canvas context for drawing - * collision boxes. - * @return {Array} - */ - function checkForCollision(obstacle, tRex, opt_canvasCtx) { - var obstacleBoxXPos = Runner.defaultDimensions.WIDTH + obstacle.xPos; - - // Adjustments are made to the bounding box as there is a 1 pixel white - // border around the t-rex and obstacles. - var tRexBox = new CollisionBox( - tRex.xPos + 1, - tRex.yPos + 1, - tRex.config.WIDTH - 2, - tRex.config.HEIGHT - 2); - - var obstacleBox = new CollisionBox( - obstacle.xPos + 1, - obstacle.yPos + 1, - obstacle.typeConfig.width * obstacle.size - 2, - obstacle.typeConfig.height - 2); - - // Debug outer box - if (opt_canvasCtx) { - drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox); - } - - // Simple outer bounds check. - if (boxCompare(tRexBox, obstacleBox)) { - var collisionBoxes = obstacle.collisionBoxes; - var tRexCollisionBoxes = tRex.ducking ? - Trex.collisionBoxes.DUCKING : Trex.collisionBoxes.RUNNING; - - // Detailed axis aligned box check. - for (var t = 0; t < tRexCollisionBoxes.length; t++) { - for (var i = 0; i < collisionBoxes.length; i++) { - // Adjust the box to actual positions. - var adjTrexBox = - createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox); - var adjObstacleBox = - createAdjustedCollisionBox(collisionBoxes[i], obstacleBox); - var crashed = boxCompare(adjTrexBox, adjObstacleBox); - - // Draw boxes for debug. - if (opt_canvasCtx) { - drawCollisionBoxes(opt_canvasCtx, adjTrexBox, adjObstacleBox); - } - - if (crashed) { - return [adjTrexBox, adjObstacleBox]; - } - } - } - } - return false; - }; - - - /** - * Adjust the collision box. - * @param {!CollisionBox} box The original box. - * @param {!CollisionBox} adjustment Adjustment box. - * @return {CollisionBox} The adjusted collision box object. - */ - function createAdjustedCollisionBox(box, adjustment) { - return new CollisionBox( - box.x + adjustment.x, - box.y + adjustment.y, - box.width, - box.height); - }; - - - /** - * Draw the collision boxes for debug. - */ - function drawCollisionBoxes(canvasCtx, tRexBox, obstacleBox) { - canvasCtx.save(); - canvasCtx.strokeStyle = '#f00'; - canvasCtx.strokeRect(tRexBox.x, tRexBox.y, tRexBox.width, tRexBox.height); - - canvasCtx.strokeStyle = '#0f0'; - canvasCtx.strokeRect(obstacleBox.x, obstacleBox.y, - obstacleBox.width, obstacleBox.height); - canvasCtx.restore(); - }; - - - /** - * Compare two collision boxes for a collision. - * @param {CollisionBox} tRexBox - * @param {CollisionBox} obstacleBox - * @return {boolean} Whether the boxes intersected. - */ - function boxCompare(tRexBox, obstacleBox) { - var crashed = false; - var tRexBoxX = tRexBox.x; - var tRexBoxY = tRexBox.y; - - var obstacleBoxX = obstacleBox.x; - var obstacleBoxY = obstacleBox.y; - - // Axis-Aligned Bounding Box method. - if (tRexBox.x < obstacleBoxX + obstacleBox.width && - tRexBox.x + tRexBox.width > obstacleBoxX && - tRexBox.y < obstacleBox.y + obstacleBox.height && - tRexBox.height + tRexBox.y > obstacleBox.y) { - crashed = true; - } - - return crashed; - }; - - - //****************************************************************************** - - /** - * Collision box object. - * @param {number} x X position. - * @param {number} y Y Position. - * @param {number} w Width. - * @param {number} h Height. - */ - function CollisionBox(x, y, w, h) { - this.x = x; - this.y = y; - this.width = w; - this.height = h; - }; - - - //****************************************************************************** - - /** - * Obstacle. - * @param {HTMLCanvasCtx} canvasCtx - * @param {Obstacle.type} type - * @param {Object} spritePos Obstacle position in sprite. - * @param {Object} dimensions - * @param {number} gapCoefficient Mutipler in determining the gap. - * @param {number} speed - * @param {number} opt_xOffset - */ - function Obstacle(canvasCtx, type, spriteImgPos, dimensions, - gapCoefficient, speed, opt_xOffset) { - - this.canvasCtx = canvasCtx; - this.spritePos = spriteImgPos; - this.typeConfig = type; - this.gapCoefficient = gapCoefficient; - this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH); - this.dimensions = dimensions; - this.remove = false; - this.xPos = dimensions.WIDTH + (opt_xOffset || 0); - this.yPos = 0; - this.width = 0; - this.collisionBoxes = []; - this.gap = 0; - this.speedOffset = 0; - - // For animated obstacles. - this.currentFrame = 0; - this.timer = 0; - - this.init(speed); - }; - - /** - * Coefficient for calculating the maximum gap. - * @const - */ - Obstacle.MAX_GAP_COEFFICIENT = 1.5; - - /** - * Maximum obstacle grouping count. - * @const - */ - Obstacle.MAX_OBSTACLE_LENGTH = 3, - - - Obstacle.prototype = { - /** - * Initialise the DOM for the obstacle. - * @param {number} speed - */ - init: function (speed) { - this.cloneCollisionBoxes(); - - // Only allow sizing if we're at the right speed. - if (this.size > 1 && this.typeConfig.multipleSpeed > speed) { - this.size = 1; - } - - this.width = this.typeConfig.width * this.size; - - // Check if obstacle can be positioned at various heights. - if (Array.isArray(this.typeConfig.yPos)) { - var yPosConfig = IS_MOBILE ? this.typeConfig.yPosMobile : - this.typeConfig.yPos; - this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)]; - } else { - this.yPos = this.typeConfig.yPos; - } - - this.draw(); - - // Make collision box adjustments, - // Central box is adjusted to the size as one box. - // ____ ______ ________ - // _| |-| _| |-| _| |-| - // | |<->| | | |<--->| | | |<----->| | - // | | 1 | | | | 2 | | | | 3 | | - // |_|___|_| |_|_____|_| |_|_______|_| - // - if (this.size > 1) { - this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width - - this.collisionBoxes[2].width; - this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width; - } - - // For obstacles that go at a different speed from the horizon. - if (this.typeConfig.speedOffset) { - this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset : - -this.typeConfig.speedOffset; - } - - this.gap = this.getGap(this.gapCoefficient, speed); - }, - - /** - * Draw and crop based on size. - */ - draw: function () { - var sourceWidth = this.typeConfig.width; - var sourceHeight = this.typeConfig.height; - - if (IS_HIDPI) { - sourceWidth = sourceWidth * 2; - sourceHeight = sourceHeight * 2; - } - - // X position in sprite. - var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) + - this.spritePos.x; - - // Animation frames. - if (this.currentFrame > 0) { - sourceX += sourceWidth * this.currentFrame; - } - - this.canvasCtx.drawImage(Runner.imageSprite, - sourceX, this.spritePos.y, - sourceWidth * this.size, sourceHeight, - this.xPos, this.yPos, - this.typeConfig.width * this.size, this.typeConfig.height); - }, - - /** - * Obstacle frame update. - * @param {number} deltaTime - * @param {number} speed - */ - update: function (deltaTime, speed) { - if (!this.remove) { - if (this.typeConfig.speedOffset) { - speed += this.speedOffset; - } - this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime); - - // Update frame - if (this.typeConfig.numFrames) { - this.timer += deltaTime; - if (this.timer >= this.typeConfig.frameRate) { - this.currentFrame = - this.currentFrame == this.typeConfig.numFrames - 1 ? - 0 : this.currentFrame + 1; - this.timer = 0; - } - } - this.draw(); - - if (!this.isVisible()) { - this.remove = true; - } - } - }, - - /** - * Calculate a random gap size. - * - Minimum gap gets wider as speed increses - * @param {number} gapCoefficient - * @param {number} speed - * @return {number} The gap size. - */ - getGap: function (gapCoefficient, speed) { - var minGap = Math.round(this.width * speed + - this.typeConfig.minGap * gapCoefficient); - var maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT); - return getRandomNum(minGap, maxGap); - }, - - /** - * Check if obstacle is visible. - * @return {boolean} Whether the obstacle is in the game area. - */ - isVisible: function () { - return this.xPos + this.width > 0; - }, - - /** - * Make a copy of the collision boxes, since these will change based on - * obstacle type and size. - */ - cloneCollisionBoxes: function () { - var collisionBoxes = this.typeConfig.collisionBoxes; - - for (var i = collisionBoxes.length - 1; i >= 0; i--) { - this.collisionBoxes[i] = new CollisionBox(collisionBoxes[i].x, - collisionBoxes[i].y, collisionBoxes[i].width, - collisionBoxes[i].height); - } - } - }; - - - /** - * Obstacle definitions. - * minGap: minimum pixel space betweeen obstacles. - * multipleSpeed: Speed at which multiples are allowed. - * speedOffset: speed faster / slower than the horizon. - * minSpeed: Minimum speed which the obstacle can make an appearance. - */ - Obstacle.types = [ - { - type: 'CACTUS_SMALL', - width: 17, - height: 35, - yPos: 105, - multipleSpeed: 4, - minGap: 120, - minSpeed: 0, - collisionBoxes: [ - new CollisionBox(0, 7, 5, 27), - new CollisionBox(4, 0, 6, 34), - new CollisionBox(10, 4, 7, 14) - ] - }, - { - type: 'CACTUS_LARGE', - width: 25, - height: 50, - yPos: 90, - multipleSpeed: 7, - minGap: 120, - minSpeed: 0, - collisionBoxes: [ - new CollisionBox(0, 12, 7, 38), - new CollisionBox(8, 0, 7, 49), - new CollisionBox(13, 10, 10, 38) - ] - }, - { - type: 'PTERODACTYL', - width: 46, - height: 40, - yPos: [100, 75, 50], // Variable height. - yPosMobile: [100, 50], // Variable height mobile. - multipleSpeed: 999, - minSpeed: 8.5, - minGap: 150, - collisionBoxes: [ - new CollisionBox(15, 15, 16, 5), - new CollisionBox(18, 21, 24, 6), - new CollisionBox(2, 14, 4, 3), - new CollisionBox(6, 10, 4, 7), - new CollisionBox(10, 8, 6, 9) - ], - numFrames: 2, - frameRate: 1000 / 6, - speedOffset: .8 - } - ]; - - - //****************************************************************************** - /** - * T-rex game character. - * @param {HTMLCanvas} canvas - * @param {Object} spritePos Positioning within image sprite. - * @constructor - */ - function Trex(canvas, spritePos) { - this.canvas = canvas; - this.canvasCtx = canvas.getContext('2d'); - this.spritePos = spritePos; - this.xPos = 0; - this.yPos = 0; - // Position when on the ground. - this.groundYPos = 0; - this.currentFrame = 0; - this.currentAnimFrames = []; - this.blinkDelay = 0; - this.blinkCount = 0; - this.animStartTime = 0; - this.timer = 0; - this.msPerFrame = 1000 / FPS; - this.config = Trex.config; - // Current status. - this.status = Trex.status.WAITING; - - this.jumping = false; - this.ducking = false; - this.jumpVelocity = 0; - this.reachedMinHeight = false; - this.speedDrop = false; - this.jumpCount = 0; - this.jumpspotX = 0; - - this.init(); - }; - - - /** - * T-rex player config. - * @enum {number} - */ - Trex.config = { - DROP_VELOCITY: -5, - GRAVITY: 0.6, - HEIGHT: 47, - HEIGHT_DUCK: 25, - INIITAL_JUMP_VELOCITY: -10, - INTRO_DURATION: 1500, - MAX_JUMP_HEIGHT: 30, - MIN_JUMP_HEIGHT: 30, - SPEED_DROP_COEFFICIENT: 3, - SPRITE_WIDTH: 262, - START_X_POS: 50, - WIDTH: 44, - WIDTH_DUCK: 59 - }; - - - /** - * Used in collision detection. - * @type {Array} - */ - Trex.collisionBoxes = { - DUCKING: [ - new CollisionBox(1, 18, 55, 25) - ], - RUNNING: [ - new CollisionBox(22, 0, 17, 16), - new CollisionBox(1, 18, 30, 9), - new CollisionBox(10, 35, 14, 8), - new CollisionBox(1, 24, 29, 5), - new CollisionBox(5, 30, 21, 4), - new CollisionBox(9, 34, 15, 4) - ] - }; - - - /** - * Animation states. - * @enum {string} - */ - Trex.status = { - CRASHED: 'CRASHED', - DUCKING: 'DUCKING', - JUMPING: 'JUMPING', - RUNNING: 'RUNNING', - WAITING: 'WAITING' - }; - - /** - * Blinking coefficient. - * @const - */ - Trex.BLINK_TIMING = 7000; - - - /** - * Animation config for different states. - * @enum {Object} - */ - Trex.animFrames = { - WAITING: { - frames: [44, 0], - msPerFrame: 1000 / 3 - }, - RUNNING: { - frames: [88, 132], - msPerFrame: 1000 / 12 - }, - CRASHED: { - frames: [220], - msPerFrame: 1000 / 60 - }, - JUMPING: { - frames: [0], - msPerFrame: 1000 / 60 - }, - DUCKING: { - frames: [262, 321], - msPerFrame: 1000 / 8 - } - }; - - - Trex.prototype = { - /** - * T-rex player initaliser. - * Sets the t-rex to blink at random intervals. - */ - init: function () { - this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT - - Runner.config.BOTTOM_PAD; - this.yPos = this.groundYPos; - this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT; - - this.draw(0, 0); - this.update(0, Trex.status.WAITING); - }, - - /** - * Setter for the jump velocity. - * The approriate drop velocity is also set. - */ - setJumpVelocity: function (setting) { - this.config.INIITAL_JUMP_VELOCITY = -setting; - this.config.DROP_VELOCITY = -setting / 2; - }, - - /** - * Set the animation status. - * @param {!number} deltaTime - * @param {Trex.status} status Optional status to switch to. - */ - update: function (deltaTime, opt_status) { - this.timer += deltaTime; - - // Update the status. - if (opt_status) { - this.status = opt_status; - this.currentFrame = 0; - this.msPerFrame = Trex.animFrames[opt_status].msPerFrame; - this.currentAnimFrames = Trex.animFrames[opt_status].frames; - - if (opt_status == Trex.status.WAITING) { - this.animStartTime = getTimeStamp(); - this.setBlinkDelay(); - } - } - - // Game intro animation, T-rex moves in from the left. - if (this.playingIntro && this.xPos < this.config.START_X_POS) { - this.xPos += Math.round((this.config.START_X_POS / - this.config.INTRO_DURATION) * deltaTime); - } - - if (this.status == Trex.status.WAITING) { - this.blink(getTimeStamp()); - } else { - this.draw(this.currentAnimFrames[this.currentFrame], 0); - } - - // Update the frame position. - if (this.timer >= this.msPerFrame) { - this.currentFrame = this.currentFrame == - this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1; - this.timer = 0; - } - - // Speed drop becomes duck if the down key is still being pressed. - if (this.speedDrop && this.yPos == this.groundYPos) { - this.speedDrop = false; - this.setDuck(true); - } - }, - - /** - * Draw the t-rex to a particular position. - * @param {number} x - * @param {number} y - */ - draw: function (x, y) { - var sourceX = x; - var sourceY = y; - var sourceWidth = this.ducking && this.status != Trex.status.CRASHED ? - this.config.WIDTH_DUCK : this.config.WIDTH; - var sourceHeight = this.config.HEIGHT; - - if (IS_HIDPI) { - sourceX *= 2; - sourceY *= 2; - sourceWidth *= 2; - sourceHeight *= 2; - } - - // Adjustments for sprite sheet position. - sourceX += this.spritePos.x; - sourceY += this.spritePos.y; - - // Ducking. - if (this.ducking && this.status != Trex.status.CRASHED) { - this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY, - sourceWidth, sourceHeight, - this.xPos, this.yPos, - this.config.WIDTH_DUCK, this.config.HEIGHT); - } else { - // Crashed whilst ducking. Trex is standing up so needs adjustment. - if (this.ducking && this.status == Trex.status.CRASHED) { - this.xPos++; - } - // Standing / running - this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY, - sourceWidth, sourceHeight, - this.xPos, this.yPos, - this.config.WIDTH, this.config.HEIGHT); - } - }, - - /** - * Sets a random time for the blink to happen. - */ - setBlinkDelay: function () { - this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING); - }, - - /** - * Make t-rex blink at random intervals. - * @param {number} time Current time in milliseconds. - */ - blink: function (time) { - var deltaTime = time - this.animStartTime; - - if (deltaTime >= this.blinkDelay) { - this.draw(this.currentAnimFrames[this.currentFrame], 0); - - if (this.currentFrame == 1) { - // Set new random delay to blink. - this.setBlinkDelay(); - this.animStartTime = time; - this.blinkCount++; - } - } - }, - - /** - * Initialise a jump. - * @param {number} speed - */ - startJump: function (speed) { - if (!this.jumping) { - this.update(0, Trex.status.JUMPING); - // Tweak the jump velocity based on the speed. - this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY - (speed / 10); - this.jumping = true; - this.reachedMinHeight = false; - this.speedDrop = false; - } - }, - - /** - * Jump is complete, falling down. - */ - endJump: function () { - if (this.reachedMinHeight && - this.jumpVelocity < this.config.DROP_VELOCITY) { - this.jumpVelocity = this.config.DROP_VELOCITY; - } - }, - - /** - * Update frame for a jump. - * @param {number} deltaTime - * @param {number} speed - */ - updateJump: function (deltaTime, speed) { - var msPerFrame = Trex.animFrames[this.status].msPerFrame; - var framesElapsed = deltaTime / msPerFrame; - - // Speed drop makes Trex fall faster. - if (this.speedDrop) { - this.yPos += Math.round(this.jumpVelocity * - this.config.SPEED_DROP_COEFFICIENT * framesElapsed); - } else { - this.yPos += Math.round(this.jumpVelocity * framesElapsed); - } - - this.jumpVelocity += this.config.GRAVITY * framesElapsed; - - // Minimum height has been reached. - if (this.yPos < this.minJumpHeight || this.speedDrop) { - this.reachedMinHeight = true; - } - - // Reached max height - if (this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop) { - this.endJump(); - } - - // Back down at ground level. Jump completed. - if (this.yPos > this.groundYPos) { - this.reset(); - this.jumpCount++; - } - - this.update(deltaTime); - }, - - /** - * Set the speed drop. Immediately cancels the current jump. - */ - setSpeedDrop: function () { - this.speedDrop = true; - this.jumpVelocity = 1; - }, - - /** - * @param {boolean} isDucking. - */ - setDuck: function (isDucking) { - if (isDucking && this.status != Trex.status.DUCKING) { - this.update(0, Trex.status.DUCKING); - this.ducking = true; - } else if (this.status == Trex.status.DUCKING) { - this.update(0, Trex.status.RUNNING); - this.ducking = false; - } - }, - - /** - * Reset the t-rex to running at start of game. - */ - reset: function () { - this.yPos = this.groundYPos; - this.jumpVelocity = 0; - this.jumping = false; - this.ducking = false; - this.update(0, Trex.status.RUNNING); - this.midair = false; - this.speedDrop = false; - this.jumpCount = 0; - } - }; - - - //****************************************************************************** - - /** - * Handles displaying the distance meter. - * @param {!HTMLCanvasElement} canvas - * @param {Object} spritePos Image position in sprite. - * @param {number} canvasWidth - * @constructor - */ - function DistanceMeter(canvas, spritePos, canvasWidth) { - this.canvas = canvas; - this.canvasCtx = canvas.getContext('2d'); - this.image = Runner.imageSprite; - this.spritePos = spritePos; - this.x = 0; - this.y = 5; - - this.currentDistance = 0; - this.maxScore = 0; - this.highScore = 0; - this.container = null; - - this.digits = []; - this.acheivement = false; - this.defaultString = ''; - this.flashTimer = 0; - this.flashIterations = 0; - this.invertTrigger = false; - - this.config = DistanceMeter.config; - this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS; - this.init(canvasWidth); - }; - - - /** - * @enum {number} - */ - DistanceMeter.dimensions = { - WIDTH: 10, - HEIGHT: 13, - DEST_WIDTH: 11 - }; - - - /** - * Y positioning of the digits in the sprite sheet. - * X position is always 0. - * @type {Array} - */ - DistanceMeter.yPos = [0, 13, 27, 40, 53, 67, 80, 93, 107, 120]; - - - /** - * Distance meter config. - * @enum {number} - */ - DistanceMeter.config = { - // Number of digits. - MAX_DISTANCE_UNITS: 5, - - // Distance that causes achievement animation. - ACHIEVEMENT_DISTANCE: 100, - - // Used for conversion from pixel distance to a scaled unit. - COEFFICIENT: 0.025, - - // Flash duration in milliseconds. - FLASH_DURATION: 1000 / 4, - - // Flash iterations for achievement animation. - FLASH_ITERATIONS: 3 - }; - - - DistanceMeter.prototype = { - /** - * Initialise the distance meter to '00000'. - * @param {number} width Canvas width in px. - */ - init: function (width) { - var maxDistanceStr = ''; - - this.calcXPos(width); - this.maxScore = this.maxScoreUnits; - for (var i = 0; i < this.maxScoreUnits; i++) { - this.draw(i, 0); - this.defaultString += '0'; - maxDistanceStr += '9'; - } - - this.maxScore = parseInt(maxDistanceStr); - }, - - /** - * Calculate the xPos in the canvas. - * @param {number} canvasWidth - */ - calcXPos: function (canvasWidth) { - this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH * - (this.maxScoreUnits + 1)); - }, - - /** - * Draw a digit to canvas. - * @param {number} digitPos Position of the digit. - * @param {number} value Digit value 0-9. - * @param {boolean} opt_highScore Whether drawing the high score. - */ - draw: function (digitPos, value, opt_highScore) { - var sourceWidth = DistanceMeter.dimensions.WIDTH; - var sourceHeight = DistanceMeter.dimensions.HEIGHT; - var sourceX = DistanceMeter.dimensions.WIDTH * value; - var sourceY = 0; - - var targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH; - var targetY = this.y; - var targetWidth = DistanceMeter.dimensions.WIDTH; - var targetHeight = DistanceMeter.dimensions.HEIGHT; - - // For high DPI we 2x source values. - if (IS_HIDPI) { - sourceWidth *= 2; - sourceHeight *= 2; - sourceX *= 2; - } - - sourceX += this.spritePos.x; - sourceY += this.spritePos.y; - - this.canvasCtx.save(); - - if (opt_highScore) { - // Left of the current score. - var highScoreX = this.x - (this.maxScoreUnits * 2) * - DistanceMeter.dimensions.WIDTH; - this.canvasCtx.translate(highScoreX, this.y); - } else { - this.canvasCtx.translate(this.x, this.y); - } - - this.canvasCtx.drawImage(this.image, sourceX, sourceY, - sourceWidth, sourceHeight, - targetX, targetY, - targetWidth, targetHeight - ); - - this.canvasCtx.restore(); - }, - - /** - * Covert pixel distance to a 'real' distance. - * @param {number} distance Pixel distance ran. - * @return {number} The 'real' distance ran. - */ - getActualDistance: function (distance) { - return distance ? Math.round(distance * this.config.COEFFICIENT) : 0; - }, - - /** - * Update the distance meter. - * @param {number} distance - * @param {number} deltaTime - * @return {boolean} Whether the acheivement sound fx should be played. - */ - update: function (deltaTime, distance) { - var paint = true; - var playSound = false; - - if (!this.acheivement) { - distance = this.getActualDistance(distance); - // Score has gone beyond the initial digit count. - if (distance > this.maxScore && this.maxScoreUnits == - this.config.MAX_DISTANCE_UNITS) { - this.maxScoreUnits++; - this.maxScore = parseInt(this.maxScore + '9'); - } else { - this.distance = 0; - } - - if (distance > 0) { - // Acheivement unlocked - if (distance % this.config.ACHIEVEMENT_DISTANCE == 0) { - // Flash score and play sound. - this.acheivement = true; - this.flashTimer = 0; - playSound = true; - } - - // Create a string representation of the distance with leading 0. - var distanceStr = (this.defaultString + - distance).substr(-this.maxScoreUnits); - this.digits = distanceStr.split(''); - } else { - this.digits = this.defaultString.split(''); - } - } else { - // Control flashing of the score on reaching acheivement. - if (this.flashIterations <= this.config.FLASH_ITERATIONS) { - this.flashTimer += deltaTime; - - if (this.flashTimer < this.config.FLASH_DURATION) { - paint = false; - } else if (this.flashTimer > - this.config.FLASH_DURATION * 2) { - this.flashTimer = 0; - this.flashIterations++; - } - } else { - this.acheivement = false; - this.flashIterations = 0; - this.flashTimer = 0; - } - } - - // Draw the digits if not flashing. - if (paint) { - for (var i = this.digits.length - 1; i >= 0; i--) { - this.draw(i, parseInt(this.digits[i])); - } - } - - this.drawHighScore(); - return playSound; - }, - - /** - * Draw the high score. - */ - drawHighScore: function () { - this.canvasCtx.save(); - this.canvasCtx.globalAlpha = .8; - for (var i = this.highScore.length - 1; i >= 0; i--) { - this.draw(i, parseInt(this.highScore[i], 10), true); - } - this.canvasCtx.restore(); - }, - - /** - * Set the highscore as a array string. - * Position of char in the sprite: H - 10, I - 11. - * @param {number} distance Distance ran in pixels. - */ - setHighScore: function (distance) { - distance = this.getActualDistance(distance); - var highScoreStr = (this.defaultString + - distance).substr(-this.maxScoreUnits); - - this.highScore = ['10', '11', ''].concat(highScoreStr.split('')); - }, - - /** - * Reset the distance meter back to '00000'. - */ - reset: function () { - this.update(0); - this.acheivement = false; - } - }; - - function Sun(canvas) { - this.canvas = canvas; - this.canvasCtx = this.canvas.getContext('2d'); - this.init(); - } - - Sun.config = { - X_POS: 120, - Y_POS: 15, - }; - - Sun.prototype = { - init: function() { - this.draw(); - }, - - draw: function() { - this.canvasCtx.drawImage(Sun.imageSprite, Sun.config.X_POS, Sun.config.Y_POS); - }, - - update: function() { - this.draw(); - } - }; - - //****************************************************************************** - - /** - * Cloud background item. - * Similar to an obstacle object but without collision boxes. - * @param {HTMLCanvasElement} canvas Canvas element. - * @param {Object} spritePos Position of image in sprite. - * @param {number} containerWidth - */ - function Cloud(canvas, spritePos, containerWidth) { - this.canvas = canvas; - this.canvasCtx = this.canvas.getContext('2d'); - this.spritePos = spritePos; - this.containerWidth = containerWidth; - this.xPos = containerWidth; - this.yPos = 0; - this.remove = false; - this.cloudGap = getRandomNum(Cloud.config.MIN_CLOUD_GAP, - Cloud.config.MAX_CLOUD_GAP); - - this.init(); - }; - - - /** - * Cloud object config. - * @enum {number} - */ - Cloud.config = { - HEIGHT: 14, - MAX_CLOUD_GAP: 400, - MAX_SKY_LEVEL: 30, - MIN_CLOUD_GAP: 100, - MIN_SKY_LEVEL: 71, - WIDTH: 46 - }; - - - Cloud.prototype = { - /** - * Initialise the cloud. Sets the Cloud height. - */ - init: function () { - this.yPos = getRandomNum(Cloud.config.MAX_SKY_LEVEL, - Cloud.config.MIN_SKY_LEVEL); - this.draw(); - }, - - /** - * Draw the cloud. - */ - draw: function () { - this.canvasCtx.save(); - var sourceWidth = Cloud.config.WIDTH; - var sourceHeight = Cloud.config.HEIGHT; - - if (IS_HIDPI) { - sourceWidth = sourceWidth * 2; - sourceHeight = sourceHeight * 2; - } - - this.canvasCtx.drawImage(Runner.imageSprite, this.spritePos.x, - this.spritePos.y, - sourceWidth, sourceHeight, - this.xPos, this.yPos, - Cloud.config.WIDTH, Cloud.config.HEIGHT); - - this.canvasCtx.restore(); - }, - - /** - * Update the cloud position. - * @param {number} speed - */ - update: function (speed) { - if (!this.remove) { - this.xPos -= Math.ceil(speed); - this.draw(); - - // Mark as removeable if no longer in the canvas. - if (!this.isVisible()) { - this.remove = true; - } - } - }, - - /** - * Check if the cloud is visible on the stage. - * @return {boolean} - */ - isVisible: function () { - return this.xPos + Cloud.config.WIDTH > 0; - } - }; - - - //****************************************************************************** - - /** - * Nightmode shows a moon and stars on the horizon. - */ - function NightMode(canvas, spritePos, containerWidth) { - this.spritePos = spritePos; - this.canvas = canvas; - this.canvasCtx = canvas.getContext('2d'); - this.xPos = containerWidth - 50; - this.yPos = 30; - this.currentPhase = 0; - this.opacity = 0; - this.containerWidth = containerWidth; - this.stars = []; - this.drawStars = false; - this.placeStars(); - }; - - /** - * @enum {number} - */ - NightMode.config = { - FADE_SPEED: 0.035, - HEIGHT: 40, - MOON_SPEED: 0.25, - NUM_STARS: 2, - STAR_SIZE: 9, - STAR_SPEED: 0.3, - STAR_MAX_Y: 70, - WIDTH: 20 - }; - - NightMode.phases = [140, 120, 100, 60, 40, 20, 0]; - - NightMode.prototype = { - /** - * Update moving moon, changing phases. - * @param {boolean} activated Whether night mode is activated. - * @param {number} delta - */ - update: function (activated, delta) { - // Moon phase. - if (activated && this.opacity == 0) { - this.currentPhase++; - - if (this.currentPhase >= NightMode.phases.length) { - this.currentPhase = 0; - } - } - - // Fade in / out. - if (activated && (this.opacity < 1 || this.opacity == 0)) { - this.opacity += NightMode.config.FADE_SPEED; - } else if (this.opacity > 0) { - this.opacity -= NightMode.config.FADE_SPEED; - } - - // Set moon positioning. - if (this.opacity > 0) { - this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED); - - // Update stars. - if (this.drawStars) { - for (var i = 0; i < NightMode.config.NUM_STARS; i++) { - this.stars[i].x = this.updateXPos(this.stars[i].x, - NightMode.config.STAR_SPEED); - } - } - this.draw(); - } else { - this.opacity = 0; - this.placeStars(); - } - this.drawStars = true; - }, - - updateXPos: function (currentPos, speed) { - if (currentPos < -NightMode.config.WIDTH) { - currentPos = this.containerWidth; - } else { - currentPos -= speed; - } - return currentPos; - }, - - draw: function () { - var moonSourceWidth = this.currentPhase == 3 ? NightMode.config.WIDTH * 2 : - NightMode.config.WIDTH; - var moonSourceHeight = NightMode.config.HEIGHT; - var moonSourceX = this.spritePos.x + NightMode.phases[this.currentPhase]; - var moonOutputWidth = moonSourceWidth; - var starSize = NightMode.config.STAR_SIZE; - var starSourceX = Runner.spriteDefinition.LDPI.STAR.x; - - if (IS_HIDPI) { - moonSourceWidth *= 2; - moonSourceHeight *= 2; - moonSourceX = this.spritePos.x + - (NightMode.phases[this.currentPhase] * 2); - starSize *= 2; - starSourceX = Runner.spriteDefinition.HDPI.STAR.x; - } - - this.canvasCtx.save(); - this.canvasCtx.globalAlpha = this.opacity; - - // Stars. - if (this.drawStars) { - for (var i = 0; i < NightMode.config.NUM_STARS; i++) { - this.canvasCtx.drawImage(Runner.imageSprite, - starSourceX, this.stars[i].sourceY, starSize, starSize, - Math.round(this.stars[i].x), this.stars[i].y, - NightMode.config.STAR_SIZE, NightMode.config.STAR_SIZE); - } - } - - // Moon. - this.canvasCtx.drawImage(Runner.imageSprite, moonSourceX, - this.spritePos.y, moonSourceWidth, moonSourceHeight, - Math.round(this.xPos), this.yPos, - moonOutputWidth, NightMode.config.HEIGHT); - - this.canvasCtx.globalAlpha = 1; - this.canvasCtx.restore(); - }, - - // Do star placement. - placeStars: function () { - var segmentSize = Math.round(this.containerWidth / - NightMode.config.NUM_STARS); - - for (var i = 0; i < NightMode.config.NUM_STARS; i++) { - this.stars[i] = {}; - this.stars[i].x = getRandomNum(segmentSize * i, segmentSize * (i + 1)); - this.stars[i].y = getRandomNum(0, NightMode.config.STAR_MAX_Y); - - if (IS_HIDPI) { - this.stars[i].sourceY = Runner.spriteDefinition.HDPI.STAR.y + - NightMode.config.STAR_SIZE * 2 * i; - } else { - this.stars[i].sourceY = Runner.spriteDefinition.LDPI.STAR.y + - NightMode.config.STAR_SIZE * i; - } - } - }, - - reset: function () { - this.currentPhase = 0; - this.opacity = 0; - this.update(false); - } - - }; - - - //****************************************************************************** - - /** - * Horizon Line. - * Consists of two connecting lines. Randomly assigns a flat / bumpy horizon. - * @param {HTMLCanvasElement} canvas - * @param {Object} spritePos Horizon position in sprite. - * @constructor - */ - function HorizonLine(canvas, spritePos) { - this.spritePos = spritePos; - this.canvas = canvas; - this.canvasCtx = canvas.getContext('2d'); - this.sourceDimensions = {}; - this.dimensions = HorizonLine.dimensions; - this.sourceXPos = [this.spritePos.x, this.spritePos.x + - this.dimensions.WIDTH]; - this.xPos = []; - this.yPos = 0; - this.bumpThreshold = 0.5; - - this.setSourceDimensions(); - this.draw(); - }; - - - /** - * Horizon line dimensions. - * @enum {number} - */ - HorizonLine.dimensions = { - WIDTH: 600, - HEIGHT: 12, - YPOS: 127 - }; - - - HorizonLine.prototype = { - /** - * Set the source dimensions of the horizon line. - */ - setSourceDimensions: function () { - - for (var dimension in HorizonLine.dimensions) { - if (IS_HIDPI) { - if (dimension != 'YPOS') { - this.sourceDimensions[dimension] = - HorizonLine.dimensions[dimension] * 2; - } - } else { - this.sourceDimensions[dimension] = - HorizonLine.dimensions[dimension]; - } - this.dimensions[dimension] = HorizonLine.dimensions[dimension]; - } - - this.xPos = [0, HorizonLine.dimensions.WIDTH]; - this.yPos = HorizonLine.dimensions.YPOS; - }, - - /** - * Return the crop x position of a type. - */ - getRandomType: function () { - return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0; - }, - - /** - * Draw the horizon line. - */ - draw: function () { - this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[0], - this.spritePos.y, - this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT, - this.xPos[0], this.yPos, - this.dimensions.WIDTH, this.dimensions.HEIGHT); - - this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[1], - this.spritePos.y, - this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT, - this.xPos[1], this.yPos, - this.dimensions.WIDTH, this.dimensions.HEIGHT); - }, - - /** - * Update the x position of an indivdual piece of the line. - * @param {number} pos Line position. - * @param {number} increment - */ - updateXPos: function (pos, increment) { - var line1 = pos; - var line2 = pos == 0 ? 1 : 0; - - this.xPos[line1] -= increment; - this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH; - - if (this.xPos[line1] <= -this.dimensions.WIDTH) { - this.xPos[line1] += this.dimensions.WIDTH * 2; - this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH; - this.sourceXPos[line1] = this.getRandomType() + this.spritePos.x; - } - }, - - /** - * Update the horizon line. - * @param {number} deltaTime - * @param {number} speed - */ - update: function (deltaTime, speed) { - var increment = Math.floor(speed * (FPS / 1000) * deltaTime); - - if (this.xPos[0] <= 0) { - this.updateXPos(0, increment); - } else { - this.updateXPos(1, increment); - } - this.draw(); - }, - - /** - * Reset horizon to the starting position. - */ - reset: function () { - this.xPos[0] = 0; - this.xPos[1] = HorizonLine.dimensions.WIDTH; - } - }; - - - //****************************************************************************** - - /** - * Horizon background class. - * @param {HTMLCanvasElement} canvas - * @param {Object} spritePos Sprite positioning. - * @param {Object} dimensions Canvas dimensions. - * @param {number} gapCoefficient - * @constructor - */ - function Horizon(canvas, spritePos, dimensions, gapCoefficient) { - this.canvas = canvas; - this.canvasCtx = this.canvas.getContext('2d'); - this.config = Horizon.config; - this.dimensions = dimensions; - this.gapCoefficient = gapCoefficient; - this.obstacles = []; - this.obstacleHistory = []; - this.horizonOffsets = [0, 0]; - this.cloudFrequency = this.config.CLOUD_FREQUENCY; - this.spritePos = spritePos; - this.nightMode = null; - - // Cloud - this.clouds = []; - this.cloudSpeed = this.config.BG_CLOUD_SPEED; - - // Horizon - this.horizonLine = null; - this.init(); - }; - - - /** - * Horizon config. - * @enum {number} - */ - Horizon.config = { - BG_CLOUD_SPEED: 0.2, - BUMPY_THRESHOLD: .3, - CLOUD_FREQUENCY: .5, - HORIZON_HEIGHT: 16, - MAX_CLOUDS: 6 - }; - - - Horizon.prototype = { - /** - * Initialise the horizon. Just add the line and a cloud. No obstacles. - */ - init: function () { - this.addCloud(); - this.horizonLine = new HorizonLine(this.canvas, this.spritePos.HORIZON); - this.nightMode = new NightMode(this.canvas, this.spritePos.MOON, - this.dimensions.WIDTH); - this.sun = new Sun(this.canvas); - }, - - /** - * @param {number} deltaTime - * @param {number} currentSpeed - * @param {boolean} updateObstacles Used as an override to prevent - * the obstacles from being updated / added. This happens in the - * ease in section. - * @param {boolean} showNightMode Night mode activated. - */ - update: function (deltaTime, currentSpeed, updateObstacles, showNightMode) { - this.sun.update(); - this.runningTime += deltaTime; - this.horizonLine.update(deltaTime, currentSpeed); - this.nightMode.update(showNightMode); - this.updateClouds(deltaTime, currentSpeed); - - if (updateObstacles) { - this.updateObstacles(deltaTime, currentSpeed); - } - - }, - - /** - * Update the cloud positions. - * @param {number} deltaTime - * @param {number} currentSpeed - */ - updateClouds: function (deltaTime, speed) { - var cloudSpeed = this.cloudSpeed / 1000 * deltaTime * speed; - var numClouds = this.clouds.length; - - if (numClouds) { - for (var i = numClouds - 1; i >= 0; i--) { - this.clouds[i].update(cloudSpeed); - } - - var lastCloud = this.clouds[numClouds - 1]; - - // Check for adding a new cloud. - if (numClouds < this.config.MAX_CLOUDS && - (this.dimensions.WIDTH - lastCloud.xPos) > lastCloud.cloudGap && - this.cloudFrequency > Math.random()) { - this.addCloud(); - } - - // Remove expired clouds. - this.clouds = this.clouds.filter(function (obj) { - return !obj.remove; - }); - } else { - this.addCloud(); - } - }, - - /** - * Update the obstacle positions. - * @param {number} deltaTime - * @param {number} currentSpeed - */ - updateObstacles: function (deltaTime, currentSpeed) { - // Obstacles, move to Horizon layer. - var updatedObstacles = this.obstacles.slice(0); - - for (var i = 0; i < this.obstacles.length; i++) { - var obstacle = this.obstacles[i]; - obstacle.update(deltaTime, currentSpeed); - - // Clean up existing obstacles. - if (obstacle.remove) { - updatedObstacles.shift(); - } - } - this.obstacles = updatedObstacles; - - if (this.obstacles.length > 0) { - var lastObstacle = this.obstacles[this.obstacles.length - 1]; - - if (lastObstacle && !lastObstacle.followingObstacleCreated && - lastObstacle.isVisible() && - (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) < - this.dimensions.WIDTH) { - this.addNewObstacle(currentSpeed); - lastObstacle.followingObstacleCreated = true; - } - } else { - // Create new obstacles. - this.addNewObstacle(currentSpeed); - } - }, - - removeFirstObstacle: function () { - this.obstacles.shift(); - }, - - /** - * Add a new obstacle. - * @param {number} currentSpeed - */ - addNewObstacle: function (currentSpeed) { - var obstacleTypeIndex = getRandomNum(0, Obstacle.types.length - 1); - var obstacleType = Obstacle.types[obstacleTypeIndex]; - - // Check for multiples of the same type of obstacle. - // Also check obstacle is available at current speed. - if (this.duplicateObstacleCheck(obstacleType.type) || - currentSpeed < obstacleType.minSpeed) { - this.addNewObstacle(currentSpeed); - } else { - var obstacleSpritePos = this.spritePos[obstacleType.type]; - - this.obstacles.push(new Obstacle(this.canvasCtx, obstacleType, - obstacleSpritePos, this.dimensions, - this.gapCoefficient, currentSpeed, obstacleType.width)); - - this.obstacleHistory.unshift(obstacleType.type); - - if (this.obstacleHistory.length > 1) { - this.obstacleHistory.splice(Runner.config.MAX_OBSTACLE_DUPLICATION); - } - } - }, - - /** - * Returns whether the previous two obstacles are the same as the next one. - * Maximum duplication is set in config value MAX_OBSTACLE_DUPLICATION. - * @return {boolean} - */ - duplicateObstacleCheck: function (nextObstacleType) { - var duplicateCount = 0; - - for (var i = 0; i < this.obstacleHistory.length; i++) { - duplicateCount = this.obstacleHistory[i] == nextObstacleType ? - duplicateCount + 1 : 0; - } - return duplicateCount >= Runner.config.MAX_OBSTACLE_DUPLICATION; - }, - - /** - * Reset the horizon layer. - * Remove existing obstacles and reposition the horizon line. - */ - reset: function () { - this.obstacles = []; - this.horizonLine.reset(); - this.nightMode.reset(); - }, - - /** - * Update the canvas width and scaling. - * @param {number} width Canvas width. - * @param {number} height Canvas height. - */ - resize: function (width, height) { - this.canvas.width = width; - this.canvas.height = height; - }, - - /** - * Add a new cloud to the horizon. - */ - addCloud: function () { - this.clouds.push(new Cloud(this.canvas, this.spritePos.CLOUD, - this.dimensions.WIDTH)); - } - }; -})(); - - -function onDocumentLoad() { - new Runner('.interstitial-wrapper'); -} - -document.addEventListener('DOMContentLoaded', onDocumentLoad); diff --git a/trex/manifest.json b/trex/manifest.json deleted file mode 100644 index 61dbb98..0000000 --- a/trex/manifest.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "manifest_version": 2, - "name": "T-Rex Outrunner", - "description": "Replaces the new tab page with a custom Chrome disconnected page, inspired by an /r/outrun post.", - "version": "1", - "author": "Michael Hoskins", - - "browser_action": { - "default_title": "Outrun", - "default_icon": "assets/icon-24x24.png" - }, - - "icons": { - "16": "assets/icon-16x16.png", - "48": "assets/icon-48x48.png", - "128": "assets/icon-128x128.png" - }, - - "chrome_url_overrides" : { - "newtab": "index.html" - }, - - "permissions": ["activeTab"] -} \ No newline at end of file