Categories
Angular

How to Make Android Text Based Games with Ionic

Spread the love

Ionic is a hybrid application framework that converts a web app into an Android or iOS app. It is handy for making mobile applications quickly with decent performance. It can easily be extended to create simple text games for entertainment.

If you don’t know how to create apps with Angular and Ionic, see:

In this story, we will build a game where you guess the number until you guessed it right, akin to the Bulls and Cows game or Mastermind game. The game provides you with a number guessing game, and you can change the settings for the number of digits of the number that you’re guessing. It will also be able to save the fastest time.

Building the Game

As usual, we start with the boilerplate code.

To start building, we can use Ionic’s CLI. If you used Angular CLI, then it should be familiar to you. To install the Ionic CLI, run npm install -g ionic.

Then to make generate skeleton code for our app, run ionic start guess-the-number-game sidemenu . guess-the-number-game is our app’s name and sidemenu specifies that we want a side menu in our app.

We need to install some libraries. We can do that by running npm i moment random-js .

Now we can build the game. We make 2 pages by running ionic g component HomePage , andionic g SettingsPage . After that we get 2 empty pages.

In home-page.component.ts , we replace the default code with the following:

import { Component, OnInit } from '@angular/core';
import { Random, browserCrypto } from "random-js";
import * as moment from 'moment';
import { NgForm } from '@angular/forms';
declare const require: any;
const words = require('an-array-of-english-words');

@Component({
  selector: 'app-home-page',
  templateUrl: './home-page.component.html',
  styleUrls: ['./home-page.component.scss'],
})
export class HomePageComponent implements OnInit {
  question: any = <any>{};
  answer: string;
  timer;
  started: boolean;
  rightNumber: number;
  inputtedAnswers: string[] = [];
  elapsedTime;
  message: string;
  numDigits: number;

constructor() { }

  ngOnInit() {
    if (!localStorage.getItem('numDigits')) {
      localStorage.setItem('numDigits', '4');
    }
  }

  ngOnDestroy() {
    this.stop();
  }

  start() {
    this.message = '';
    this.started = true;
    const random = new Random(browserCrypto);
    this.numDigits = +localStorage.getItem('numDigits');
    this.rightNumber = random.integer(Math.pow(10, (this.numDigits - 1)), Math.pow(10, this.numDigits) - 1);
    console.log(this.rightNumber);
    let numSeconds = 0;
    this.timer = setInterval(() => {
      numSeconds++;
      this.elapsedTime = moment.utc(numSeconds * 1000).format('H:mm:ss');
    }, 1000)
  }

  recordFastestTime() {
    const currentFastTime = moment(this.getFastestTime(), 'H:mm:ss');
    const newFastestTime = moment(this.elapsedTime, 'H:mm:ss')
    if (currentFastTime > newFastestTime) {
      localStorage.setItem(
        'fastestTime',
        this.elapsedTime
      );
    }
  }

  stop() {
    this.started = false;
    this.inputtedAnswers = [];
    clearInterval(this.timer);
  }

  checkAnswer(answerForm: NgForm) {
    if (answerForm.invalid) {
      return;
    }
    this.inputtedAnswers.push(this.answer);
    if (+this.answer == this.rightNumber) {
      this.stop();
      this.recordFastestTime();
      this.message = `You win! The correct number is ${this.rightNumber}`;
    }
    this.answer = '';
  }

  getFastestTime() {
    return localStorage.getItem('fastestTime') || '1000000:00:00';
  }

  getNumBullsandCows(answer: number) {
    const rightAnsStr = this.rightNumber.toString().split('');
    const answerStr = answer.toString().split('');
    const numBulls = rightAnsStr.filter((r, i) => rightAnsStr[i] == answer[i]).length;
    const numCows = answerStr.length - numBulls;
    return `${numBulls} bulls, ${numCows} cows`;
  }
}

The code above will generate the number you guess according to the numDigits setting stored in local storage. By default, the number will have 4 digits. After the number is generated, it will start a timer to start calculating the time. Both are in the start function.

inputtedAnswers store the answers you entered. The checkAnswer function checks if you entered anything, then calls the stop function which calls clearInterval on the timer object returned by the setInterval function if your answer is right. The getNumBullsandCows function checks which digits are right and which one are wrong. numBulls is the number of correct digits and numCows the number of wrong digits in your guess. When the guess is right, the fastest time will be computer by comparing with the last fastest time, which is store in local storage by calling the recordFastestTime function and if the current elapsed time is shorter than the saved one, will save the shorter one into the local storage.

Then in home-page.component.html , replace what’s there with the following:

<form #answerForm='ngForm' (ngSubmit)='checkAnswer(answerForm)'>
  <ion-list>
    <ion-item no-padding *ngIf='started' id='instruction'>
      How to Play: Guess the correct number. The closeness to the correct number will be indicated by the number of
      bulls and cows. The number of bulls means the number of digits with the right value and position in your guess.
      The number of cows is the
      number of digits with the wrong value and position in your guess.
    </ion-item>
    <ion-item no-padding *ngIf='started && getFastestTime()'>
      <ion-label class="ion-float-left">
        <h2>Your Fastest Time {{getFastestTime()}}</h2>
      </ion-label>
    </ion-item>
    <ion-item no-padding *ngIf='started'>
      <ion-label class="ion-float-left">
        <h2>Time {{elapsedTime}}</h2>
      </ion-label>
    </ion-item>
    <ion-item no-padding *ngIf='started'>
      <ion-label>Answer</ion-label>
      <ion-input [(ngModel)]='answer' #ans='ngModel' name='ans' required type="text" pattern="d*"
        [minlength]='numDigits' [maxlength]='numDigits'></ion-input>
    </ion-item>
  </ion-list>
  <ion-button type='submit' *ngIf='started'>Answer</ion-button>
  <ion-button (click)='start();' *ngIf='!started'>Start Game</ion-button>
  <ion-button (click)='stop(); started = false' *ngIf='started'>Stop Game</ion-button>
