Thursday, September 14, 2017

Procedurally Generated Game Terrain with React, PHP, and WebSockets

Last time, I began telling you the story of how I wanted to make a game. I described how I set up the async PHP server, the Laravel Mix build chain, the React front end, and WebSockets connecting all this together. Now, let me tell you about what happened when I starting building the game mechanics with this mix of React, PHP, and WebSockets…


The code for this part can be found at http://ift.tt/2mVNk1y. I've tested it with PHP 7.1, in a recent version of Google Chrome.


Final image

Making a Farm

"Let's start simple. We have a 10 by 10 grid of tiles, filled with randomly generated stuff."

I decided to represent the farm as a Farm, and each tile as a Patch. From app/Model/FarmModel.pre:

namespace App\Model;

class Farm
{
  private $width
  {
    get { return $this->width; }
  }

  private $height
  {
    get { return $this->height; }
  }

  public function __construct(int $width = 10,
    int $height = 10)
  {
    $this->width = $width;
    $this->height = $height;
  }
}

I thought it would be a fun time to try out the class accessors macro by declaring private properties with public getters. For this I had to install pre/class-accessors (via composer require).

I then changed the socket code to allow for new farms to be created on request. From app/Socket/GameSocket.pre:

namespace App\Socket;

use Aerys\Request;
use Aerys\Response;
use Aerys\Websocket;
use Aerys\Websocket\Endpoint;
use Aerys\Websocket\Message;
use App\Model\FarmModel;

class GameSocket implements Websocket
{
  private $farms = [];

  public function onData(int $clientId,
    Message $message)
  {
    $body = yield $message;

    if ($body === "new-farm") {
      $farm = new FarmModel();

      $payload = json_encode([
        "farm" => [
          "width" => $farm->width,
          "height" => $farm->height,
        ],
      ]);

      yield $this->endpoint->send(
        $payload, $clientId
      );

      $this->farms[$clientId] = $farm;
    }
  }

  public function onClose(int $clientId,
    int $code, string $reason)
  {
    unset($this->connections[$clientId]);
    unset($this->farms[$clientId]);
  }

  // …
}

I noticed how similar this GameSocket was to the previous one I had --- except, instead of broadcasting an echo, I was checking for new-farm and sending a message back only to the client that had asked.

"Perhaps it's a good time to get less generic with the React code. I'm going to rename component.jsx to farm.jsx."

From assets/js/farm.jsx:

import React from "react"

class Farm extends React.Component
{
  componentWillMount()
  {
    this.socket = new WebSocket(
      "ws://127.0.0.1:8080/ws"
    )

    this.socket.addEventListener(
      "message", this.onMessage
    )

    // DEBUG

    this.socket.addEventListener("open", () => {
      this.socket.send("new-farm")
    })
  }
}

export default Farm

In fact, the only other thing I changed was sending new-farm instead of hello world. Everything else was the same. I did have to change the app.jsx code though. From assets/js/app.jsx:

import React from "react"
import ReactDOM from "react-dom"
import Farm from "./farm"

ReactDOM.render(
  <Farm />,
  document.querySelector(".app")
)

It was far from where I needed to be, but using these changes I could see the class accessors in action, as well as prototype a kind of request/response pattern for future WebSocket interactions. I opened the console, and saw {"farm":{"width":10,"height":10}}.

"Great!"

Then I created a Patch class to represent each tile. I figured this was where a lot of the game's logic would happen. From app/Model/PatchModel.pre:

namespace App\Model;

class PatchModel
{
  private $x
  {
    get { return $this->x; }
  }

  private $y
  {
    get { return $this->y; }
  }

  public function __construct(int $x, int $y)
  {
    $this->x = $x;
    $this->y = $y;
  }
}

I'd need to create as many patches as there are spaces in a new Farm. I could do this as part of FarmModel construction. From app/Model/FarmModel.pre:

namespace App\Model;

class FarmModel
{
  private $width
  {
    get { return $this->width; }
  }

  private $height
  {
    get { return $this->height; }
  }

  private $patches
  {
    get { return $this->patches; }
  }

