Competing against other players

Ethereum Simulation Game — Part 5 – Competing Against Other Players

Ethereum Simulation Game — Part 5 - Competing Against Other Players
Photo by Raj Tatavarthy from Pexels

The code repo is on Github so it’s public and open to pull requests. If you’re interested in helping out please to contribute!


Recap

In the previous article, I walked through implementing Training and Resting players in the DApp interface. Since then I’ve made some fairly big changes to the game, which include:

  • Segregating the game interface using Tabs.
  • Deploying to the Kovan testnet
  • The ability to compete against other players.

Here’s how I did it…

Interface Update

Up until this point, the supported functionality in the interface was pretty limited. so displaying all functionality on a single page wasn’t much of an issue. However, adding new features to the interface meant restructuring it somewhat.

I decided to segregate the functionality into a few categories using Bootstrap Tabs. The react-bootstrap package came in handy here. Figures 1 and 2 show the interface before and after the new structure.

Figure 1: Interface Before
Figure 1: Interface Before
Figure 2: Interface After
Figure 2: Interface After

Each set of functionality has a separate tab to manage the player. The “Details” tab shows all the information and attributes about your player, the “Training” tab is where you can train specific attributes and rest your player, the “Compete” tab is where you can enlist to compete and play matches against other enlisted players, and the “Results” tab will show the results of your player’s previous matches.

Organising the tabs is very easy using react-bootstrap. Figure 3 shows my Content component, which declares the interface structure.

import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Container, Row, Col, Tabs, Tab} from 'react-bootstrap';
import Connections from './Connections';
import PlayerList from "./PlayerList";
import PlayerDetails from "./PlayerDetails";
import PlayerAttributes from './PlayerAttributes';
import Train from './Train';
import Compete from './Compete';
class Content extends Component {
render() {
return (
<Container>
<Connections />
<Row>
<Col md="3">
<PlayerList />
</Col>
<Col md="9">
<Tabs>
<Tab eventKey="details" title="Details">
<Row>
<Col md="6">
<PlayerDetails />
</Col>
<Col md="6">
<PlayerAttributes />
</Col>
</Row>
</Tab>
<Tab eventKey="training" title="Training">
<Train />
</Tab>
<Tab eventKey="compete" title="Compete">
<Compete />
</Tab>
<Tab eventKey="results" title="Results">
Results Tab
</Tab>
</Tabs>
</Col>
</Row>
</Container>
)
}
}
function mapStateToProps(state){
return {
}
}
export default connect(mapStateToProps)(Content);
view raw Content.js hosted with ❤ by GitHub

Lines 21 to 41 show how easy it is to declare Tabs and the content within them.


I also removed the “Create Player” form from the main page and replaced for a modal which is shown when the button under the player list is clicked, shown in figure 4.

Figure 4: Create player modal
Figure 4: Create player modal

Deploying To Public Testnet

Testing a DApp on a local Blockchain is very different from testing it on a public one. For a start, you get used to the speedy response times and the immediate transactions. That, unfortunately, doesn’t happen on public testnets or the mainnet. This means your DApp needs to monitor the status of each transaction it executes on the Blockchain and provides sufficient feedback to the user.

Not only that, but if you’re subscribing to events in your DApp, listening for things like “Transfer” events, or in this game, “Train”, “Rest” and “MatchPlayed” events; the events won’t be pushed to your DApp through HTTP. No matter how many listeners you put in place, if you are connecting to the Blockchain with an HTTP connection, event listeners will never fire.

To catch the events, you need to maintain a Websocket connection, which I write about in this article. Fortunately, Infura offers HTTP and Websockets, so it wasn’t an issue, it’s just a little frustrating making sure you’re keeping both connections stored in parallel in the Redux store. Even more frustrating is the fact that the Websocket API is very limited compared with the HTTP one. This means that transactions need to be sent through HTTP, and listeners set up through Websocket.

Figure 5 shows my subscriptions.js file.

