TypeScript Tutorial
TypeScript Project: Tic-Tac-Toe
A 3x3 board as a string array. Place marks, print the grid, and detect a winner.
What you will build
Tic-tac-toe is a 3 by 3 grid. This project stores nine cells in a string[]. Empty cells are a single space. Players place X and O. After each move you print the board and check rows, columns, and diagonals for a winner.
Moves are a fixed sequence so the winner is the same every run. That keeps the demo deterministic at/typescript/try. Use Try it in TypeScript.
Nine cells
Index 0 is top-left. Index 2 is top-right. Index 8 is bottom-right. Printing uses three cells per line with bars between them. A helper builds an empty board so tests start from the same state.
Example
function emptyBoard(): string[] {
return [" ", " ", " ", " ", " ", " ", " ", " ", " "];
}
function printBoard(b: string[]): void {
console.log(" " + b[0] + " | " + b[1] + " | " + b[2]);
console.log("---+---+---");
console.log(" " + b[3] + " | " + b[4] + " | " + b[5]);
console.log("---+---+---");
console.log(" " + b[6] + " | " + b[7] + " | " + b[8]);
}
const board: string[] = emptyBoard();
printBoard(board);Place a mark
A legal move needs an index from 0 through 8 and an empty cell. Return false when the cell is taken or the index is out of range. Do not overwrite an existing mark.
Example
function place(b: string[], cell: number, mark: string): boolean {
if (cell < 0 || cell > 8) {
return false;
}
if (b[cell] !== " ") {
return false;
}
b[cell] = mark;
return true;
}
const board: string[] = [" ", " ", " ", " ", " ", " ", " ", " ", " "];
console.log(place(board, 0, "X"));
console.log(place(board, 0, "O"));
console.log(place(board, 9, "X"));
console.log("cell 0 is " + board[0]);First place succeeds and prints true. Second place on the same cell fails and printsfalse. Index 9 is invalid. Cell 0 still holds X.
Run this at /typescript/try with Try it in TypeScript. The printedtrue and false values are enough to see which calls were accepted.
Detect a winner
Eight lines can win: three rows, three columns, two diagonals. If all three cells in a line share a mark that is not a space, that mark wins. Return that string, or a space when there is no winner yet.
Example
function winner(b: string[]): string {
const lines: number[][] = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6],
];
for (let i = 0; i < 8; i++) {
const a = b[lines[i][0]];
const c = b[lines[i][1]];
const d = b[lines[i][2]];
if (a !== " " && a === c && c === d) {
return a;
}
}
return " ";
}
const board: string[] = [" ", " ", " ", " ", " ", " ", " ", " ", " "];
board[0] = "X";
board[1] = "X";
board[2] = "X";
console.log("Winner: [" + winner(board) + "]");The top row is three X marks, so the function returns X.
Complete game, fixed moves
X starts. The sequence fills the top row for X while O takes the middle row’s first two cells. After each successful place, print the board and stop if someone has won. There is no keyboard input, so the transcript never changes.
Example
function printBoard(b: string[]): void {
console.log(" " + b[0] + " | " + b[1] + " | " + b[2]);
console.log("---+---+---");
console.log(" " + b[3] + " | " + b[4] + " | " + b[5]);
console.log("---+---+---");
console.log(" " + b[6] + " | " + b[7] + " | " + b[8]);
}
function place(b: string[], cell: number, mark: string): boolean {
if (cell < 0 || cell > 8 || b[cell] !== " ") {
return false;
}
b[cell] = mark;
return true;
}
function winner(b: string[]): string {
const lines: number[][] = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6],
];
for (let i = 0; i < 8; i++) {
const a = b[lines[i][0]];
const c = b[lines[i][1]];
const d = b[lines[i][2]];
if (a !== " " && a === c && c === d) {
return a;
}
}
return " ";
}
const board: string[] = [" ", " ", " ", " ", " ", " ", " ", " ", " "];
const moves: number[] = [0, 3, 1, 4, 2];
const marks: string[] = ["X", "O", "X", "O", "X"];
for (let i = 0; i < 5; i++) {
console.log(marks[i] + " plays cell " + moves[i]);
if (!place(board, moves[i], marks[i])) {
console.log("Illegal move");
break;
}
printBoard(board);
const w = winner(board);
if (w !== " ") {
console.log(w + " wins");
break;
}
console.log("");
}X takes cells 0, 1, and 2. After the fifth move the top row is full and the program prints X wins. Change the last X move to cell 8 and you should see no winner line instead.
Common mistakes
- Using a 3 by 3 nested array and then mixing up row and column. Nine cells and a printed grid keep the index rule in one place:
row * 3 + colif you add coordinates later. - Checking only rows. Columns and diagonals are how most first games are actually won.
- Treating three spaces as a win. The empty mark must be excluded or a blank board wins on the first row.
- Allowing a second mark in a filled cell. Always test
b[cell] !== " "before you assign.
Practice
- Change the last X move from cell 2 to cell 8 and confirm the program no longer prints a winner.
- Add a sixth move for O at cell 5 and confirm the middle row then wins for O if X did not already win.
- After nine legal moves with no winner, print
Draw, and write a function that returns how many empty cells remain.
Next project: a contact book keyed by name in a Map.