play(ply, first, computerLevel) {
		var bestMove = null;
		var assessment;

		if (first && this.outcome === ONGOING) {
			// on first call, test whether to intentionally reduce play quality
			if (computerLevel < Math.sqrt(Math.random())) {
				//intentionally degrading move quality at computerLevel% chance
				let rand = Math.sqrt(Math.random());
				// at lower computerlevels, incline to reduce quality by reducing ply
				if (computerLevel >= rand) {
					ply = Math.ceil(ply * rand);
				} //otherwise, reduce level through random moves (to introduce some variety)
				else {
					let legalMoves = this.getLegalMoves();
					bestMove = legalMoves[Math.floor(Math.random() * legalMoves.length)];
					return {
						advisedMove: legalMoves[Math.floor(Math.random() * legalMoves.length)],
						bestAssessment: 0,
					};
				}
			}
		}

		// Play strongest from this point:
		if (this.totalMoves === 0) {
			// always start in the middle
			bestMove = this.cols >> 1;
			assessment = 0;
		} else if (ply === 0) {
			assessment = this.assessment();
		} else if (this.outcome !== ONGOING) {
			assessment = this.outcome;

			if (assessment === WHITE_WIN) assessment += ply;
			else if (assessment === BLACK_WIN) assessment -= ply;
		} //game ongoing and ply >= 1
		else {
			let legalMoves = this.getLegalMoves();
			assessment = this.whiteMoves ? BLACK_WIN - ply : WHITE_WIN + ply;

			for (const move of legalMoves) {
				let tempBoard = this.clone();
				tempBoard.move(move);
				const advisedPlay = tempBoard.play(ply - 1, false, null);

				if (this.whiteMoves) {
					if (advisedPlay.bestAssessment > assessment) {
						bestMove = move;
						assessment = advisedPlay.bestAssessment;
					}
				} // black
				else {
					if (advisedPlay.bestAssessment < assessment) {
						bestMove = move;
						assessment = advisedPlay.bestAssessment;
					}
				}
			}

			// no best move found - pick one at random
			if (bestMove === null) {
				bestMove = legalMoves[Math.floor(Math.random() * legalMoves.length)];
			}
		}

		return { advisedMove: bestMove, bestAssessment: assessment };
	} //play

/**
	 * Returns what it thinks is the best move for the currently playing side using ply number of recursive assessments, as well as the assessment of this position.
	 * If the game is already over, returns null as the best move.
	 *
	 * @param {*} ply 0 will return an assessment of the current sitaution without looking ahead
	 * @param {*} first pass true if calling to this function externally (as opposed to recursive calls) - for diagnostics only
	 * @param {x} computerLevel a value from 0 to 1 denoting the quality of play/randomness (0 = lowest, 1 = highest)
	 */
Returns what it thinks is the best move for the currently playing side using ply number of recursive assessments, as well as the assessment of this position.
If the game is already over, returns null as the best move.