import { loadOwnedPlayers, loadWalletDetails, unselectPlayer, loadSelectedPlayer, checkIfPlayerEnlisted } from "./interactions";
import { playerFinishedResting, playerFinishedTraining, playerCreated, matchFinished } from "./actions";
export const subscribeToTransferEvents = async (dispatch, tennisPlayer, tennisPlayerSocket, account) => {
tennisPlayerSocket.events.Transfer({filter: {to: account}})
.on('data', async function(event){
await loadOwnedPlayers(dispatch, tennisPlayer, account);
dispatch(playerCreated());
})
.on('error', console.error);
}
export const subscribeToTrainingEvents = async (dispatch, tennisPlayer, tennisPlayerSocket, playerId) => {
tennisPlayerSocket.events.Train({filter: {playerId: playerId}})
.once('data', async function(event) {
await loadSelectedPlayer(dispatch, tennisPlayer, tennisPlayerSocket, playerId);
dispatch(playerFinishedTraining());
})
.on('error', console.error);
tennisPlayerSocket.events.Rest({filter: {playerId: playerId}})
.once('data', async function(event) {
await loadSelectedPlayer(dispatch, tennisPlayer, tennisPlayerSocket, playerId);
dispatch(playerFinishedResting());
})
.on('error', console.error);
}
export const subscribeToMatchEvents = async (dispatch, tennisPlayer, tennisPlayerSocket, playerId) => {
tennisPlayerSocket.events.Enlist({filter: {playerId: playerId}})
.once('data', async function(event) {
await checkIfPlayerEnlisted(dispatch, tennisPlayer, playerId);
})
.on('error', console.error);
tennisPlayerSocket.events.Delist({filter: {playerId: playerId}})
.once('data', async function(event) {
await checkIfPlayerEnlisted(dispatch, tennisPlayer, playerId);
})
.on('error', console.error);
tennisPlayerSocket.events.MatchPlayed({filter: {playerId: playerId}})
.once('data', async function(event) {
await loadSelectedPlayer(dispatch, tennisPlayer, tennisPlayerSocket, playerId);
dispatch(matchFinished(event.returnValues));
})
.on('error', console.error);
}
export const subscribeToAccountsChanging = async (dispatch, web3, web3Socket, tennisPlayer) => {
window.ethereum.on('accountsChanged', function (accounts) {
loadWalletDetails(dispatch, web3, web3Socket, tennisPlayer);
unselectPlayer(dispatch);
});
}
view raw subscriptions.js hosted with ❤ by GitHub

On line 29 the function subscribeToMatchEvents is declared, where “Enlist”, “Delist” and “MatchPlayed” events are listened to.

Taking a look at line 41, notice how tennisPlayerWebsocket variable is used, representing a Websocket connection to the TennisPlayer smart contract, to listen for the event. 

It still needs the HTTP connected contract (tennisPlayer) to reload the player after the match is played because Websockets don’t allow function calls to smart contracts. Line 43 shows a call to loadSelectedPlayer with tennisPlayer as a parameter to make the HTTP request.


Playing Matches

The ability to play players against each other is the last major piece of the puzzle when it comes to the interface supporting all of the function calls in the smart contract backend.

As I mentioned in part 2, players must be enlisted to compete and must meet certain attribute requirements to do so. Once enlisted, the user should be able to choose another enlisted player for their player to compete against, and await the result and subsequent XP gains.

Figure 6 shows the “Compete” tab when the user’s selected player is enlisted.

Figure 6: “Compete” tab

The “Find Opponent” section lists all the other enlisted players to compete against. (At the moment, the function that retrieves these from the blockchain gets every player. This will need to be changed as the player list grows to enable pagination, or even searching.) The bottom section enables the user to challenge a player using the player ID directly instead of selecting from the list.

Figure 7 shows how the matches are played. The player with ID 2 is the slightly better player, so when the player with ID 3 challenges in the first match, player 3 loses. When player 2 challenges player 3 in the second match, player 2 wins again.


Next Steps

There are several things wrong with the game that need to be addressed:

  • The “Results” page isn’t done yet.
  • The interface is disgusting. It doesn’t even look like a game. The feedback mechanisms need to be sharpened, and it needs a colour scheme, branding and artwork.
  • After training, competing and resting, eventually, your player either runs out of XP or runs out of condition. There needs to be a time-based mechanism to replenish one, most likely the Condition. As well as this, I might add a paid “Super Rest” and call it something like “Physio Room” where it costs ETH to supercharge your player’s condition.
  • The match playing algorithm in the Smart Contract is very primitive. The player with more attribute points always wins.
  • Training player attributes should become more difficult as that attribute gets better. In other words, the higher the attribute level, the more difficult it should be to level up.

Initially, I’ll finish off the results page, and then after that, I’m not too sure. Design is not my strong-point, so coming up with fancy branding will likely be put off until I get the motivation to do it. I think that’s what is holding the game back from going to the next level. 

The code repo is on Github so it’s public and open to pull requests. If you’re interested in helping out please to contribute! Here is the repo: alexroan/tennis-manager


Further Reading

If you’re interested in Blockchain Development, I write tutorials, walkthroughs, hints, and tips on how to get started and build a portfolio.

Check out some of these resources:Blockchain Development Resources To Follow Right Now

Also Read

Default image
Alex Roan
Blockchain Developer, writer. https://alexroan.co.uk

Wanna learn about Crypto?

Subscribe to our weekly newsletter

Leave a Reply