  public function __construct($width = 10, $height = 10)
  {
    $this->width = $width;
    $this->height = $height;

    $this->createPatches();
  }

  private function createPatches()
  {
    for ($i = 0; $i < $this->width; $i++) {
      $this->patches[$i] = [];

      for ($j = 0; $j < $this->height; $j++) {
        $this->patches[$i][$j] =
        new PatchModel($i, $j);
      }
    }
  }
}

For each cell, I created a new PatchModel object. These were pretty simple to begin with, but they needed an element of randomness --- a way to grow trees, weeds, flowers … at least to begin with. From app/Model/PatchModel.pre:

public function start(int $width, int $height,
array $patches)
{
  if (!$this->started && random_int(0, 10) > 7) {
    $this->started = true;
    return true;
  }

  return false;
}

I thought I'd begin just by randomly growing a patch. This didn't change the external state of the patch, but it did give me a way to test how they were started by the farm. From app/Model/FarmModel.pre:

namespace App\Model;

use Amp;
use Amp\Coroutine;
use Closure;

class FarmModel
{
  private $onGrowth
  {
    get { return $this->onGrowth; }
  }

  private $patches
  {
    get { return $this->patches; }
  }

  public function __construct(int $width = 10,
  int $height = 10, Closure $onGrowth)
  {
    $this->width = $width;
    $this->height = $height;
    $this->onGrowth = $onGrowth;
  }

  public async function createPatches()
  {
    $patches = [];

    for ($i = 0; $i < $this->width; $i++) {
      $this->patches[$i] = [];

      for ($j = 0; $j < $this->height; $j++) {
        $this->patches[$i][$j] = $patches[] =
        new PatchModel($i, $j);
      }
    }

    foreach ($patches as $patch) {
      $growth = $patch->start(
        $this->width,
        $this->height,
        $this->patches
      );

      if ($growth) {
        $closure = $this->onGrowth;
        $result = $closure($patch);

        if ($result instanceof Coroutine) {
          yield $result;
        }
      }
    }
  }

  // …
}

There was a lot going on here. For starters, I introduced an async function keyword using a macro. You see, Amp handles the yield keyword by resolving Promises. More to the point: when Amp sees the yield keyword, it assumes what is being yielded is a Coroutine (in most cases).

I could have made the createPatches function a normal function, and just returned a Coroutine from it, but that was such a common piece of code that I might as well have created a special macro for it. At the same time, I could replace code I had made in the previous part. From helpers.pre:

async function mix($path) {
  $manifest = yield Amp\File\get(
    .."/public/mix-manifest.json"
  );

  $manifest = json_decode($manifest, true);

  if (isset($manifest[$path])) {
    return $manifest[$path];
  }

  throw new Exception("{$path} not found");
}

Previously, I had to make a generator, and then wrap it in a new Coroutine:

use Amp\Coroutine;

function mix($path) {
  $generator = () => {
    $manifest = yield Amp\File\get(
      .."/public/mix-manifest.json"
    );

    $manifest = json_decode($manifest, true);

    if (isset($manifest[$path])) {
      return $manifest[$path];
    }

    throw new Exception("{$path} not found");
  };

  return new Coroutine($generator());
}

I began the createPatches method as before, creating new PatchModel objects for each x and y in the grid. Then I started another loop, to call the start method on each patch. I would have done these in the same step, but I wanted my start method to be able to inspect the surrounding patches. That meant I would have to create all of them first, before working out which patches were around each other.

I also changed FarmModel to accept an onGrowth closure. The idea was that I could call that closure if a patch grew (even during the bootstrapping phase).

Each time a patch grew, I reset the $changes variable. This ensured the patches would keep growing until an entire pass of the farm yielded no changes. I also invoked the onGrowth closure. I wanted to allow onGrowth to be a normal closure, or even to return a Coroutine. That's why I needed to make createPatches an async function.

Note: Admittedly, allowing onGrowth coroutines complicated things a bit, but I saw it as essential for allowing other async actions when a patch grew. Perhaps later I'd want to send a socket message, and I could only do that if yield worked inside onGrowth. I could only yield onGrowth if createPatches was an async function. And because createPatches was an async function, I would need to yield it inside GameSocket.

