{"id":18927629,"url":"https://github.com/mmazzarolo/tap-the-number","last_synced_at":"2025-07-07T05:37:21.120Z","repository":{"id":66227240,"uuid":"78965593","full_name":"mmazzarolo/tap-the-number","owner":"mmazzarolo","description":"A simple React-Native game for iOS","archived":false,"fork":false,"pushed_at":"2017-06-21T23:41:27.000Z","size":5338,"stargazers_count":159,"open_issues_count":0,"forks_count":36,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-07-01T21:14:43.238Z","etag":null,"topics":["flowtype","game","ios","mobx","react-native"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mmazzarolo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-01-14T20:45:03.000Z","updated_at":"2024-03-22T23:49:29.000Z","dependencies_parsed_at":"2023-04-02T13:17:49.276Z","dependency_job_id":null,"html_url":"https://github.com/mmazzarolo/tap-the-number","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/mmazzarolo/tap-the-number","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmazzarolo%2Ftap-the-number","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmazzarolo%2Ftap-the-number/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmazzarolo%2Ftap-the-number/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmazzarolo%2Ftap-the-number/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mmazzarolo","download_url":"https://codeload.github.com/mmazzarolo/tap-the-number/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmazzarolo%2Ftap-the-number/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264021215,"owners_count":23545458,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flowtype","game","ios","mobx","react-native"],"created_at":"2024-11-08T11:19:48.193Z","updated_at":"2025-07-07T05:37:21.100Z","avatar_url":"https://github.com/mmazzarolo.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://raw.githubusercontent.com/mmazzarolo/tap-the-number/master/extra/tap_the_number_title.png\" width=\"420\"\u003e\u003c/img\u003e\u003cbr /\u003e  \u003cbr /\u003e \n\u003ca href=\"https://itunes.apple.com/us/app/tap-the-number/id1206517840?l=it\u0026ls=1\u0026mt=8\"\u003e\u003cimg src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/3/3c/Download_on_the_App_Store_Badge.svg/800px-Download_on_the_App_Store_Badge.svg.png\" width=\"120\"\u003e\u003c/img\u003e\u003c/a\u003e \n\u003c/p\u003e\n\u0026nbsp;  \n\u0026nbsp;  \n\nTap The Number is a simple React-Native game where you have to tap the tiles that appear on the screen in a specific order.  \nEven if developing this game didn't take too much time (I should have put in it ~20 hours I guess) it has been a fun ride and I'd like to share some tips and some info on the complexities I found during its development.   \nBut first...  \n\n## Screenshots and video preview\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://raw.githubusercontent.com/mmazzarolo/tap-the-number/master/extra/screenshot-iphone6plus-1.png\" width=\"240\"\u003e\u003c/img\u003e\n\u003cimg src=\"https://raw.githubusercontent.com/mmazzarolo/tap-the-number/master/extra/screenshot-iphone6plus-2.png\" width=\"240\"\u003e\u003c/img\u003e    \n\u003cimg src=\"https://raw.githubusercontent.com/mmazzarolo/tap-the-number/master/extra/screenshot-iphone6plus-3.png\" width=\"240\"\u003e\u003c/img\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://www.youtube.com/watch?v=bz7Id_HcEmw\"\u003eI also published a small game preview on Youtube\u003c/a\u003e\n\u003c/p\u003e\n\u0026nbsp;  \n\n## The stack and the dependencies\n#### React-Native\nTap The Number is built entirely with [**React-Native**](https://facebook.github.io/react-native/), \nwhich has been an obvious choice for me given the fact that I love React and I use it on a daily \nbasis at my current workplace.  \nReact-Native comes with [**Jest**](https://facebook.github.io/jest/), a test framework that shines \nwhen coupled with React.  \nRelated dependencies: \n```json\n\"babel-jest\": \"18.0.0\",\n\"babel-preset-react-native\": \"1.9.1\",\n\"react\": \"~15.4.0-rc.4\",\n\"react-native\": \"0.41.2\",\n\"react-test-renderer\": \"~15.4.0-rc.4\"\n```\n\n#### State management: MobX\nI started this project using [**Redux**](http://redux.js.org/docs/introduction/) for handling the state management, but after a while I noticed that, being a simple project, it was slowing me down, so I seized the opportunity and implemented [**MobX**](https://github.com/mobxjs/mobx).  \n[This is not the first time I used MobX](https://github.com/GPMDP/google-play-music-desktop-remote), \nbut this time I tried using its `provider` and its `inject` a là Redux, and I really liked \nit!   \nRelated dependencies: \n```json\n\"babel-preset-react-native-stage-0\": \"1.0.1\", // Enables the decorators support\n\"mobx\": \"3.1.0\",\n\"mobx-react\": \"4.1.0\",\n```\n\n#### Static type checking: Flowtype \n[**Flowtype**](https://flowtype.org/) is a static type checker.  \nI've been using it for a while now and it saved me A LOT of time. I can't praise it enough.  \nI strongly suggest you to give it a try, or better still stick with it for some days for seeing its real benefits.\nRelated dependencies: \n```json\n\"flow-bin\": \"0.37.4\",\n\"babel-plugin-transform-flow-strip-types\": \"6.22.0\",\n```\n\n#### Linting: ESlint and Prettier\nI wasted way too much time in the past tweaking my ESlint configuration.  \nNow I'm super happy with [a minimal ESlint config](https://github.com/mmazzarolo/eslint-plugin-react-app) and with \n[**Prettier**](https://github.com/prettier/prettier), which takes care of all the code styling of \nmy application.  \nRelated dependencies: \n```json\n\"babel-eslint\": \"7.1.1\",\n\"eslint\": \"3.15.0\",\n\"eslint-plugin-prettier\": \"2.0.0\",\n\"eslint-plugin-react-app\": \"1.0.2\",\n\"prettier\": \"0.16.0\",\n```\n\n#### Importing from absolute path: babel-plugin-module-resolver\nI use [**babel-plugin-module-resolver**](https://github.com/tleunen/babel-plugin-module-resolver) \nfor importing files from the `src` path. \nRelated dependencies: \n```json\n\"babel-plugin-module-resolver\": \"2.5.0\", \n```\n\n#### Playing audio files: react-native-sound\nIf you need to play an audio file in a React-Native application you must use some kind of external \nlibrary at the moment because it is not (yet) implement in React-Native out of the box.  \nTo me [**react-native-sound**](https://github.com/zmxv/react-native-sound) is the most \ncomplete library at the moment and it worked fine on this application.  \nP.S.: I'm pulling the library directly from its Github Master branch because the latest version\navailable on NPM does not support React-Native 0.40 yet.  \nRelated dependencies: \n```json\n\"react-native-sound\": \"git+https://github.com/zmxv/react-native-sound.git\",\n```\n\n#### Simple animations: react-native-animatable\nFor simple animations [**react-native-animatable**](https://github.com/oblador/react-native-animatable) \nis the de-facto standard on React-Native.  \nRelated dependencies: \n```json\n\"react-native-animatable\": \"1.1.0\",\n```\n\n## Project structure \nThe structure of the application and all its files are the following:\n```javascript\nsrc\n ├── index.js // The app entry point\n │\n ├── assets // audio \u0026 fonts that must be linked in the app\n │\n ├── components\n │   ├── CustomText.js // A wrapper on the text used in the entire app (responsive + custom font)\n │   ├── Tile.js // The tile component used in the home screen and in the playground\n │   └── TouchableView.js // A cross-platform helper view with a touchable behavior\n │\n ├── config\n │   ├── colors.js // Colors (the available tiles colors, etc...)\n │   ├── env.js // Platform specific variables (IS_ENV_DEVELOPMENT, IS_ANDROID, etc...)\n │   ├── metrics.js // App metrics (DEVICE_WIDTH, TILE_SIZE, etc...)\n │   └── timings.js // Timing specific variables (TIME_LIMIT_MS, etc...)\n │\n ├── containers\n │   ├── App // The root app screen, routing is handled here\n │   ├── Endgame // The post-game screen (with the score and restart button)\n │   ├── Home // The home screen (with the start game button)\n │   └── Playground // The screen where the game runs\n │       ├── Board // The board game, renders the tiles\n │       ├── BoardTile // A Tile with Board-specific behaviors\n │       └── TimeBar // The top-bar with that shows the remaining time\n │\n ├── images // The app images\n │\n ├── services\n │   └── audio.js // Simple wrapper over react-native-sound \n │\n ├── stores // MobX stores\n │   ├── game.js // All the app logic is handled here (Board setup, scoring, etc...)\n │   └── router.js // A super simple router \n │\n ├── types // Flowtype types\n │\n └── utils\n     ├── boardUtils.js // Board setup utils (getRandomTilePosition, getRandomNumber, etc...)\n     ├── colorUtils.js // Color utils (getDifferentLuminance, etc...)\n     └── timeUtils.js // Simple timing helpers (mostly wrappers over setTimeout)\n```\nThe project structure I used here may seem over-engineered at first, but this setup paid off almost \ninstantly (continue below).\n\n## A bit of history\nI started working on this game with a totally different idea in my mind: I wanted to play a bit with \n[React-Native ART](https://github.com/facebook/react-native/tree/master/Libraries/ART), and \n[if you take a closer look at the commit history](https://github.com/mmazzarolo/tap-the-number/commit/9ab03803babecd38d4e320782a0c826623241c4b) \nyou'll see that at some point I even implemented an [animation similar to the Twitter's heart one](http://browniefed.com/blog/react-native-how-to-create-twitter-exploding-hearts/) \nwhen tapping a Tile.  \nUnfortunately I had to drop the idea because React-Native suffers from small lags when you run \nmultiple animations (in my case when tapping on tiles rapidly), but it seems that something is \nchanging thanks to [Native Driver](https://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html).  \nAt the time, I had already created the game engine and some components, so, instead of throwing \naway the project, I decided to turn it into this game.\n\n## Interesting stuff\n### Config files are your friends\nI tried to gather all the variables that describe the app behaviour in the `config` directory.  \nThe `config/metrics.js` for example exposes all the application dimensions:  \n```javascript\n/* @flow */\nimport { Dimensions, Platform } from 'react-native';\n\nconst IS_ANDROID = Platform.OS === 'android';\nconst { height, width } = Dimensions.get('window');\n\nconst ANDROID_STATUSBAR = 24;\nconst DEVICE_HEIGHT = IS_ANDROID ? height - ANDROID_STATUSBAR : height;\nconst DEVICE_WIDTH = width;\n\nconst TILE_SIZE = DEVICE_WIDTH * 0.28;\nconst TILE_SHADOW_DEPTH = 6;\nconst TILE_BORDER_RADIUS = TILE_SIZE * 0.1;\n\nconst BOARD_MARGIN = 20;\nconst BOARD_HEIGHT = DEVICE_HEIGHT * 0.96;\nconst BOARD_WIDTH = DEVICE_WIDTH;\n\nexport default {\n  DEVICE_HEIGHT,\n  DEVICE_WIDTH,\n  TILE_SIZE,\n  TILE_SHADOW_DEPTH,\n  TILE_BORDER_RADIUS,\n  BOARD_MARGIN,\n  BOARD_HEIGHT,\n  BOARD_WIDTH,\n  TIME_BAR_HEIGHT: DEVICE_HEIGHT * 0.02,\n};\n``` \nThis setup, coupled with hot-reloading, came in super handy even for a simple game like this, \nbecause I've been able to concentrate most of the variables I needed to change in a single \ndirectory.  \n\n### Relative dimensions  \nReact-Native is not a game-development framework and one of the things you'll miss a lot, if you'll \never try to build a game with it, is a proper way to handle the camera/viewport.  \nTap The Number is a simple game though, so using relative dimensions is more than enough for its \nuse case.  \nUsing relative dimensions means that instead of defining the dimensions of the views using the \nlogical pixel units (which is the default unit of React-Native) you should define the dimensions \nrelative to the device size (or to their parents).  \nFollowing this approach will make your game resize automatically on bigger/smaller devices (even \non tablets!) but it also has many drawbacks in my opinion:\n- The more the app grows, the more dimensions you'll have to define and keep track of;  \n- Handling screen rotation might become difficult (Tap The Game works only in portrait mode);  \n- If you're using some native components you'll might not be able to resize them;  \n\nSpeaking about dimensions, In Tap The Number I did something that I'm bit ashamed of: I tied the game \nengine to the device size, as you can see in `getRandomTilePosition` of `utils/boardUtils.js`:\n```javascript\n/**\n * Gets a random tile position (making sure that it does not overlap another tile).\n * @param {Array\u003cTile\u003e} blacklist - An array the already placed tiles.\n * @return {Object} An object with the x and y coordinates of the tile.\n */\nconst getRandomTilePosition = (board: Array\u003cTile\u003e): { x: number, y: number } =\u003e {\n  const position = {};\n  const boardOriginX = metrics.BOARD_MARGIN;\n  const boardOriginY = metrics.BOARD_MARGIN;\n  const boardWidth = metrics.BOARD_WIDTH - metrics.BOARD_MARGIN;\n  const boardHeight = metrics.BOARD_HEIGHT - metrics.BOARD_MARGIN;\n  // Gets random tile positions until it finds a position that does not overlap another tile.\n  // The while loop is a bit scary but we don't have to worry: we're using relative metrics\n  // and we're limiting the number of tiles.\n  while (true) {\n    const randomX = random(boardOriginX, boardWidth - metrics.TILE_SIZE);\n    const randomY = random(boardOriginY, boardHeight - metrics.TILE_SIZE);\n    if (_isPositionAvailable(randomX, randomY, board)) {\n      position.x = randomX;\n      position.y = randomY;\n      break;\n    }\n  }\n  return position;\n};\n```\nWhen the app initializes the game board the above function searches for available tiles positions \nusing a while-loop.  \nI'm aware that this function [can be optimized in many different ways](http://jsfiddle.net/fZtdt/13/), \nand that it can break if the device has a weird width / height ratio, but considering that:  \n- I'm targeting only iOS (\u003e= iPhone 5);  \n- The maximum number of tiles on the board is 6;  \n- The tile size is 28% of the device width;     \n\n...I decided to opt for this solution for the sake of simplicity (and for keeping the code readable, but feel free to correct me if it seems too unreasonable).    \n\nOh! One last thing: keep in mind that the React-Native `\u003cText /\u003e` component does not scale the text \nbased on the device size.  \nThis is one of the reasons I always use a custom wrapper over the built-in `\u003cText /\u003e` component, \nso that I can change its default behaviour/font/color easily.  \nTo get the scaled font size you should do the following: \n```javascript\nconst scaledFontSize = Math.round(fontSize * metrics.DEVICE_WIDTH / 375);\n```\n(Thanks to Facebook F8 app for the trick).  \n\n\n### MobX (and src/utils) tips\nLet's be honest here: I love Redux and I use it daily, but for simple applications like this, MobX is \nmore then enough.  \nIn fact, if you're not interested in middlewares or in having a centralized pattern for dispatching \nactions, in my opinion MobX might be a better choice than Redux.  \nOne thing I just recently started using with MobX is the `provider` + `inject` combo, which provides \na nice abstraction on connecting components to the store (in a similar way to the Redux `mapStateToProps`).  \nAnother thing I've found really useful has been using abusing the `src/utils` and `src/services` folders: \nfrom my experience the MobX actions tend to get cluttered, so I prefer to keep them easy to read \nby minimizing the verbosity of the code.  \nTalking about my utils functions... I'm a bit sad because they are not super pure -- they use the \n`src/config` files internally -- but if your application is bigger than mine I'd advise you to make \nthem accepts those configs as parameters to make them testable.  \n\nP.S.: use `@computed` values whenever possible if you need to compute an observable value \n (just like Redux's selectors).  \n\n### Animations, part 1: The animations library I used  \nIn Tap The Number I animated the components in three different ways:\n\n##### React-Native-Animatable\n`react-native-animatable` is a wrapper of React-Native Animated API which exposes many simple \nanimations and allows you to use them both programmatically and in a declarative way, embracing \nReact's philosophy.  \nIf you don't need complex animations, interpolations or timings, `react-native-animatable` is a solid \nchoice.  \n\n##### React-Native Animated API\nI used the Animated API for the TimeBar animation in `src/containers/Playground/TimeBar`: I wanted \nto achieve an effect that required some manual tweaking, and the Animated API is the most flexible \none of the lot.  \nSpecifically, I wanted to animate the TimeBar width and the TimeBar color from grey to red.  \n```javascript\ntype State = {\n  animateValue: any,\n};\n\nexport default class TimeBar extends Component\u003cvoid, {}, State\u003e {\n  state = {\n    animateValue: new Animated.Value(timings.TIME_LIMIT_MS),\n  };\n\n  componentDidMount() {\n    Animated.timing(this.state.animateValue, {\n      duration: timings.TIME_LIMIT_MS,\n      easing: Easing.linear, // No easing\n      toValue: 0,\n    }).start();\n  }\n\n  render() {\n    // Animate the TimeBar color from grey to red, starting when there are left only 12 seconds\n    const backgroundColor = this.state.animateValue.interpolate({\n      inputRange: [0, timings.TIME_LIMIT_MS * 0.4, timings.TIME_LIMIT_MS],\n      outputRange: ['rgba(255,0,0, 1)', 'rgba(0,0,0, 0.3)', 'rgba(0,0,0, 0.3)'],\n    });\n    // Animate the TimeBar width from DEVICE_WIDTH to 0 in TIME_LIMIT_MS (which currently is 30 seconds)\n    const width = this.state.animateValue.interpolate({\n      inputRange: [0, timings.TIME_LIMIT_MS],\n      outputRange: [0, metrics.DEVICE_WIDTH],\n    });\n    return (\n      \u003cView style={styles.container}\u003e\n        \u003cView style={[styles.content, { width, backgroundColor }]} /\u003e\n      \u003c/View\u003e\n    );\n  }\n}\n```  \n\n##### React-Native LayoutAnimation  \nLayoutAnimation is a powerful way to animate the transitions between layout changes without the \nneed to specify the animation behaviour.  \nYou just call `LayoutAnimation.spring()` (or [one of the other available configurations](https://facebook.github.io/react-native/docs/layoutanimation.html)) before calling `setState` and \nReact-Native will animate the component that has been subjected to a layout change.  \nYou can see an example of it in `src/components/Tile.js`, where I animated the tile depth by calling \n`LayoutAnimation.spring()` just before `this.setState({ isTouched: true });`.   \nThe drawback of LayoutAnimation is, as you may have already guessed, that it provides much less \ncontrol than the other animation alternatives.  \n\n### Animations, part 2: React and animations  \nI'll go straight to the point: **In my opinion animations don't get along nicely with React (and \nReact-Native), and they never will**.  \nI know that it might be a controversial opinion, but having tried many different libraries both on React-Native (and on React, for example React-Motion), I still think that animations move against the declarative React pattern.\nDon't get me wrong here, you can still achieve a clean code while using small animations, but when \nyou'll start linking animations one after another you'll end up doing it programmatically:  \n```javascript\n_handleButtonPress = async () =\u003e {\n  this.setState({ disableAllButton: true }); // Prevent pressing buttons while animating\n  if (this._headerRef \u0026\u0026 this._bodyRef) { // Animates out header and body\n    await Promise.all([\n      this._headerRef.fadeOutLeft(400), \n      this._bodyRef.fadeOutRight(400)\n    ]);\n  }\n  this.props.navigateToNextScreen(); // Animations are ended: move to the next screen\n};\n```\nOtherwise you'll have to keep track of the animations state in your component's state (e.g: \n`this.setState({ isContainerFadingOut: true })` which adds a nice amount of unneeded complexity \nto your component's lifecycle.  \n\n…And last opinion: I don’t think that the clashing of the imperative nature of animations with  the React's \"declarativeness\" can be solved easily (I’ll be super happy to be proved wrong though): after all, animating **IS** hard.\n\n### Android support\nI was planning to release this game on Android too at first, but I had some issue that I've not been \nable to solve easily.  \nThe most annoying one was my inability to use custom fonts on Android: I tried linking the assets \nfolder using `react-native link` (which works perfectly on iOS) and adding the fonts manually, but \nit seems that [some fonts don't link correctly at all](https://github.com/facebook/react-native/issues/7301), while other works perfectly even when using the first method.  \nThe other issue I faced was a sluggish responsiveness in the animations (specifically when using \nLayoutAnimation), but I guess that I could have easily fixed them by investigating the issue a bit \nmore.\n  \n\n### Thanks to...\nI'm not a creative guy at all: every single thing I used in this application is just a re-iteration \nof stuff I had already seen before.  \nSo, without further ado, here are all the sources I can think of that I used to build this simple \ngame:\n- [Asset Catalog Creator](https://itunes.apple.com/it/app/asset-catalog-creator-free/id866571115?mt=12) \nfor the iOS assets;  \n- [The game background image](https://freeios7.com/download/freeios7.com_apple_wallpaper_geometry-white_ipad_retina.jpg) \nthat I've found on Google Images (sorry, I don't know who the author is);   \n- [freesound.org](https://freesound.org/browse/tags/sound-effects/) for the sound effects;  \n- [This Slack image](https://raw.githubusercontent.com/mmazzarolo/tap-the-number/master/extra/slack-tiles.png) that inspired the design of the tiles;  \n- Mostaza, the startup where I work for all the support (and for the App Store account too!)\n- And all the libraries I already linked and talked about in this post;  \n\n\nForks, comments and critics are warmly welcomed, I just hope that this app example might be helpful to someone sooner or later! :)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmmazzarolo%2Ftap-the-number","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmmazzarolo%2Ftap-the-number","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmmazzarolo%2Ftap-the-number/lists"}