Scroll To Bottom Mystery Master TypeScript Member

typescript

This help article explains what I learned when converting the Mystery Master Logic Puzzle Solver Application (aka Mystery Master App, or simply app) from JavaScript to TypeScript. Wikipedia says TypeScript, developed by Microsoft, is a strict syntactical superset of JavaScript with optional static typing. TutorialsTeacher says TypeScript increases code quality, readability and makes it easy to maintain and refactor codebase. More importantly, errors can be caught at compile time rather than at runtime. It supports object-oriented programming features like data types, classes, enums, and more. TypeScript transcompiles to JavaScript. A JavaScript program is also a valid TypeScript program.

I will say this: the things I don't like about JavaScript are the things I don't like about TypeScript. I think JavaScript is more functional than object-oriented language. I don't want to define a function as a class, then initiate an instance of the class with the constructor method. And I really don't like prefixing every class member with the keyword this. That said, I do like I can easily export and input classes, and use them as the data type for a variable. Example: const viewer: Viewer = new Viewer(); I just wish I could use a function as a data type.

Node.js

Node.js (aka node) is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node allows you to run JavaScript on your development computer (aka server), compared to running JavaScript in a web browser (aka client). The Node Package Manager (npm) is a package manager for the JavaScript programming language maintained by npm, Inc. Open the terminal window of VS for the following examples.

Here are ways to maintain TypeScript. If you want to learn how to compile, please see my metawork article after reading this article.

TSDoc

I updated my comments from JSDoc to TSDoc. To see how I generated documentation from my comments, see my article on metawork.

Access Modifiers

In object-oriented programming, the concept of encapsulation is used to make class members public or private i.e. a class can control the visibility of its data members. This is done using access modifiers. TypeScript has three access modifiers: public, private and protected.

Data Types

JavaScript (and TypeScript) have three primitive data types: boolean, number, and string. In TypeScript, an array can be defined as a data type followed by brackets. Examples: boolean[], number[], and string[]. The any type means not to restrict the type. In TypeScript, you can specify the types of both the input and output values of functions. A class I define can be the type for a variable. Implicitly, const myObj = new MyClass(); is explicitly given as const myObj: MyClass = new MyClass();. I can even define types that look similar to object literals.

Modules

I didn't use modules in JavaScript, but with TypeScript I am. The TypeScript website says modules are executed within their own scope, not in the global scope; this means that variables, functions, classes, etc. declared in a module are not visible outside the module unless they are explicitly exported using one of the export forms. Conversely, to consume a variable, function, class, interface, etc. exported from a different module, it has to be imported using one of the import forms. I like modules because there is no this to worry about.

I grouped my TypeScript (ts) files into these subfolders under the ts folder: puzzle, puzzles, server, solver, and viewer. Each folder can be considered a library or assembly of files, where all the files in that folder have the same objective. I must point out that files in the server folder only run on my development server under Node.js - they are NOT uploaded to the server hosting my website.

Folders
NameDescription
puzzle Builds a logic puzzle. These files are the building blocks of a logic puzzle.
puzzlesLoads a unique logic puzzle. A file in this folder is called a puzzle module.
server Performs "metawork" on logic puzzles. Only runs under Node.js on the server.
solver Solves a logic puzzle. These files help solve a logic puzzle.
viewer Displays a logic puzzle in a browser. These files show stats and data for solving a logic puzzle.

The table belows lists the TypeScript files under each folder sorted by name, and gives the dependencies (imports) it has. The puzzle modules in the puzzles folder are not given here.

TypeScript (*.ts) Files for Mystery Master App
FolderFilenameTypeDependencies
puzzle
Fact Class Verb, Noun, Link
Helper Module Verb, Noun, Puzzle
ISolver InterfaceVerb, NounType, Noun, Rule, Mark
Link Class Verb, NounType, Noun
Mark Class Verb, Noun, Fact, Rule
Noun Class NounType, Fact, Helper
NounType Class Noun
Puzzle Class Verb, NounType, Noun, Link, Fact, Rule, SmartLink, Helper
Rule Class Noun, Mark
SmartLinkModule Verb, Noun, LinkFunction
SmartRuleModule Verb, NounType, Noun, Link, Rule, RuleFunction, Mark, Helper, Solver
Verb Class [none]
server
Filer Module Puzzle, Solver, Viewer, Former
solver
Finder Function Verb, NounType, Noun, Fact, Puzzle, Mark, Helper, Solver, Loner
IViewer InterfaceFact, Rule, Mark
Lawyer Function Verb, NounType, Noun, Fact, Puzzle, Mark, Helper
Loner Class Verb, NounType, Noun, Puzzle, Solver
Solver Class Verb, NounType, Noun, Link, Fact, Rule, Mark, MarkType, Puzzle, Helper, Finder, Lawyer, Viewer
Stats Class Mark, Solver
viewer
Board Function Puzzle, Solver, Viewer, UIX
Filer Stub Puzzle, Solver
Former Module Puzzle, Helper, Solver, Stats, LevelCounter, UIX
Locker Module Helper
Setup Function Solver, Viewer, UIX
Tabby Function Verb, Fact, Rule, Puzzle, Mark, Helper, Solver, Stats, Viewer, Locker, Former, UIX
UIX Module Verb, Puzzle, Viewer, Locker
Viewer Class Verb, Fact, Rule, Mark, Puzzle, Helper, Solver, IViewer, Board, Tabby, Setup

