Show HN: Looptap – A minimal game to waste your time
looptap.vasanthv.com
looptap.vasanthv.com
At a score of 1,619 the ball is moving so fast it no longer works! I switched to a new tab (hoping that the event loop would be sent to the background/context switch/whatever) and ta-da - it misclicked. High score of 1,637.
function intersectRect(rectA, rectB) { return !( rectB.left >= rectA.right || rectB.right <= rectA.left || rectB.top >= rectA.bottom || rectB.bottom <= rectA.top ); }
const arc = document.getElementById("arc"); const ball = document.getElementById("ball");
setInterval(() => { if (intersectRect(arc.getBoundingClientRect(), ball.getBoundingClientRect())) { window.dispatchEvent(new KeyboardEvent('keypress', { keyCode: 32, })); } }, 10);
My code seems to go on forever, closed the tab after it reached 60K
Of course, for how delightfully simple this one is, your approach is definitely faster/better!
Just adding this information for the ones who don't know and like to know more about browser performance :)
EDIT: Looked down thread and basically saw a bunch of examples of just that!
So calling it 100 times in a loop should be fast, but calling it 100 times in a loot that also updates the DOM should be cripplingly slow.
Are you usually able to do this when e.g. it's more of a SPA and you have to drill down more to get to the state of some deeply nested component?
Also, in retrospect, I probably should have read the code before pasting it into my console.
setInterval(() => {
loopTapApp.arc = [ -Infinity, Infinity ]
loopTapApp.tap({ preventDefault: () => {}, stopPropagation: () => {} })
})Safari Version 15.2 (16612.3.6.1.8, 16612) macOS Big Sur 11.6.2 (20G314) iMac Pro 2017
loopTapApp.startPlay();
setInterval(() => {
if(loopTapApp.getBallAngle() > loopTapApp.arc[0] && loopTapApp.getBallAngle() < loopTapApp.arc[1]) {
loopTapApp.tap({preventDefault: ()=>{}, stopPropagation: ()=>{}});
}
}, 50);I didn't peek and ended up with the same solution as you, so for an extra bit of fun I tried to golf it to be as short as possible. There's probably room for much improvement: (94 chars)
setInterval("l=loopTapApp;b=l.getBallAngle();a=l.arc;b+6>a[0]&b-6<a[1]&&l.tap(new Event(1))")
setInterval("l=loopTapApp;b=l.getBallAngle();[a,c]=l.arc;b+6>a&b-6<c&&l.tap(new Event(1))") loopTapApp.score = Number.MAX_SAFE_INTEGER;loopTapApp.score = Infinity
loopTapApp.setArc=function(){this.arc=[0,359];} function autoPlay() {
const fakeClick = {
preventDefault: function() {},
stopPropagation: function() {}
}
const ballAngle = loopTapApp.getBallAngle();
if (ballAngle + 6 > loopTapApp.arc[0] && ballAngle - 6 < loopTapApp.arc[1]) {
loopTapApp.tap(fakeClick)
}
}
const autoPlayer = setInterval(autoPlay, 10)One thing to note about this kind of endless runner games is the scoring system. Everyone uses the system "get as much points as possible in one run, each run starts from zero". But that forces people to slog through the early parts each time, and also makes them more likely to stop playing after an especially good run. I thought about this for a while, and came up with the idea that a better system would dock points for mistakes. The run would continue, a misclick would just give you a short breather and set you back fifty points or so.
I made some prototypes like that and the effect was striking: you stick with one run for a long time, falling into a kind of meditative state, never wanting to stop. Eventually you find an equilibrium where wins and losses balance out, and keep playing there, at the best of your ability. If I ever release an endless runner again, it will use that system.
For anyone else confused: the goal is to keep the light colored "snake" moving through the dark colored "tunnel". When the snake stops moving, it's because it's at a junction in the tunnel. You're goal is to tell the snake which direction to go (either left or right) _from its perspective_.
I really like your idea for solving the "endless runner problem." I guess the reason we haven't seen mobile games adopt this already is because each restart is an opportunity to show you an ad. :/
- Ads after X failures
- Spend Fun Points to avoid ads and prevent misclicks from subtracting your score
- Fun points can be bought
- or obtained by watching an ad
- or obtained very rarely through regular play (e.g. by spending many Fun Coins)
- Spend Fun Coins to customize your ball and background
- Fun coins can be bought
- or obtained by watching an ad
- or obtained infrequently through regular play
- Daily bonuses which scale with number of consecutive days logged in
- Fun Points and/or Coins obtained by referring a friend
et cetera
Oh.
Oh ok. This is whatever the opposite of a bootleg is. It's something entirely new.
So I think this might be my new favorite thing, if for no reason than that I honestly cannot think of anything as straightforwardly and equivalently awesome. Perhaps the Bad Apple stop-motion PV (https://youtu.be/240Vq6tIxio) is vaguely in the ballpark.
Thanks.
Now to close it so I don't wear it out. :D
Really sad the Android build appears to have fallen off the Play store :/
Couple of recommendations, in no particular order:
1. Keep the ball moving even after you die. Instead of pressing start, the user will just click when the ball is back inside the colored area. 2. You can click anywhere to restart, but you have to click the play button to restart. You should allow the user to just click anywhere to restart. 3. When you fail, show where the ball was. 4. As for collision detection, I think you should err on being lenient and count success if the bounds of the circle overlap, rather than the center or entirely. I think this is also due to the rounded linecaps for the arc potentially not counting as part of the valid area. 5. Show where the next arc will be. This is more of a game design thing. If you do this, quick fade out of the old and fade in on the new would look nice.
I understand that this was probably a small little one off project, and that there's always more you can iterate on, so I just want to say nice job in putting out a fun little time waster!
this.score = this.score + (tapInterval < 500 ? 5 : tapInterval < 1000 ? 2 : 1);Also I should have lead my original comment with kudos to the maker, didn’t mean to only leave (apparently inaccurate) critical feedback!
EDIT: Found the code
(tapInterval < 500 ? 5 : tapInterval < 1000 ? 2 : 1)
So 5 points for under 0.5s, 2 points for under 1s, 1 point otherwise. 1. Add a share button to share your score
2. Add a timer component (can be hidden). It will add urgency.
3. Speed up the ball as time goes on (maybe i haven't gotten far enough for this to happen...?)
4. If you're adding more points for "same rotation hit", make it clear that it's doing that and don't just add more points to the score (shake the number, throw some fireworks, or something). Your focus is on the ball not the score so you won't even notice the number went up a little bit more than usual until you lose.Wordle 209 6/6
⬛⬛⬛⬛ ⬛⬛⬛⬛ ⬛⬛⬛ ⬛⬛⬛ ⬛⬛
EDIT: the formatting doesn't seem to work here, green and yellow squares disappear after posting the comment.
let robot = function () {
const ballAngle = loopTapApp.getBallAngle();
const arc = loopTapApp.arc;
if (ballAngle + 6 > arc[0] && ballAngle - 6 < arc[1]) {
window.dispatchEvent(new Event('mousedown'));
}
}
var timer = setInterval(robot, 10);Great little fun game though!
I feel really superior when I succeed in clicking immediately after the previous click. I clicked on the target arc and was relieved. But I immediately realize the next arc is right there, or I'm even on the arc now! Then adrenaline kicks in, and I click again immediately! Don't think, feel! And it continues because I haven't missed it.
Like I was a top F1 driver...
Start the game by pressing "space" as well.
Doesn't work for the first game yet.
Feature Request: I'd love to see the ratio of successful points to number of times the ball traverses the circle. I think others have already said things about scoring mechanisms for faster taps, I wonder if there could be one around a sweet spot on the line (earlier is better? Later?!)
Well done, you've made me late for a meeting!
Safari, iOS (whatever the latest update is)
setInterval(function() {loopTapApp.arc=[0,359];loopTapApp.tap(document.createEvent('Event'))},5);This version is more of a hack/cheat as it doesn't obey the rules of the game and just fires the tap event all the time.
A play on "flappy bird".
Great game!
https://m.media-amazon.com/images/I/617M1m5N-aL._AC_SS450_.j...
I made a game quite similar that took me years to complete. And I feel this is much more immediately engaging, could just be me being hyper critical of my own work...but its always amazing to see how other designers approach similar ideas.
Either way I love it!!!!
One thing that you could improve is add user-select:none to the page. Quickly tapping the document did select the counter text and opened a google mini popup, distracting from the game.
Well done!
I love these styles of development where it can be thrown together quickly but complete the full spectrum of ux, creativity, and hitting that sweet spot that felt satisfying to play.
Seriously well done!
this.score = this.score + (tapInterval < 500 ? 5 : tapInterval < 1000 ? 2 : 1);There should be a timed mode to encourage rapid clicking (i.e., score the most points within 30s or 1min).
"Martha. If you beat my score on 1st attempt, I'll do those TPS reports." lol.
Make this an iOS game!
Does anyone have any interesting iOS recommendations for ADHD stmming?
Otherwise fun game :)
https://github.com/vasanthv/looptap/blob/4953700d0e8b58dd57d...
You could change the dot rotation direction on each success ...
literally the same
what is the point other than pure 100% identical copy
no shame?
click and test by yourself: https://coogyloop.com/
On mobile, it just presents a link to the app store.
Addictive drop-dead simplicity is repeatable recipe for success in games. I'm working towards this same strategy in my own projects, but I think you've really nailed it here!
EDIT: Whoops, I just noticed that this was based off such an app. Still, great work on the implementation!