The Angular PWA Demo includes several classic games with AI implementations, demonstrating game logic, minimax algorithms, and automated solvers.
Overview
Sudoku
Generate and solve Sudoku puzzles
Tic-Tac-Toe
Play against minimax AI opponent
Tower of Hanoi
Manual play or automated solver
Tetris
Classic game with AI auto-play
Sudoku
Generate and solve 9×9 Sudoku puzzles using backend algorithms in C++ or Node.js.
Key features
- Puzzle generation from backend or file upload
- Multiple solver backends (C++, Node.js)
- Board validation
- PDF export of puzzles
- Visual highlighting of solved cells
Implementation
// src/app/_modules/_Demos/_DemosFeatures/games/game-sudoku/game-sudoku.component.ts
public GenerateFromBackend(): void {
this.status_message.set("[...generating...]");
let generatedSudoku: Observable<string>;
let selectedIndex: number = this._languajeList.nativeElement.options.selectedIndex;
switch (selectedIndex) {
case 1: // C++
generatedSudoku = this._sudokuService._GetSudoku_CPP();
break;
case 2: // Node.js
generatedSudoku = this._sudokuService._GetSudoku_NodeJS();
break;
default:
return;
}
this.sudokuSolved = false;
this.btnGenerateCaption = '[...generating...]';
generatedSudoku.subscribe({
next: (jsondata: string) => {
this._sudokuGenerated = jsondata;
// Parse JSON to 2D array
jsondata = jsondata.replaceAll('[', '').replaceAll(']', '');
jsondata = jsondata.replaceAll('},', '|');
let jsonDataArray = jsondata.split('|');
this.board = [];
for (let i = 0; i < 9; i++) {
const row: number[] = [];
const rowString = jsonDataArray[i].split(',');
for (let j = 0; j < 9; j++) {
row.push(parseInt(rowString[j]));
}
this.board.push(row);
}
this.status_message.set("[Generated correctly]");
}
});
}
Solving puzzles
public _SolveSudoku(): void {
this.sudokuSolved = true;
this.btnSolveCaption = '[...solving...]';
let solveSudoku: Observable<string>;
let selectedIndex = this._languajeList.nativeElement.options.selectedIndex;
switch (selectedIndex) {
case 1: // C++
solveSudoku = this._sudokuService._SolveSudoku_CPP(this._sudokuGenerated);
break;
case 2: // Node.js
solveSudoku = this._sudokuService._SolveSudoku_NodeJS(this._sudokuGenerated);
break;
}
solveSudoku.subscribe({
next: (jsondata: string) => {
this.status_message.set("[Solved correctly]");
// Update board with solution
}
});
}
Sudoku puzzles can also be uploaded from JSON files for solving.
Tic-Tac-Toe
Classic Tic-Tac-Toe game with an unbeatable AI opponent using the minimax algorithm.
Minimax algorithm
The AI uses minimax with game tree exploration to find optimal moves:
// src/app/_engines/tictactoe.engine.ts
minimax(board: ('X' | 'O' | null)[][], depth: number, isAI: boolean): number | undefined {
let score: number | undefined = 0;
let bestScore: number | undefined = 0;
if (this.gameOver(board) == true) {
if (isAI == true) return -1;
if (isAI == false) return +1;
}
if (depth < 9) {
if (isAI == true) {
bestScore = -999;
for (let i = 0; i < this.SIDE; i++) {
for (let j = 0; j < this.SIDE; j++) {
if (board[i][j] == null) {
board[i][j] = this.COMPUTERMOVE;
score = this.minimax(board, depth + 1, false);
board[i][j] = null;
if (score! > bestScore!) {
bestScore = score;
}
}
}
}
return bestScore;
} else {
bestScore = 999;
for (let i = 0; i < this.SIDE; i++) {
for (let j = 0; j < this.SIDE; j++) {
if (board[i][j] == null) {
board[i][j] = this.HUMANMOVE;
score = this.minimax(board, depth + 1, true);
board[i][j] = null;
if (score! < bestScore!) {
bestScore = score;
}
}
}
}
return bestScore;
}
} else {
return 0;
}
}
Best move calculation
bestMove(board: ('X' | 'O' | null)[][], moveIndex: number): number {
let x = -1;
let y = -1;
let score: number | undefined = 0;
let bestScore: number | undefined = -999;
for (let i = 0; i < this.SIDE; i++) {
for (let j = 0; j < this.SIDE; j++) {
if (board[i][j] == null) {
board[i][j] = this.COMPUTERMOVE;
score = this.minimax(board, moveIndex + 1, false);
board[i][j] = null;
if (score! > bestScore!) {
bestScore = score;
x = i;
y = j;
}
}
}
}
return ((x * 3) + y);
}
Game flow
makeMove(n: number) {
if (this.squares[n] || this.winner) {
return;
}
// Human move
this.makeHumanMove(n);
if (this._declareWinner() == false) {
// Computer move
this.makeComputerMove();
this._declareWinner();
}
}
Tower of Hanoi
Classic Tower of Hanoi puzzle with both manual play mode and automated solver using Angular v21 signals.
Signal-based state management
Manual play
Auto solver
Win detection
// Angular v21: Signal updates for state changes
manual_moveDisk(fromTower: number, toTower: number) {
const currentState = this._gameState();
const newState = currentState.map(tower => [...tower]);
const diskToMove = newState[fromTower].pop();
if (diskToMove) {
const targetTower = newState[toTower];
if (targetTower.length === 0 ||
targetTower[targetTower.length - 1].size > diskToMove.size) {
targetTower.push(diskToMove);
// Signal update triggers change detection
this._gameState.set(newState);
this._moves.update(m => m + 1);
}
}
}
auto_towerOfHanoi(n: number, from_rod: string, to_rod: string, aux_rod: string): void {
if (n === 0) return;
this.auto_towerOfHanoi(n - 1, from_rod, aux_rod, to_rod);
this.auto_saveStep(n, from_rod, to_rod);
this.auto_towerOfHanoi(n - 1, aux_rod, to_rod, from_rod);
}
auto_printSteps() {
if (this._stepsIndex > this._stepsAmt) {
clearTimeout(this._timeoutId);
return;
}
if (this._steps[this._stepsIndex]) {
let hanoiStep = this._steps[this._stepsIndex];
let message = `Step ${(this._stepsIndex + 1)} of ${this._stepsAmt}. Move disk ${hanoiStep.n} from Tower ${hanoiStep.from} to Tower ${hanoiStep.to}`;
this.steps.push(message);
this.auto_makeMove(hanoiStep);
}
this._stepsIndex++;
this._timeoutId = setTimeout(() => {
this.auto_printSteps();
}, this._delayInMilliseconds);
}
// Computed signal for win condition
public readonly isWin = computed(() => this._gameState()[2].length === 3);
public _checkWinCondition(): boolean {
return this.isWin(); // Delegate to computed signal
}
The Hanoi engine uses Angular v21’s signal-based state management for reactive updates without RxJS observables.
Tetris
Classic Tetris game with AI auto-play capability powered by a C++ backend.
Game management
// src/app/_services/__Games/TetrisService/tetris.service.ts
createGame(): Observable<{ message: string; handle: number }> {
return this.http.post<{ message: string; handle: number }>(`${this.apiUrl}/create`, {}).pipe(
tap(() => {
this.gameCreated = true;
console.log('✅ Game instance created');
}),
catchError(this.handleError)
);
}
step(): Observable<any> {
if (!this.gameCreated) {
return throwError(() => new Error('Game not created. Call createGame() first.'));
}
return this.http.post(`${this.apiUrl}/step`, {}).pipe(
catchError(this.handleError)
);
}
getState(): Observable<TetrisState | null> {
if (!this.gameCreated) {
return of(null);
}
return this.http.get<TetrisState>(`${this.apiUrl}/state`).pipe(
catchError(err => {
console.warn('⚠️ State fetch failed:', err);
return of(null);
})
);
}
AI features
trainAI(weightsFile: string = 'tetris_weights.txt', generations: number = 20): Observable<any> {
return this.http.post(`${this.apiUrl}/train`, { weightsFile, generations }).pipe(
tap(() => console.log('✅ AI training complete')),
catchError(this.handleError)
);
}
Game architecture
All games follow a consistent architecture pattern:
Handles user input, UI state, and orchestrates game flow. Uses Angular reactive forms and signals for state management.
Manages HTTP communication with backend services for game generation, solving, and AI features.
Implements game logic, AI algorithms (minimax), and state validation. Pure TypeScript classes for portability.
Canvas-based or DOM-based rendering depending on the game. Optimized for performance and responsiveness.
Algorithms
Algorithm visualizations and demos
Machine learning
AI and TensorFlow implementations