Type Aliases

A type alias is a name for a defined type. This is convenient when the same type is needed more than once. Below is a table with the file and the type defined in the file.

Type Aliases
FilenameType Alias
Linkexport type LinkFunction = (noun1: Noun, noun2: Noun) => Verb;
Ruleexport type RuleFunction = (mark: Mark) => number;
Markexport type MarkType = { num: number; name: string; }

Interfaces

An Interface is a contract that a class must implement. Classes that are derived from an interface must follow the structure provided by their interface. The interfaces are given below.

  1. ISolver is the contract the Solver class must implement for the Puzzle class.
  2. IViewer is the contract the Viewer class must implement for the Solver class.

Callbacks

When the solver calls the viewer, this is an event that the program may pause on. When the program resumes, the viewer must know what method (function) to call. This function the viewer is calling is a callback. The callback invoked with the setTimeout statement. Using setTimeout allows time for the user to interact with the user interface (UI). In other programming languages, threads could be used to pause/resume a running program. JavaScript has Web Workers which run on their own thread, but managing communication between the worker and the main thread is difficult.

Callbacks are not required for modules within the puzzle and puzzles folder. Callbacks are performed internally within the Finder object. Callbacks are essential between the Solver object and the Viewer object. The viewer has several methods called by the solver (hence the IViewer interface), but only the viewer's doResume method needs to make the callback. This method could be written in one line:

if (this.solver.callback !== null) setTimeout(this.solver.callback, 0);

Question: Could I make the callback without using setTimeout? Below is a table containing how the solver sets the callbacks that the doResume method calls.

Callbacks
Solver MethodEdited Callback Code
sayStarted
finder.doWork
sayStopped
null
sayLevel
Set by finder.doNextLevel to doLevel1, doLevel2, doLevel3, or doLevel4
saySolution
quitFlag ? sayStopped : undoAssumption
sayAddMark
doResume
sayRemoveMark
if (who === 1) { mark.type === Mark.Type.User ? null : undoUserMark }
if (who === 2) { mark.type === Mark.Type.Level ? finder.doResume : undoAssumption }
			
sayValidMark
doResume
sayContradiction
quitFlag ? doQuit : undoAssumption
sayFactViolation
quitFlag ? doQuit : undoAssumption
sayRuleViolation
quitFlag ? doQuit : undoAssumption
sayLawViolation
quitFlag ? doQuit : undoAssumption
sayPlacers
doResume

The solver's doResume method is the callback function for sayAddMark, sayValidMark, and sayPlacers. Depending on the number of validated marks, this method will call either lawyer.doWork, saySolution, finder.doResume, or nothing.

The finder's doResume method is the callback function for sayRemoveMark. Depending on whether assumptions are being made, it will either continue to make assumptions, or start again at level 1.

Module v Static Class

In JavaScript, I created an object literal to contain helper methods. When I converted to TypeScript, I changed the object literal to a class that was exported. Now that I'm using modules, I converted my "helper" class to a module file that contains my exported methods. I can then import all methods in one gulp.

Helper.js as Object Literal Helper.ts as Class Helper.ts as Module
const Helper = {
	method1() { },
	method2() { },
	method3() { }
};
export class Helper {
	static method1() { }
	static method2() { }
	static method3() { }
}

import { Helper } from "./path/Helper";
export function method1() { }
export function method2() { }
export function method3() { }

import * as Helper from "./path/Helper";

Programming Tips

  1. There are two good ways to clear an array. If the array is defined with let and is not referenced elsewhere, set it to the empty array. This technique redefines the array, so it does not work for constants. If the array is defined with const, set its length to zero. I think its best to set its length to zero in either case.
    let a = [1, 2, 3];
    a = [];
    							
    const a = [1, 2, 3];
    a.length = 0;
    							

  2. Optional Chaining. To access a property (prop) on an object (obj) that may not exist (undefined) or is null, add "?" after the property. Example: obj?.prop. This will return "undefined" instead of throwing an error.
  3. A browser tab is the same as a browser window. Each window has its own JavaScript environment under the global object: window. Globals defined in JavaScript are also in this global scope. Example: let myName = "Michael" is the same as window.myName.
  4. I read that the node.js server environment has the global context global, but I receive an error when I try to refence this in a Visual Studio Code terminal window.
  5. Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. The website is https://nodejs.org. While I am converting my program to TypeScript, I am also learning Node.js to handle my server-side needs. One need is read all of my *.ts files in the puzzles folder. These are my puzzle modules.
  6. To read files in a folder, I require (literally) the fs module. As explained by TutorialsTeacher, the fs module is responsible for all the asynchronous or synchronous file I/O operations.