@param {*} ply 0 will return an assessment of the current sitaution without looking ahead
@param {*} first pass true if calling to this function externally (as opposed to recursive calls) - for diagnostics only
@param {x} computerLevel a value from 0 to 1 denoting the quality of play/randomness (0 = lowest, 1 = highest)
Returns what it thinks is the best move for the currently playing side using ply number of recursive assessments as well as the assessment of this position . If the game is already over returns null as the best move . @param { * } ply 0 will return an assessment of the current sitaution without looking ahead @param { * } first pass true if calling to this function externally ( as opposed to recursive calls ) - for diagnostics only @param { x } computerLevel a value from 0 to 1 denoting the quality of play / randomness ( 0 = lowest 1 = highest )
play(ply, first, computerLevel) {
		var bestMove = null;
		var assessment;
		if (first && this.outcome === ONGOING) {
			if (computerLevel < Math.sqrt(Math.random())) {
				let rand = Math.sqrt(Math.random());
				if (computerLevel >= rand) {
					ply = Math.ceil(ply * rand);
				} 
				else {
					let legalMoves = this.getLegalMoves();
					bestMove = legalMoves[Math.floor(Math.random() * legalMoves.length)];
					return {
						advisedMove: legalMoves[Math.floor(Math.random() * legalMoves.length)],
						bestAssessment: 0,
					};
				}
			}
		}
		if (this.totalMoves === 0) {
			bestMove = this.cols >> 1;
			assessment = 0;
		} else if (ply === 0) {
			assessment = this.assessment();
		} else if (this.outcome !== ONGOING) {
			assessment = this.outcome;
			if (assessment === WHITE_WIN) assessment += ply;
			else if (assessment === BLACK_WIN) assessment -= ply;
		} 
		else {
			let legalMoves = this.getLegalMoves();
			assessment = this.whiteMoves ? BLACK_WIN - ply : WHITE_WIN + ply;
			for (const move of legalMoves) {
				let tempBoard = this.clone();
				tempBoard.move(move);
				const advisedPlay = tempBoard.play(ply - 1, false, null);
				if (this.whiteMoves) {
					if (advisedPlay.bestAssessment > assessment) {
						bestMove = move;
						assessment = advisedPlay.bestAssessment;
					}
				} 
				else {
					if (advisedPlay.bestAssessment < assessment) {
						bestMove = move;
						assessment = advisedPlay.bestAssessment;
					}
				}
			}
			if (bestMove === null) {
				bestMove = legalMoves[Math.floor(Math.random() * legalMoves.length)];
			}
		}
		return { advisedMove: bestMove, bestAssessment: assessment };
	} 
play ( ply , first , computerLevel ) { var bestMove = null ; var assessment ; if ( first && this . outcome === ONGOING ) { if ( computerLevel < Math . sqrt ( Math . random ( ) ) ) { let rand = Math . sqrt ( Math . random ( ) ) ; if ( computerLevel >= rand ) { ply = Math . ceil ( ply * rand ) ; } else { let legalMoves = this . getLegalMoves ( ) ; bestMove = legalMoves [ Math . floor ( Math . random ( ) * legalMoves . length ) ] ; return { advisedMove : legalMoves [ Math . floor ( Math . random ( ) * legalMoves . length ) ] , bestAssessment : 0 , } ; } } } if ( this . totalMoves === 0 ) { bestMove = this . cols >> 1 ; assessment = 0 ; } else if ( ply === 0 ) { assessment = this . assessment ( ) ; } else if ( this . outcome !== ONGOING ) { assessment = this . outcome ; if ( assessment === WHITE_WIN ) assessment += ply ; else if ( assessment === BLACK_WIN ) assessment -= ply ; } else { let legalMoves = this . getLegalMoves ( ) ; assessment = this . whiteMoves ? BLACK_WIN - ply : WHITE_WIN + ply ; for ( const move of legalMoves ) { let tempBoard = this . clone ( ) ; tempBoard . move ( move ) ; const advisedPlay = tempBoard . play ( ply - 1 , false , null ) ; if ( this . whiteMoves ) { if ( advisedPlay . bestAssessment > assessment ) { bestMove = move ; assessment = advisedPlay . bestAssessment ; } } else { if ( advisedPlay . bestAssessment < assessment ) { bestMove = move ; assessment = advisedPlay . bestAssessment ; } } } if ( bestMove === null ) { bestMove = legalMoves [ Math . floor ( Math . random ( ) * legalMoves . length ) ] ; } } return { advisedMove : bestMove , bestAssessment : assessment } ; }
Returns what it thinks is the best move for the currently playing side using ply number of recursive assessments, as well as the assessment of this position.
Returns what it thinks is the best move for the currently playing side using ply number of recursive assessments as well as the assessment of this position .
// on first call, test whether to intentionally reduce play quality
 //intentionally degrading move quality at computerLevel% chance
 // at lower computerlevels, incline to reduce quality by reducing ply
 //otherwise, reduce level through random moves (to introduce some variety)
 // Play strongest from this point:
 // always start in the middle
 //game ongoing and ply >= 1
 // black
 // no best move found - pick one at random
 //play