"It's easy to get turned off by all the things that need learning when making one's first async PHP application. Don't give up too soon!"

The last bit of code I needed to write to check that this was all working was in GameSocket. From app/Socket/GameSocket.pre:

if ($body === "new-farm") {
  $patches = [];

  $farm = new FarmModel(10, 10,
  function (PatchModel $patch) use (&$patches) {
    array_push($patches, [
      "x" => $patch->x,
      "y" => $patch->y,
    ]);
  }
);

yield $farm->createPatches();

$payload = json_encode([
  "farm" => [
    "width" => $farm->width,
    "height" => $farm->height,
  ],
  "patches" => $patches,
]);

yield $this->endpoint->send(
  $payload, $clientId
);

$this->farms[$clientId] = $farm;
}

This was only slightly more complex than the previous code I had. I needed to provide a third parameter to the FarmModel constructor, and yield $farm->createPatches() so that each could have a chance to randomize. After that, I just needed to pass a snapshot of the patches to the socket payload.

Random Patches being returned

Random patches for each farm

"What if I start each patch as dry dirt? Then I could make some patches have weeds, and others have trees …"

I set about customizing the patches. From app/Model/PatchModel.pre:

private $started = false;

private $wet {
  get { return $this->wet ?: false; }
};

private $type {
  get { return $this->type ?: "dirt"; }
};

public function start(int $width, int $height,
array $patches)
{
  if ($this->started) {
    return false;
  }

  if (random_int(0, 100) < 90) {
    return false;
  }

  $this->started = true;
  $this->type = "weed";

  return true;
}

I changed the order of logic around a bit, exiting early if the patch had already been started. I also reduced the chance of growth. If neither of these early exits happened, the patch type would be changed to weed.

I could then use this type as part of the socket message payload. From app/Socket/GameSocket.pre:

$farm = new FarmModel(10, 10,
function (PatchModel $patch) use (&$patches) {
  array_push($patches, [
    "x" => $patch->x,
    "y" => $patch->y,
    "wet" => $patch->wet,
    "type" => $patch->type,
  ]);
}
);

Continue reading %Procedurally Generated Game Terrain with React, PHP, and WebSockets%


by Christopher Pitt via SitePoint

10 Scientific Benefits of a Digital Detox [Infographic]

Chances are you’re reading this slouched over a desk, waiting in line for lunch, or just as you’ve laid your head on your pillow. People are using their electronics now more than ever. In fact, recent studies show that 90% of Americans use digital devices for two or more hours each day. Which isn’t...

