JS: External User Custom Code
File Name
2: game.html or game.js
Related
[Feature]() ::
[User Story]() ::
[UAC]() ::
NOTICE: External Original Code as Source
This code's orignal author is: Scott Windon: GitHub
This code's version 1.0 was found on a CodePen: here
This code was refactored and improve upon to port it from version 1.0 to a version 2.0
Decision: Why Reuse
This project is a resubmission.
Decided to rearchitect the original Code base from 1st submission.
1st attempt was over designed and was influenced by a old approach of code, and was not modern.
So starting off with a simple working code and logic model was a choice.
Why is this different
This is different and improved as this code based is refactored and demonstrates my refactoring skills.
Uses a Object Orrinetated / Class based approach over a functional approach.
Compoent methods have been refactored in to more efficient, more readable and lower congitive complex method length/signatures.
Uses a constructor() method initialise object/instance's state and private members.
Has private members: this._member has an underscore by convention.
Uses static Getters for accessing private and public members as class API methods.
Setter methods would be used if values need to be changed as an input into the class/instance state.
These static Properties (Get/Set) as the class API for class state/behaviours.
Public Methods are limited to those methods that relate to
User Interaction
User Feedback
User Action
Private Methods are internal class methods, refactored mostly from more complex original ones from original source. These have a underscore before the name by convention.
Retain Concepts
Syntactical sugar from source code retained.
Use of RegEx for Global Sequence (g) matching for each turn.
this._grid = Alpine.reactive(new Array(App.MAX_LENGTH).fill(null)) // ES6 Arrow Function
1: Apline.reactive Fill an array over map(function) and return null for initialising an array.
---
---
---
this.won = 'X';
this._won = App.P1
Remove hard coded values Use of App.P1 | App.P2 for instance/class method getters of class properties (X or O)
API
Uses JSDoc to
Define the API documentation from Automatic build and CLI scripts
Define static typing for JavaScript (as JS is dynamically typed_ that is compatible with Typescript
Well documented code as a technical writing practice + skillset.
Properties: Get/Set
class App {
/**
* Gets the Player 1 Token.
* @returns {string} The name of the property for turns.
* @static
*/
static get P1 () {
return 'X'
}
/**
* Gets the Player 1 Token.
* @returns {string} The name of the property for turns.
* @static
*/
// noinspection FunctionNamingConventionJS
static get P2 () {
return 'O'
}
/**
* Gets the name of the property for keeping track off the number of X turns.
* @returns {string} The name of the property for X turns.
* @static
*/
// noinspection FunctionNamingConventionJS
static get X_TURNS_PROP () {
return 'xTurns'
}
/**
* Gets the name of the property for keeping track of the number of O turns.
* @returns {string} The name of the property for O turns.
* @static
*/
// noinspection FunctionNamingConventionJS
static get O_TURNS_PROP () {
return '._oTurns'
}
/**
* Returns the maximum (array/grid cell) length allowed for the grid
* @returns {number} The maximum length allowed.
* @static
*/
// noinspection FunctionNamingConventionJS
static get MAX_LENGTH () {
return 9
}
/**
* Returns the initialisation value for turns.
* @returns {number} The value for TURN_INIT.
* @static
*/
// noinspection FunctionNamingConventionJS
static get TURN_INIT () {
return 0
}
/**
* Returns the initialisation value for turns.
* @returns {array} The value for private _grid.
*/
get GRID () {
return this._grid
}
/**
* Returns the initialisation value for turns.
* @returns {boolean} The value for private _won.
*/
get WON () {
return this._won
}
/**
* Returns the initialisation value for turns.
* @returns {number} The value for current private _turns.
*/
get TURNS () {
return this._turns
}
....
}
App Class & Constructor
class App {
...
/**
* Constructor for the App class.
* Initializes the App object with default values and reactivity, for
* - turns,
* - win state,
* - win sequences,
* - game grid,
* - x characters | o characters.
* The constructor also logs the instantiation of the ...
* App object to the console.
* @url https://alpinejs.dev/advanced/reactivity
* @constructor
*/
// noinspection FunctionNamingConventionJS
constructor () {
//
console.log('Instantiate App:', this) // jshint ignore:line
/** @access private */
this._turns = App.TURN_INIT
/** @access private */
this._won = false // The default check win state of the game
/** @access private */
this.winSeq = [
'012',
'345',
'678', // HorizontalWins
'036',
'147', // DiagonalWins
'258',
'048',
'246',
] // VerticalWins
// noinspection JSUnresolvedReference, ChainedFunctionCallJS, NestedFunctionCallJS
/** @access public */
this._grid = Alpine.reactive(new Array(App.MAX_LENGTH).fill(null)) // ES6 Arrow Function
// noinspection JSUnresolvedReference
/** @access public */
this._xChars = Alpine.reactive(['x', 'X'])
// noinspection JSUnresolvedReference
/** @access public */
this._oChars = Alpine.reactive(['o', 'O'])
/** @access public */
this._xTurns = ''
/** @access public */
this._oTurns = ''
}
....
}
App Public Methods
App.select()
Calls: _isInvalidMove (index)
Calls: _isEvenTurn (index)
Calls: checkWinner(index) for every turn and sets boolean value.
class App {
...
/**
* Selects a cell, bu index, on the game board.
* @function select
* @param {number} index - The index of the cell to be selected.
* @returns {string} - Game Token for X || Y.
* @desc It logs the selected index, checks if the move is invalid, and returns the game token for X or Y.
* If the move is invalid, it logs an error message and returns the current item from the grid. //
* If the move is valid, it updates the turns and grid, checks if the game has been won, logs a success message,
* and returns the updated item/token from the grid.
* */
select (index) {
console.log('The selected index:', index) // jshint ignore:line
// Check if move is invalid, and proceed to next turn if the return is false,
if (this._isInvalidMove(index)) {
//
console.log('Invalid Move: ', index) // jshint ignore:line
return this._grid[index]
} else {
// Update the turns and grid
this._turns += 1
this._isEvenTurn(index)
// Check if the game has been won
const isWinner = this.checkWinner()
// Return the updated item from grid
console.log('Valid Move: ', this._turns, this._grid[index], isWinner) // jshint ignore:line
return this._grid[index] // @Update 23/12/06 to return updated item/token from grid
}
}
App.checkWinner()
Calls: _checkSequenceWin(turn, sequence) for either P1 or P2 properties per turn.
/**
* Checks if the sequence of turns is a winner.
* @function checkWinner
* @returns {boolean} True if the sequence of turns is a winner.
* @desc checks for a winner in a game. It iterates over a list of winning sequences and calls the
* _checkSequenceWin method to check if either player has won.
* If Player 1 has won, the won variable is set to App.P1 (indicating Player 1's victory).
* If Player 2 has won, the won variable is set to App.P2 (indicating Player 2's victory).
* The method returns the value of the won variable, indicating the winner of the game.
* */
checkWinner () {
for (const sequence of this.winSeq) {
if (this._checkSequenceWin(this._xTurns, sequence)) {
this._won = App.P1 // Player 1 wins | X wins
break
} else if (this._checkSequenceWin(this._oTurns, sequence)) {
this._won = App.P2 // Player 2 wins | O wins
break
}
}
return this._won
}
App.reset()
/**
* Reset the game state to its initial values.
* @function reset
* @return {void}
* @desc that resets the game state to its initial values.
* - Resets turns to a predefined value
* - Reset won to false.
* - Reset grid to an empty array.
* - Reset _xTurns and _oTurns to empty strings.
* */
reset () {
this._turns = App.TURN_INIT
this._won = false
// noinspection ChainedFunctionCallJS,NestedFunctionCallJS,JSUnresolvedReference
this._grid = Alpine.reactive(new Array(App.MAX_LENGTH).fill(null))
this._xTurns = ''
this._oTurns = ''
}
...
}
App Private Methods
_getRandonCharacter(characterArray)
Used/Called by: _updayeTurnsAndGrid()
/**
* Returns a random character from the given character array.
* @design
* This randomiser allows variation of the game token sizes (lower case/upper case).
* It mimics variation in hand strokes when hand drawing the game tokens.
* @function _getRandomCharacter
* @param {Array} characterArray - The array containing characters.
* @return {string} - A random character from the character array.
*/
_getRandomCharacter (characterArray) {
// noinspection LocalVariableNamingConventionJS,NestedFunctionCallJS
/** An inner function/closure for generating a random index. Improve maintainability/readability.
* @function {arrow function} _getRandomIndex
* @param {number} arrayLength
*/
const _getRandomIndex = arrayLength => Math.floor(Math.random() * arrayLength)
const index = _getRandomIndex(characterArray.length)
return characterArray[index]
}
/**
* Update the turns and grid at the specified index
* with a random character from the characterArray.
* @function _updateTurnsAndGrid
* @param {number} index - The index of the grid to update.
* @param {Array} characterArray - The array of characters to choose from.
* @param {string} turnProperty - The property to update the turns with.
* @return {undefined}
* @desc It takes in three parameters: index, characterArray, and turnProperty.
* It updates the grid array at the specified index with a random character from the characterArray and ...
* increments the value of the turnProperty by index.
*/
_updateTurnsAndGrid (index, characterArray, turnProperty) {
this._grid[index] = this._getRandomCharacter(characterArray)
this[turnProperty] += index
}
_isInvalidMove (index)
Used/Called by: select()
/**
* Determines if the turn is even and updates the turns and grid accordingly.
* @function _isEvenTurn
* @param {number} index - The index of the grid to be updated.
* @return {undefined}
* @desc determines if the turn is even and updates the turns and grid accordingly.
* It takes an input parameter index which represents the index of the grid to be updated.
* It calculates whether the turn is even by checking if the remainder of the division of `this._turns`
* by 2 is equal to 0. If the turn is even, it assigns certain values to variables char and prop.
*/
_isEvenTurn (index) {
const isEvenTurn = this._turns % 2 === 0
// noinspection ConditionalExpressionJS
const char = isEvenTurn ? this._xChars : this._oChars
// noinspection ConditionalExpressionJS
const prop = isEvenTurn ? App.X_TURNS_PROP : App.O_TURNS_PROP
this._updateTurnsAndGrid(index, char, prop)
console.log('isEvenTurn:', isEvenTurn, char, prop, index, this._grid[index]) // jshint ignore:line
}
_isEvenTurn (index)
Used/Called by: select()
Calls: _updateTurnsAndGrid(index, char, prop)
/**
* Determines if the turn is even and updates the turns and grid accordingly.
* @function _isEvenTurn
* @param {number} index - The index of the grid to be updated.
* @return {undefined}
* @desc determines if the turn is even and updates the turns and grid accordingly.
* It takes an input parameter index which represents the index of the grid to be updated.
* It calculates whether the turn is even by checking if the remainder of the division of `this._turns`
* by 2 is equal to 0. If the turn is even, it assigns certain values to variables char and prop.
*/
_isEvenTurn (index) {
const isEvenTurn = this._turns % 2 === 0
// noinspection ConditionalExpressionJS
const char = isEvenTurn ? this._xChars : this._oChars
// noinspection ConditionalExpressionJS
const prop = isEvenTurn ? App.X_TURNS_PROP : App.O_TURNS_PROP
this._updateTurnsAndGrid(index, char, prop)
console.log('isEvenTurn:', isEvenTurn, char, prop, index, this._grid[index]) // jshint ignore:line
}
_checkSequenceWin
Used/Called by: checkWinner()
/**
* Checks whether the sequence of turns is a winner.
* @function _checkSequenceWin
* @param {string} turns - The sequence of turns.
* @param {string} sequence - The sequence of turns to check.
* @returns {boolean}
* @private
* @desc It takes in two parameters: turns and sequence. Inside the function, it creates a regular expression
* using the sequence parameter. This regular expression matches any three characters in the sequence string.
* Then, it uses the created regular expression to filter out any characters in the turns string that are not
* present in the sequence string. The filtered string is stored in the filteredTurns variable.
* Finally, the function returns true if the filtered string contains a sequence of three consecutive characters
* that match the sequence string, and false otherwise.
* @credit Scott Window for use of RegExp constructor, regex pattern and RegExp.test() methods.
* */
_checkSequenceWin (turns, sequence) {
const sequenceRegExp = new RegExp(`[${sequence}]{3}`)
const searchRegExp = new RegExp(`[^${sequence}]+`, 'g')
const filteredTurns = turns.replace(searchRegExp, '')
return sequenceRegExp.test(filteredTurns)
}
CODE
:Related
Feature
]() ::User Story
]() ::UAC
]() ::NOTICE: External Original Code as Source
Decision: Why Reuse
Why is this different
Original Code
New Code:
Design
constructor()
method initialise object/instance's state and private members.this._member
has an underscore by convention.Retain Concepts
g
) matching for each turn.Code Use Improvements
grid: Array.apply(null, Array(9)).map(function (v,i) { return null})
this._grid = Alpine.reactive(new Array(App.MAX_LENGTH).fill(null)) // ES6 Arrow Function
Fill an array over map(function) and return null for initialising an array.
this.won = 'X';
this._won = App.P1
Use of App.P1 | App.P2 for instance/class method getters of class properties (X or O)
API
Properties: Get/Set
App Class & Constructor
App Public Methods
App.select()
_isInvalidMove (index)
_isEvenTurn (index)
checkWinner(index)
for every turn and sets boolean value.App.checkWinner()
_checkSequenceWin(turn, sequence)
for either P1 or P2 properties per turn.App.reset()
App Private Methods
_getRandonCharacter(characterArray)
_updayeTurnsAndGrid()
_updateTurnsAndGrid (index, characterArray, turnProperty)
_getRandomCharacter
_isInvalidMove (index)
select()
_isEvenTurn (index)
select()
_updateTurnsAndGrid(index, char, prop)
_checkSequenceWin
checkWinner()