Whack-A-Mole is a popular arcade redemption game invented in 1976 by Aaron Fechter of Creative Engineering, Inc. We have all played this game at least once when we go to arcade game shops with friends during our childhood. We attempted to make it "multiplayer" by recording the score and compare among our group of friends. What if we can make it truly "multiplayer" and real-time? Introducing Holey Moley, the true multiplayer Whack-A-Mole game implemented with modern technologies. Maybe it's time to reconnect with that long-lost friend over a game of Holey Moley? Play it NOW
Our group want to create something that is retro with a taste of modern technology. The aim of Holey Moley is to bring nostalgic feeling with more fun from the classic game of Whack-A-Mole. As such, the game can bridge the generation gap, bringing family members of different generations together to play a game that each one of them knows.
As for the above concept, we decided to implement a real-time online multi-player version of Whack-A-Mole that can be played with smart phones. Inside Holey Moley, you can battle other players in a Whack-A-Mole showdown where you can use items to affect your opponent. Experience and Gold can be earn through matches for acquiring more powerful items. This Gold-for-special-powers system is designed to motivate the players to keep playing to accumulate Gold.
The different components that make up our awesome Holey Moley
This is where users can register for new accounts, or log in using their credentials.
This is where all the purchases, equipment of items, display of user info are made. Users can also start games via the game menu.
This is where the actual game takes place, objective of the game is score higher, or out-live your opponents during the game. Users can hit mole to get more points, and energy to use items. These items enhances their advantages or sabotages their opponents.
- Users need to be able to join and play with another player in real time
- Users need to transfer and receive data instantaneously during game
- Users need a system (profiles, items, adds-on, etc) that is just right in term of complexity to have fun but not too steep learning curve for new players
- Users want a game that is aesthetically pleasing and responsive
- Concept: Modern Whack-A-Mole game that is way more fun to play but still easy and intuitive enough as its classic counterpart
- Functionality: The game should have a players system with levels and gold for acquiring items to use in game. The gameplay should be able to handle real-time user IO.
- IO Operations: The game needs to be responsive and reliable
- Logic: The game should have logical and modularized implementation
- Responsiveness: The game should be very responsive to user interactions
- Reliability: The game should be reliable and clean of bugs
- Availability: The game should be easily accessible and available all the time
- Security: The game should be secure. Users' data must be protected. Third-party services should be trustable
- Cost: The game should cost little to nothing
ID | HoleyMoley_registeringNewAccount |
---|---|
Name | Registering Account |
Objective | Registering new account to start playing Holey Moley |
Pre-Condition | User must go to our landing page |
Post-Condition | Success a. Account successfully created b. User redirected to lobby Failure a. Duplicate account b. Registration fail |
Actors | Primary a. Player Secondary a. Backend Server |
Trigger | "Register" button on landing page |
Normal Flow | 1. User click on "Register" button and the prompt appears 2. User fill in all required information (e.g. username, password) 3. Information checking by server 4. New account successfully registered |
Alternative Flow | Scenario 1: 1. User enter information that doesn't satisfy conditions (e.g. duplicate username, not secure password) 2. Visual Warning on the page 3. User re-enter the information Scenario 2: 1. Server Error 2. User is signified to try again later |
Interacts With | Landing page, User checking use case |
Open Issues | 1. How to signified to admin in case of server error? 2. User experience of filling information |
ID | HoleyMoley_loginToGame |
---|---|
Name | Login Account |
Objective | Login to start playing Holey Moley |
Pre-Conditions | Success a. Account successfully logged in b. User is redirected to lobby Failure: a. Wrong credentials b. Login fails |
Actors | Primary a. Player Secondary a. Backend Server |
Trigger | "Login" button on landing page |
Normal Flow | 1. User click on "Login" button and the prompt appears 2. User fill in all requested information (e.g. username and password) 3. Information checking by server 4. Login successful |
Alternative Flow | Scenario 1: 1. Wrong credentials 2. Visual warning on the page 3. User re-enter the information Scenario 2: 1. Server Error 2. User is signified to try again later |
Interacts With | Landing Page |
Open Issues | 1. How to signify admin in case of server error? 2. User experience of filling up information |
ID | HoleyMoley_buyingPowerUps |
---|---|
Name | Shopping |
Objective | Getting power-ups to use inside game |
Pre-Conditions | Player must have earned enough gold and have at least required level to buy the desired item |
Post-Conditions | Success a. Payment went throught and item added to inventory Failure: a. Failed to pay or server error |
Actors | Primary a. Player |
Trigger | "Shop" button in Game Lobby |
Normal Flow | 1. Player goes inside shop by pressing "Shop" in lobby screen 2. Player looks for desired item by searching/filtering/sorting 3. Player presses on the item Player receives item in Inventory |
Alternative Flow | Scenario 1: 1. Not enough gold or level to buy 2. Player is signified that he/she doesn't have enough gold Scenario 2: 1. Server Error 2. Player is signified that the transaction didn't go through and asked to try again later |
Interacts With | User login |
Open Issues | 1. How to prevent user from hacking gold? 2. How to notify admin when there's server error? |
ID | HoleyMoley_gamePlay |
---|---|
Name | Playing Holey Moley |
Objective | Getting most points before time's up or outliving your opponent |
Pre-Conditions | There must be at least 2 players |
Post-Conditions | Success a. Time's up and winner is determined Failure a. Player leaves game before time's up |
Actors | Primary a. Players Secondary a. Backend server providing real-time data |
Trigger | "Random Match" button on game mode choosing screen |
Normal Flow | 1. Both players have equipped the desired power-ups and searching for random match 2. 2 Players will be matched against each other 3. Players can accumulate energy to use their power-ups (e.g. Spawn bombs to the opponent, freeze the opponent) to give them advantages 4. Time's up Winner is determined. Experience and gold are earned |
Alternative Flow | Scenario 1: 1. Player quits during them game 2. The other player is declared winner 3. The game is discontinued and the game room destroyed Scenario 2: 1. Server Error 2. No penalty to players 3. Users are signified to try again later |
Interacts With | User login, Joining game |
Open Issues | 1. How strong the connection needs to be? 2. What is the penalty for in-game quitting? |
Clients (Users on Web Browsers or Phone App) communicate with a server in 2 ways:
- HTTP: For any users' data related requests like login/logout, purchases of items, equip items
- WebSocket: For in game interactions like signaling score update, usage of items
The server is written in NodeJS using a minimal frameworks called Express. The database used is MongoDB. The web application is in HTML, CSS, and Javascript without any external library. The mobile application is written in Java with Android Studio. For realtime communication, Socket.io is used.
Below are the communication models:
The web app is structure based on the Model/View/Controllers pattern. Explanation for each folder is below:
- /index.js: entry point to start the application
- /package.json: records of dependencies and app scripts
- /models: data models for all users' data
- /api: implementation of all API endpoints
- /controllers: implementation of views rendering
- /middlewares: middlewares to ensure endpoints security
- /routes: routing actual endpoints to all implementation
- /socket: implementation of web socket
- /views: templates for views
- /public: all assets, stylesheets, and client-side javascript files served to users
- /test: all tests for application's components
Our choice of this backend technology is because of its asynchronous nature, which makes it able to handle tens of thousands of concurrent connections (this will be further elaborated in Concurrency Section). The fact that our game does not need much CPU-intensive computations but rather than I/O supports makes NodeJS good option.
We chose MongoDB as out database because of the flexible nature of a NoSQL database. Since we don't have much time to carefully craft the models but rather do it on the go as we implement the app, this flexibility will help our database to be change-tolerant and save our time as we carry out migrations
Socket.IO enables real-time bidirectional event-based communication. It works on every platform, browser or device, focusing equally on reliability and speed.
From Microsoft Office, Yammer, Zendesk, Trello... to hackathon winners and little startups.
One of the most powerful JavaScript frameworks on GitHub, and most depended-upon npm module.
-- Socket.io statement
Socket.io is a Node.js module, so it runs in-process with Node. On the client side, it provides a library clients use to connect to the server.
In authentication, when the user successfully logs in using their credentials, a JSON Web Token will be returned and must be saved locally (typically in local or session storage, but cookies can also be used), instead of the traditional approach of creating a session in the server and returning a cookie.
This is a stateless authentication mechanism as the user state is never saved in server memory. The server's protected routes will check for a valid JWT in the Authorization header, and if it's present, the user will be allowed to access protected resources.
-- Wikipedia
We chose JSON Web Token to implement our users' logging as it's a simple but secure, and easy to implement on other platforms (such as the Android Native App)
We choose to not use any third-party library but rather browser native implementation because that way, our game will be more independent of any front-end vendor that constantly change.
In Frontend Development, the only constant is change
-- Anonymous Author
Invest in apps, not ops. Heroku handles the hard stuff — patching and upgrading, 24/7 ops and security, build systems, failovers, and more — so your developers can stay focused on building great apps.
Choose Heroku for the same reasons disruptive startups do: it’s the best platform for building with modern architectures, innovating quickly, and scaling precisely to meet demand.
-- Heroku statement
We chose Heroku as our hosting because of its simplicity and zero-cost. Heroku saves us time from devOps as we can easily deploy with a single-line command. Moreover, Heroku has supports for HTTPS out-of-the-box as we care for our users' privacy and security.
/app/src/main/java/zouyun/com/example/whackamole: contains all our java codes for the activities
- WelcomeActivity.java: default page on open, for users to login or register
- TabsActivity.java: game lobby that implements fragments for each page
- Game.java: fragment that allows user to choose the game mode
- Inventory.java: fragment that allows user to view their inventory and equip skills
- InventoryAdapter.java: extends BaseAdapter to populate the inventory
- Shop.java: fragment that allows user to view the shop and buy skills
- ShopAdapter.java: extends BaseAdapter to populate the shop
- Profile.java: fragment that shows user their profile
- Parser.java: a class to handle parsing of JSONArrays to Java arrays
- GameActivity.java: activity where the user plays the game
/app/src/main/res: contains our resources for the game
- /drawable: contains assets in .png and .xml
- /layout: contains activity layouts
- /values: contains colour, dimensions, styles and string values
- /mipmap-*: contains our app logo
We chose to implement using vanilla Java with Android Studio rather than any third-party implementation like Unity as it's simple but elegant, enabling us to achieve more understanding and customizations.
In this section, we will go through how to start the app, the tests implementation, and rationale for each test
All our unit tests for web application is implemented using
Mocha and
Chai.
Mocha is a feature-rich JavaScript test framework running on
Node.js and in the browser, making asynchronous testing
simple and fun. Mocha tests run serially, allowing for
flexible and accurate reporting, while mapping uncaught
exceptions to the correct test cases.
Chai is a BDD / TDD
assertion library for Node and the browser that can be
delightfully paired with any Javascript testing framework.
The web app can be started with our preconfigured npm
scripts.
Note: Before starting the server, there will be a few
environment variables that need to be set. This can be
achieve in dev mode using a .env
file base on the sample
.env.sample
# script for starting server
npm start
# script for starting server in dev mode
npm run dev
# script for running test suite
npm run test
npm run test:watch # with watch mode
Since our architecture is modularized, we can easily carry out testing on each unit of implementations to cover all out implementations. For the server and web app, we tested the all our api endpoints, and socket implmentation. The below screenshot is 100% test coverage
The unit test cases for Android App are implemented using Android Instrumented Unit Tests.
Instrumented unit tests are tests that run on physical devices and emulators, and they can take advantage of the Android framework APIs and supporting APIs. Such unit tests are more reliable due to the fact that the they run on an instance of Android on a device or an emulator with access to the real device and its resources.
However, the speed of execution is noticeably slower.
Before running the unit test cases, there are some run configurations to edit.
- Click Run > Edit Configurations from the main menu
- Click Add New Configurations and select Android Tests
- In the Name field, enter a name for your new configuration (eg.MyTest).
- In the Module dropdown menu, select app
- For Test, select All in Module
- For Deployment Target Options > Target, I suggest selecting Open Select Deployment Target Dialog
- Click OK
Now you can simply run the unit test cases by clicking Run and select the deployment target, which can be a connected Android device or an emulator.
Note: Please ensure that the screen of the device is on at all time when running the test cases.
Javascript offers an asynchronous behavior by default
that sets it apart from other programming languages. In
asynchronous programs, you can have two lines of code (L1
followed by L2), where L1 schedules some task to be run in
the future, but L2 runs before that task completes. For
example, in a restaurant, if you order a steak, and then I
order a glass of water, I will likely receive my order
first, since it typically doesn't take as much time to serve
a glass of water as it does to prepare and serve a steak.
Note that asynchronous does not mean the same thing as
concurrent or multi-threaded. JavaScript can have
asynchronous code, but it is generally single-threaded.
All operations in Javascript are thread-safe since Javascript
programs are single-threaded, making it impossible for any 2
statement to execute at the same time.
Nevertheless, other than your code, everything else runs in
parallel which might cause problem at run-time. To tackle
this issue, we embrace Functional
Programming,
encapsulating our operations into blocks that does not
affect the global environment (making them as "pure" as
possible).
This is the secret sauce to NodeJS's high performance
despite the fact that NodeJS applications are
single-threaded. Much like Javascript in the browser, NodeJS
utilizes a fixed-size C++ thread pool behind the scene to
handle all blocking I/O tasks.
NodeJS server will accept requests from clients with a
single-threaded event loop and dispatch the task to C++
worker threads for processing, freeing the main event loop
to accept new requests.
Thanks to the asynchronous nature of Javascript, our browsers like Google Chrome, Mozilla Fire fox can easily executes multiple task while waiting for users' input but still are very responsive to new user inputs/requests. This is achieved by decoupling main thread listeners from worker threads which do all the processing.
The game requires multiple tasks to execute simultaneously, e.g. moving the mole up and down while informing server the special power employed. Since there are a lot of animations in fast pace, it is important to utilize resources effectively, hence multithreading is used to improve performance and avoid busy waiting. Multithreading is especially used to make sure that certain action is triggered only when certain requirement is fulfilled. By using wait() and notifyAll(), busy waiting is avoided and computational space is saved. For example, in our game design, we will need the user to accumulate certain amount of energy (mana) in order to enable a special power. Hence every time a mole is popping out, the energy level is checked and if it passes the basic requirement, notifyAll() will wake up every thread that is waiting on the lock, then subsequent action will be performed. The diagram can be seen below.
In the player thread, each method is synchronized to achieve thread-safety so that only one operation in the player thread is able to operate at a time. Since the operations in the player thread are mainly number calculation takes minimal time, starvation is unlikely to happen to affect the performance.
The user interface from the login page to the game lobby are made concurrent using AsyncTask. AsyncTask enables proper and easy use of the UI thread, allowing us to perform background operations and publish results on the UI thread without having to manipulate threads or handlers.
Information is fetched from the server in the background to allow users to log in or register, as well as to get data to populate the shop, inventory and user profile. Since the fetching operation is expected to be short (depending on network connection), AsyncTask would work well for our application.
When the background computation finishes, the onPostExecute() method will run, and the UI elements will be updated. For instance, the game lobby activity will first check for the user’s information in the background. During this short period of checking, a loading spinner will be shown while the inventory’s grid view is hidden. Once the user data is received, the inventory’s grid view is populate and shown to the user.
Team #11
- Kai Lue (1001779)
- Ruth Wong (1001795)
- Stanley Nguyen (1001692)
- Zou Yun (1001831)
SUTD 50.003 Elements of Software Construction Instructors and TAs:
- Prof. Sun Jun
- Prof. Sudipta Chattopadhyay
- Pham Hong Long
- Chen Yuqi