[ This is a content summary only. Visit our website http://ift.tt/1b4YgHQ for full links, other content, and more! ]

by Web Desk via Digital Information World

How to Create a Reddit Clone Using React and Firebase

React is an awesome JavaScript library for building user interfaces. Since the publishing of Create React App, it has become very easy to scaffold a barebones React application.

In this article, we will be using Firebase along with Create React App to build an app which will function similar to Reddit. It will allow the user to submit a new link which can then be voted on.

Here's a live demo of what we'll be building.

Why Firebase?

Using Firebase will make it very easy for us to show real-time data to the user. Once a user votes on a link, the feedback will be instantaneous. Firebase's Realtime Database will help us in developing this feature. Also, it will help us to understand how to bootstrap a React application with Firebase.

Why React?

React is particularly known for creating user interfaces using a component architecture. Each component can contain internal state or be passed data as props. State and props are the two most important concepts in React. These two things help us determine the state of our application at any point in time. If you are not familiar with these terms, please head over to the React docs first.

Note: You can also use a state container like Redux or MobX, but for the sake of simplicity, we won't be using one for this tutorial.

The whole project is available on Github.

Setting up the project

Let's walk through the steps to set up our project structure and any necessary dependencies.

Installing create-react-app

If you haven't already, you need to install create-react-app. To do so, you can type the following in your terminal:

npm install -g create-react-app

Once you've installed it globally, you can use it to scaffold a React project inside any folder.

Now, let's create a new app and call it reddit-clone.

create-react-app reddit-clone

This will scaffold a new create-react-app project inside the reddit-clone folder. Once the bootstrapping is done, we can go inside reddit-clone directory and fire up the development server:

npm start

At this point, you can go to http://localhost:3000/ and see your app skeleton up and running.

Structuring the app

For maintenance, I always like to separate my containers and components. Containers are the smart components which are contains the business logic of our application and manage Ajax requests. Components are simply dumb presentational components. They can have their own internal state which can be used to control the logic of that component (e.g. showing the current state of a controlled input component).

After removing the unnecessary logo and css files, this is how your app should look like now. We created a components folder and a containers folder. Let's move App.js inside the containers/App folder and create registerServiceWorker.js inside the utils folder.

Structuring the app

Your src/containers/App/index.js file should look like this:

// src/containers/App/index.js

import React, { Component } from 'react';

class App extends Component {
  render() {
    return (
      <div className="App">
        Hello World
      </div>
    );
  }
}

export default App;

Your src/index.js file should look like this:

// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './containers/App';
import registerServiceWorker from './utils/registerServiceWorker';

ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();

Go to your browser and if every thing works fine you will see Hello World on your screen.

You can check my commit on Github.

Adding react-router

React-router will help us define the routes for our app. It's very customizable and very popular in the react ecosystem.

We will be using version 3.0.0 of react-router.

npm install --save react-router@3.0.0

Now, add a new file routes.js inside the src folder with the following code:

// routes.js

import React from 'react';
import { Router, Route } from 'react-router';

import App from './containers/App';

const Routes = (props) => (
  <Router {...props}>
    <Route path="/" component={ App }>
    </Route>
  </Router>
);

export default Routes;

The Router component wraps all the Route components. Based on the path prop of the Route component, the component passed to the component prop, will be rendered on the page. Here, we are setting up the root URL (/) to load our App component using the Router component.

<Router {...props}>
  <Route path="/" component={ <div>Hello World!</div> }>
  </Route>
</Router>

The above code is also valid. For the path /, the <div>Hello World!</div> will be mounted.

Now, we need to call our routes.js file from our src/index.js file. The file should have the following content:

// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { browserHistory } from 'react-router';

import App from './containers/App';
import Routes from './routes';
import registerServiceWorker from './utils/registerServiceWorker';

ReactDOM.render(
  <Routes history={browserHistory} />,
  document.getElementById('root')
);

registerServiceWorker();

Basically, we are mounting our Router component from our routes.js file. We pass in the history prop to it so that the routes know how to handle history tracking.

You can check my commit on Github.

Adding Firebase

If you don't have a Firebase account, create one now (it's free!) by going to their website. After you're done creating a new account, log into your account and go to the console page and click on Add project.

Enter the name of your project (I'll call mine reddit-clone), choose your country, and click on the Create project button.

Now, before we proceed we need to change the rules for the database since, by default, Firebase expects the user to be authenticated to be able to read and write data. If you select your project and click on the Database tab on the left, you will be able to see your database. You need to click on the Rules tab on the top that will redirect us to a screen which will have the following data:

Continue reading %How to Create a Reddit Clone Using React and Firebase%


by Nirmalya Ghosh via SitePoint

Code a Real-Time App With NativeScript: Push Notifications

Getting Started With End-to-End Testing in Angular Using Protractor

10 Time-Saving Gmail Tricks in 2 Minutes [video]

These "Gmail Tricks" will change the way you use Gmail. Sorts turns your Gmail into a set of useful lists.

[ This is a content summary only. Visit our website http://ift.tt/1b4YgHQ for full links, other content, and more! ]

by Web Desk via Digital Information World

How to Add Qualifying Questions to Your Facebook Lead Ads

Do you use Facebook lead ads? Want to learn more about the people who sign up for your offers? Facebook lead ads now let marketers ask customized questions to reveal valuable trends in consumer preferences. In this article, you’ll discover how to add custom questions to your Facebook lead ads. Why Use Questions in Lead [...]

This post How to Add Qualifying Questions to Your Facebook Lead Ads first appeared on .
- Your Guide to the Social Media Jungle


by Stefan Des via