</form>
<ion-content [scrollEvents]="false">
  <ion-list *ngIf='started'>
    <ion-item no-padding *ngFor='let a of inputtedAnswers; let i = index'>
      <ion-label>
        {{i+1}}
      </ion-label>
      <ion-label>
        {{a}}
      </ion-label>
      <ion-label>
        {{getNumBullsandCows(a)}}
      </ion-label>
    </ion-item>
  </ion-list>
  <ion-list *ngIf='message'>
    <ion-item no-padding>
      {{message}}
    </ion-item>
  </ion-list>
</ion-content>

This creates a form for you to enter your number guess and list your guesses, along with the number of right and wrong digits for each guess.

Next we build the settings page. To do this, add the following to settings-page.component.ts :

import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { ToastController } from '@ionic/angular';

@Component({
  selector: 'app-settings-page',
  templateUrl: './settings-page.component.html',
  styleUrls: ['./settings-page.component.scss'],
})
export class SettingsPageComponent implements OnInit {
  settings: any = <any>{};
  fastestTime: string;

  constructor(
    public toastController: ToastController
  ) { }

  ngOnInit() {
    if (!localStorage.getItem('numDigits')) {
      localStorage.setItem('numDigits', '4');
    }
    this.settings.numDigits = +localStorage.getItem('numDigits');
    this.fastestTime = localStorage.getItem('fastestTime');
  }

  save(settingsForm: NgForm) {
    if (settingsForm.invalid) {
      return;
    }
    localStorage.setItem('numDigits', this.settings.numDigits || 4);
    this.presentToast();
  }

  clear() {
    localStorage.removeItem('fastestTime');
    this.fastestTime = localStorage.getItem('fastestTime');
  }

  async presentToast() {
    const toast = await this.toastController.create({
      message: 'Your settings have been saved.',
      duration: 2000
    });
    toast.present();
  }
}

In settings-page.component.html , we replace what’s there with:

<ion-content [scrollEvents]="true">
  <form #settingsForm='ngForm' (ngSubmit)='save(settingsForm)'>
    <ion-list>
      <ion-item no-padding>
        <ion-label>Number of Digits</ion-label>
        <ion-range min="4" max="8" [(ngModel)]='settings.numDigits' #numDigits='ngModel' name='numDigits'>
          <ion-label slot="start">4</ion-label>
          <ion-label slot="end">8</ion-label>
        </ion-range>
      </ion-item>
      <ion-item no-padding>
        <ion-label>Fastest Time</ion-label>
        <ion-label>{{fastestTime}}</ion-label>
        <ion-button (click)='clear()'>Clear Score</ion-button>
      </ion-item>
    </ion-list>
    <ion-button type='submit'>Save</ion-button>
  </form>
</ion-content>

You can clear local storage from the settings form and set the number of digits of the number you guess in this page. The numDigits control is a slider.

In app-routing.module.ts , add:

import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
import { HomePageComponent } from './home-page/home-page.component';
import { SettingsPageComponent } from './settings-page/settings-page.component';

const routes: Routes = [
  {
    path: '',
    redirectTo: 'home',
    pathMatch: 'full'
  },
  {
    path: 'home',
    component: HomePageComponent
  },
  {
    path: 'settings',
    component: SettingsPageComponent
  }
];

@NgModule({
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {}

In app.component.ts , add:

import { Component } from '@angular/core';

import { Platform } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html'
})
export class AppComponent {
  public appPages = [
    {
      title: 'Home',
      url: '/home',
      icon: 'home'
    },
    {
      title: 'Settings',
      url: '/settings',
      icon: 'settings'
    }
  ];

constructor(
    private platform: Platform,
    private splashScreen: SplashScreen,
    private statusBar: StatusBar
  ) {
    this.initializeApp();
  }

initializeApp() {
    this.platform.ready().then(() => {
      this.statusBar.styleDefault();
      this.splashScreen.hide();
    });
  }
}

The last 2 files will add the menu entries for the side menu and allow us to visit the pages that we created.

In app.module.ts , write:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular")';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { HomePageComponent } from './home-page/home-page.component';
import { AdMobFree } from '@ionic-native/admob-free/ngx';
import { SettingsPageComponent } from './settings-page/settings-page.component';
import { FormsModule } from '@angular/forms")';

@NgModule({
  declarations: [
    AppComponent,
    HomePageComponent,
    SettingsPageComponent
  ],
  entryComponents: [],
  imports: [
    BrowserModule,
    IonicModule.forRoot(),
    AppRoutingModule,
    FormsModule
  ],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

This is the final wiring that allows all the pages to be seen.

Leave a Reply

Your email address will not be published.

If you like the content of this blog, subscribe to my email list to get exclusive articles not available to anyone else.