Skip to content

Instantly share code, notes, and snippets.

@primaryobjects
Last active January 11, 2020 21:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save primaryobjects/c46206ed4ba47902ca621b73ec2cf616 to your computer and use it in GitHub Desktop.
Save primaryobjects/c46206ed4ba47902ca621b73ec2cf616 to your computer and use it in GitHub Desktop.
Isolation 3x3 with game tree generation and AI Minimax with Alpha-Beta Pruning algorithm. https://codepen.io/primaryobjects/pen/QWWGgmR
Styles
https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css
Libraries
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js
https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js
https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js
https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js
<div class='container'>
<div id='root'></div>
</div>
<div class='container'>
<div id='graph'></div>
</div>
// Main React render hook.
$(function() {
const isolationCtrl = ReactDOM.render(
<div>
<IsolationContainer width="3" height="3"></IsolationContainer>
</div>,
document.getElementById('root')
);
});
//import Tree from 'https://cdn.jsdelivr.net/npm/react-tree-graph@4.0.1/dist/index.min.js';
window.CP.PenTimer.MAX_TIME_IN_LOOP_WO_EXIT = 6000;
const IsolationManager = {
isValidMove: (x, y, playerIndex, players, values, width, height) => {
const activePlayer = players[playerIndex];
const opponentPlayer = players[!playerIndex ? 1 : 0];
let isValid = activePlayer.x === -1; // Initialize the first-move to valid.
if (x < 0 || x >= width || y < 0 || y >= height) {
isValid = false;
}
// Verify this cell is not already used (i.e., it's value is 0).
else if (values[y][x]) {
//console.log(`Cell ${x},${y} is already taken.`);
isValid = false;
}
else if (!isValid) {
// Verify this move is valid for the player and the path is not blocked.
let isBlocked;
// Verify path is valid.
if (x !== activePlayer.x && y !== activePlayer.y && (Math.abs(activePlayer.x - x) !== Math.abs(activePlayer.y - y))) {
// This is a diagonal move but not valid one for one.
isBlocked = true;
console.log(`Invalid move to ${x},${y}`);
}
// Verify path is not blocked.
else if (y < activePlayer.y && x < activePlayer.x) {
// Up-left.
let posy = activePlayer.y - 1
for (let posx = activePlayer.x - 1; posx > x; posx--) {
if (values[posy][posx]) {
isBlocked = true;
break;
}
posy--;
}
}
else if (y < activePlayer.y && x > activePlayer.x) {
// Up-right.
let posy = activePlayer.y - 1
for (let posx = activePlayer.x + 1; posx < x; posx++) {
if (values[posy][posx]) {
isBlocked = true;
break;
}
posy--;
}
}
else if (y > activePlayer.y && x < activePlayer.x) {
// Down-left.
let posy = activePlayer.y + 1;
for (let posx = activePlayer.x - 1; posx > x; posx--) {
if (values[posy][posx]) {
isBlocked = true;
break;
}
posy++;
}
}
else if (y > activePlayer.y && x > activePlayer.x) {
// Down-right.
let posy = activePlayer.y + 1;
for (let posx = activePlayer.x + 1; posx < x; posx++) {
if (values[posy][posx]) {
isBlocked = true;
break;
}
posy++;
}
}
else if (x > activePlayer.x) {
// Right.
for (let pos = activePlayer.x + 1; pos < x; pos++) {
if (values[y][pos]) {
isBlocked = true;
break;
}
}
}
else if (x < activePlayer.x) {
// Left.
for (let pos = activePlayer.x - 1; pos > x; pos--) {
if (values[y][pos]) {
isBlocked = true;
break;
}
}
}
else if (y > activePlayer.y) {
// Down.
for (let pos = activePlayer.y + 1; pos < y; pos++) {
if (values[pos][x]) {
isBlocked = true;
break;
}
}
}
else if (y < activePlayer.y) {
// Up.
for (let pos = activePlayer.y - 1; pos > y; pos--) {
if (values[pos][x]) {
isBlocked = true;
break;
}
}
}
isValid = !isBlocked;
}
return isValid;
},
availableMoves: (playerIndex, players, values, width, height) => {
let moves = [];
const activePlayer = players[playerIndex];
if (activePlayer.x !== -1) {
let x, y;
/*let blockUp = false;
let blockDown = false;
for (let step=1; step<height; step++) {
// Up.
y = activePlayer.y - step;
if (!blockUp && IsolationManager.isValidMove(activePlayer.x, y, playerIndex, players, values, width, height)) {
moves.push({ x: activePlayer.x, y });
}
else {
// Path is blocked from going further.
blockUp = true;
}
// Down.
y = activePlayer.y + step;
if (!blockDown && IsolationManager.isValidMove(activePlayer.x, y, playerIndex, players, values, width, height)) {
moves.push({ x: activePlayer.x, y });
}
else {
// Path is blocked from going further.
blockDown = true;
}
if (blockUp && blockDown) {
break;
}
}*/
// Up.
for (y=activePlayer.y - 1; y>=0; y--) {
if (IsolationManager.isValidMove(activePlayer.x, y, playerIndex, players, values, width, height)) {
moves.push({ x: activePlayer.x, y });
}
else {
// Path is blocked from going further.
break;
}
}
// Down.
for (y=activePlayer.y + 1; y<height; y++) {
if (IsolationManager.isValidMove(activePlayer.x, y, playerIndex, players, values, width, height)) {
moves.push({ x: activePlayer.x, y });
}
else {
// Path is blocked from going further.
break;
}
}
/*let blockLeft = false;
let blockRight = false;
for (let step=1; step<width; step++) {
// Left.
x = activePlayer.x - step;
if (!blockLeft && IsolationManager.isValidMove(x, activePlayer.y, playerIndex, players, values, width, height)) {
moves.push({ x, y: activePlayer.y });
}
else {
// Path is blocked from going further.
blockLeft = true;
}
// Right.
x = activePlayer.x + step;
if (!blockRight && IsolationManager.isValidMove(x, activePlayer.y, playerIndex, players, values, width, height)) {
moves.push({ x, y: activePlayer.y });
}
else {
// Path is blocked from going further.
blockRight = true;
}
if (blockLeft && blockRight) {
break;
}
}*/
// Left.
for (x=activePlayer.x - 1; x>=0; x--) {
if (IsolationManager.isValidMove(x, activePlayer.y, playerIndex, players, values, width, height)) {
moves.push({ x, y: activePlayer.y });
}
else {
// Path is blocked from going further.
break;
}
}
// Right.
for (x=activePlayer.x + 1; x<width; x++) {
if (IsolationManager.isValidMove(x, activePlayer.y, playerIndex, players, values, width, height)) {
moves.push({ x, y: activePlayer.y });
}
else {
// Path is blocked from going further.
break;
}
}
/*let blockUpLeft = false;
let blockUpRight = false;
let blockDownLeft = false;
let blockDownRight = false;
for (let step=1; step<height; step++) {
const yUp = activePlayer.y - step;
const yDown = activePlayer.y + step;
if (yUp < 0) {
blockUpLeft = true;
blockUpRight = true;
}
if (yDown >= height) {
blockDownLeft = true;
blockDownRight = true;
}
// Up-left.
x = activePlayer.x - step;
if (!blockUpLeft && IsolationManager.isValidMove(x, yUp, playerIndex, players, values, width, height)) {
moves.push({ x, y: yUp });
}
else {
// Path is blocked from going further.
blockUpLeft = true;
}
// Down-left.
if (!blockDownLeft && IsolationManager.isValidMove(x, yDown, playerIndex, players, values, width, height)) {
moves.push({ x, y: yDown });
}
else {
// Path is blocked from going further.
blockDownLeft = true;
}
// Up-right.
x = activePlayer.x + step;
if (!blockUpRight && IsolationManager.isValidMove(x, yUp, playerIndex, players, values, width, height)) {
moves.push({ x, y: yUp });
}
else {
// Path is blocked from going further.
blockUpRight = true;
}
// Down-right.
if (!blockDownRight && IsolationManager.isValidMove(x, yDown, playerIndex, players, values, width, height)) {
moves.push({ x, y: yDown });
}
else {
// Path is blocked from going further.
blockDownRight = true;
}
if (blockUpLeft && blockUpRight && blockDownLeft && blockDownRight) {
break;
}
}*/
// Up-left.
x = activePlayer.x;
for (y=activePlayer.y - 1; y>=0; y--) {
x--;
if (x === -1) {
break;
}
if (IsolationManager.isValidMove(x, y, playerIndex, players, values, width, height)) {
moves.push({ x, y });
}
else {
// Path is blocked from going further.
break;
}
}
// Up-right.
x = activePlayer.x;
for (y=activePlayer.y - 1; y>=0; y--) {
x++;
if (x >= width) {
break;
}
if (IsolationManager.isValidMove(x, y, playerIndex, players, values, width, height)) {
moves.push({ x, y });
}
else {
// Path is blocked from going further.
break;
}
}
// Down-left.
x = activePlayer.x;
for (y=activePlayer.y + 1; y<height; y++) {
x--;
if (x === -1) {
break;
}
if (IsolationManager.isValidMove(x, y, playerIndex, players, values, width, height)) {
moves.push({ x, y });
}
else {
// Path is blocked from going further.
break;
}
}
// Down-right.
x = activePlayer.x;
for (y=activePlayer.y + 1; y<height; y++) {
x++;
if (x >= width) {
break;
}
if (IsolationManager.isValidMove(x, y, playerIndex, players, values, width, height)) {
moves.push({ x, y });
}
else {
// Path is blocked from going further.
break;
}
}
}
else {
moves = IsolationManager.allMoves(playerIndex, players, width, height, width, height);
}
return moves;
},
allMoves: (playerIndex, players, width, height) => {
const moves = [];
// First move, all spaces are available. Second move, all spaces but 1 are available.
for (let y=0; y<height; y++) {
for (let x=0; x<width; x++) {
if (!playerIndex || x !== players[0].x || y !== players[0].y) {
moves.push({ x, y });
}
}
}
return moves;
}
};
const StrategyManager = {
none: function() {
return null;
},
random: function(tree, playerIndex, players, values, width, height) {
let isValid = false;
let count = 0;
let x, y;
console.log('Using AI strategy random.');
while (!isValid && count++ < 1000) {
x = Math.floor(Math.random() * width);
y = Math.floor(Math.random() * height);
isValid = IsolationManager.isValidMove(x, y, playerIndex, players, values, width, height);
}
if (count >= 1000) {
console.log('Random strategy failed to find a move.');
}
return { x, y };
},
minimax: function(tree, playerIndex, players, values, width, height) {
// https://tonypoer.io/2016/10/28/implementing-minimax-and-alpha-beta-pruning-using-python/
let bestState = null;
let bestVal = -9999;
let beta = 9999;
console.log('Using AI strategy minimax.');
const getSuccessors = node => {
return node ? node.children : [];
};
const isTerminal = node => {
return node ? node.children.length === 0 : true;
};
const getUtility = node => {
return node ? node.score : -9999;
};
const maxValue = (node, alpha, beta) => {
console.log(`AlphaBeta-->MAX: Visited Node: ${toString(node)}`);
if (isTerminal(node)) {
return getUtility(node);
}
else {
let value = -9999;
const successors = getSuccessors(node);
successors.forEach(state => {
value = Math.max(value, minValue(state, alpha, beta));
if (value >= beta) {
return value;
}
else {
alpha = Math.max(alpha, value);
}
});
return value;
}
};
const minValue = (node, alpha, beta) => {
console.log(`AlphaBeta-->MIN: Visited Node: ${toString(node)}`);
if (isTerminal(node)) {
return getUtility(node);
}
else {
let value = 9999;
const successors = getSuccessors(node);
successors.forEach(state => {
value = Math.min(value, maxValue(state, alpha, beta));
if (value <= alpha) {
return value;
}
else {
beta = Math.min(beta, value);
}
});
return value;
}
};
const toString = state => {
return `Depth ${state.depth}, Score ${state.score}, activePlayer ${state.activePlayer}, players: (${state.players[0].x}, ${state.players[0].y}), (${state.players[1].x}, ${state.players[1].y})`;
};
const successors = getSuccessors(tree);
successors.forEach(state => {
const value = minValue(state, bestVal, beta);
if (value > bestVal) {
bestVal = value;
bestState = state;
}
});
console.log(`MiniMax: Utility value of best node is ${bestVal}.`);
console.log(`MiniMax: Best state is: ${toString(bestState)}`)
return bestState ? { x: bestState.players[bestState.activePlayer].x, y: bestState.players[bestState.activePlayer].y } : { x: 1, y: 1 };
},
tree: function(playerIndex, players, values, width, height, maxDepth = 4) {
//playerIndex = 0; // Always use scores for human player.
const referencePlayerIndex = !playerIndex ? 1 : 0; // Point-of-view for the player that the tree is calculated for. The root node will be from the opposing player.
const heuristic = (playerMoves, opponentMoves) => {
// Use a herustic of (moves * 2) - opponent moves, thus maximizing the number of moves for the player while minimizing the number of moves for your opponent.
return (playerMoves * 2) - opponentMoves;
};
let root = { depth: 0, player: playerIndex, activePlayer: playerIndex, baseScore: players[referencePlayerIndex].moves.length, score: heuristic(players[referencePlayerIndex].moves.length, players[!referencePlayerIndex ? 1 : 0].moves.length), moves: players[referencePlayerIndex].moves, players, values, children: [], width, height };
let fringe = [ root ];
let node = fringe.shift();
while (node) {
if (node.depth <= maxDepth && node.moves.length) {
const newPlayerIndex = node.depth % 2 === 0 ? referencePlayerIndex : playerIndex;//0 : 1;
// Evaluate all possible moves from the current state.
node.moves.forEach(move => {
// Make a copy of the players.
let newPlayers = JSON.parse(JSON.stringify(node.players));
// Move the player to a new position.
newPlayers[newPlayerIndex].x = move.x;
newPlayers[newPlayerIndex].y = move.y;
// Set the new position as used.
let newValues = JSON.parse(JSON.stringify(node.values));
newValues[newPlayers[newPlayerIndex].y][newPlayers[newPlayerIndex].x] = !newPlayerIndex ? 'gray' : 'silver';
// Get available moves at new position in relation to the reference player.
const movesReferencePlayer = IsolationManager.availableMoves(referencePlayerIndex, newPlayers, newValues, width, height);
// Get available moves with new game board for next player.
const moves = IsolationManager.availableMoves(!newPlayerIndex ? 1 : 0, newPlayers, newValues, width, height);
// Add the new node to our tree.
const child = { depth: node.depth + 1, player: playerIndex, activePlayer: newPlayerIndex, baseScore: movesReferencePlayer.length, score: heuristic(movesReferencePlayer.length, moves.length), moves, players: newPlayers, values: newValues, children: [] };
node.children.push(child);
fringe.push(child);
});
}
// Process next node.
node = fringe.shift();
}
return root;
},
renderTree: function(tree, maxNodes = 50) {
const graph = $('#graph');
graph.html('');
const fringe = [{ tree, depth: 0 }];
let index = 0;
let count = 0;
let node = fringe.shift();
while (node && count++ < maxNodes) {
const player1 = node.tree.players[0/*node.tree.activePlayer*/];
const player2 = node.tree.players[1/*node.tree.activePlayer ? 0 : 1*/];
const id = `node-${index++}-${node.depth}-${player1.x}-${player1.y}`;
// Append a new div line to the output.
graph.append(`<div id='xx'></div><div id='header-${id}' class='ml-${node.depth}'>Depth: ${node.depth}, Player 1: (${player1.x}, ${player1.y}), Player 2: (${player2.x}, ${player2.y}), Score: ${node.tree.score}, Active Player: ${ node.tree.activePlayer }, Moves: ${JSON.stringify(node.tree.moves)}<div id='${id}'</div></div>`);
// Render a mini version of the grid for this state.
ReactDOM.render(
<div>
<Isolation width={ tree.width } height={ tree.height } strategy={ StrategyManager.random } playerIndex = { node.tree.activePlayer } player1x={ player1.x } player1y={ player1.y } player2x={ player2.x } player2y={ player2.y } grid={ node.tree.values } moves={ node.tree.baseScore } cellStyle="small"></Isolation>
</div>,
document.getElementById(id)
);
// Add each child node from this state to the fringe.
node.tree.children.forEach(child => {
fringe.push({ tree: child, depth: node.depth + 1 });
});
node = fringe.pop();
}
/* ReactDOM.render(
<div>
<Tree
data={tree}
height={400}
width={400}/>
</div>,
document.getElementById('xx')
);*/
}
};
class Cell extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick(e) {
// Callback handler for cell click event.
this.props.onClick(this, this.props.x, this.props.y);
}
render() {
return (
<div id={ `cell-${this.props.x}-${this.props.y}` } className={`cell ${this.props.cellStyle || ''}`} style={{width: this.props.width || '', height: this.props.height || '', backgroundColor: this.props.color }} onClick={ this.onClick }>
{ this.props.children }
</div>
);
};
};
class Grid extends React.Component {
constructor(props) {
super(props);
const values = props.grid || [];
if (!props.grid) {
// Populate the grid values with zeros.
for (let y=0; y <= props.height; y++) {
const row = [];
for (let x=0; x <= props.width; x++) {
row.push(0);
}
values.push(row);
}
}
this.state = {
values,
width: props.width,
height: props.height,
};
this.onClick = this.onClick.bind(this);
this.setValue = this.setValue.bind(this);
}
componentDidUpdate(nextProps) {
const { width, height } = this.props;
// Reset the grid values when the width or height changes.
if ((width && nextProps.width !== width) || (height && nextProps.height !== height)) {
const values = [];
// Populate the grid values with zeros.
for (let y=0; y <= height; y++) {
const row = [];
for (let x=0; x <= width; x++) {
row.push(0);
}
values.push(row);
}
this.setState({ values, width, height });
console.log(`Reset grid to ${width},${height}`);
}
}
onClick(cell, x, y) {
console.log(`${x},${y}`);
// Callback handler for cell click event.
this.props.onClick(x, y, this.state.values, cell);
}
setValue(x, y, value) {
// Set the cell value.
const values = this.state.values;
values[y][x] = value;
this.setState({ values });
}
render() {
const rows = [];
for (let y=0; y<this.state.height; y++) {
const cols = []
for (let x=0; x<this.state.width; x++) {
cols.push(
<td>
<Cell x={x} y={y} color={ this.state.values[y][x] } cellStyle={ this.props.cellStyle } onClick={ this.onClick }>
{ this.props.players.map((player, index) => {
return (x === player.x && y === player.y) ? this.props.children[index] : null
}) }
</Cell>
</td>
);
}
rows.push(<tr>{cols}</tr>);
}
return (
<div class='grid'>
<table>
<tbody>
{rows}
</tbody>
</table>
</div>
)
}
}
class Player extends React.Component {
constructor(props) {
super(props);
}
render() {
// Adjust offset to position player icons on grid.
let leftOffset = 25;
let topOffset = 5;
const container = $('#app');
if (container.length) {
const rect = container[0].getBoundingClientRect();
leftOffset = rect.left + 15;
topOffset = rect.top + 5;
}
return (
<i class={ `player ${this.props.cellStyle || ''} fas fa-female` } style={{ top: `${this.props.y * this.props.height + topOffset}px`, left: `${this.props.x * this.props.width + leftOffset}px`, color: this.props.color }}></i>
);
}
}
class Isolation extends React.Component {
constructor(props) {
super(props);
this.state = {
round: 1,
playerIndex: props.playerIndex || 0,
players: [ { x: props.player1x || -1, y: props.player1y || -1, moves: [{}] }, { x: props.player2x || -1, y: props.player2y || -1, moves: [{}] } ],
grid: props.grid,
strategy: props.strategy,
width: props.width,
height: props.height,
treeDepth: props.treeDepth,
};
this.state.players[0].moves = IsolationManager.allMoves(0, this.state.players, props.width, props.height);
this.state.players[1].moves = IsolationManager.allMoves(1, this.state.players, props.width, props.height);
this.grid = React.createRef();
this.onGrid = this.onGrid.bind(this);
}
componentDidUpdate(nextProps) {
const { strategy, width, height, treeDepth } = this.props;
if (strategy && nextProps.strategy !== strategy) {
this.setState({ strategy });
}
if (width && nextProps.width !== width) {
this.setState({ width });
}
if (height && nextProps.height !== height) {
this.setState({ height });
}
if (treeDepth && nextProps.treeDepth !== treeDepth) {
this.setState({ treeDepth });
}
}
onGrid(x, y, values) {
const playerIndex = this.state.playerIndex;
const players = this.state.players;
if (IsolationManager.isValidMove(x, y, playerIndex, players, values, this.grid.current.props.width, this.grid.current.props.height)) {
// Update player position.
players[playerIndex].x = x;
players[playerIndex].y = y;
// Update the grid local variable with the player move (so available moves will be accurate).
values[y][x] = playerIndex + 1;
// Update available moves for all players.
players[0].moves = IsolationManager.availableMoves(0, players, values, this.grid.current.props.width, this.grid.current.props.height);
players[1].moves = IsolationManager.availableMoves(1, players, values, this.grid.current.props.width, this.grid.current.props.height);
// Update cell value in the grid.
this.grid.current.setValue(x, y, !playerIndex ? 'gray' : 'silver');
/*let tree = [];
if (playerIndex === 1) {
tree = StrategyManager.tree(!playerIndex ? 1 : 0, JSON.parse(JSON.stringify(players)), values, this.grid.current.props.width, this.grid.current.props.height);
StrategyManager.renderTree(tree);
}*/
// Update state and play opponent's turn.
this.setState({ round: this.state.round + 1, playerIndex: !playerIndex ? 1 : 0, players }, () => {
if (this.state.playerIndex && this.state.players[this.state.playerIndex].moves.length > 0) {
if (this.state.strategy && this.state.strategy !== StrategyManager.none) {
// AI turn.
setTimeout(() => {
const tree = StrategyManager.tree(playerIndex, JSON.parse(JSON.stringify(players)), values, this.grid.current.props.width, this.grid.current.props.height);
StrategyManager.renderTree(tree, this.state.treeDepth);
// Get the AI's move.
({ x, y } = this.props.strategy(tree, this.state.playerIndex, this.state.players, values, this.grid.current.props.width, this.grid.current.props.height));
console.log(`AI is moving to ${x},${y}.`)
// Move the AI player.
this.onGrid(x, y, values);
}, 1000);
}
}
});
return true;
}
}
render() {
const moves = this.props.moves !== undefined ? this.props.moves : this.state.players[this.state.playerIndex].moves.length;
const winnerIndex = this.state.playerIndex ? 1 : 2;
return (
<div id='app' ref={ this.container }>
<Grid width={ this.state.width } height={ this.state.height } grid={ this.props.grid } cellStyle={ this.props.cellStyle } players={ this.state.players } onClick={ this.onGrid } ref={ this.grid }>
<Player width="50" height="50" x={ this.state.players[0].x } y={ this.state.players[0].y } cellStyle={ this.props.cellStyle } color="blue"></Player>
<Player width="50" height="50" x={ this.state.players[1].x } y={ this.state.players[1].y } cellStyle={ this.props.cellStyle } color="orange"></Player>
</Grid>
<div class='row'>
<div class='col col-auto'>
<div class={ `badge ${!this.state.playerIndex ? 'badge-primary' : 'badge-warning'}` }>Player { this.state.playerIndex + 1 }'s Turn</div>
</div>
<div class='col col-auto'>
<div class='badge badge-light'>{ moves } Moves Available</div>
</div>
<div class='col col-auto'>
<div class={ `badge badge-success ${!moves ? '' : 'd-none'}` }>Player { winnerIndex } wins!</div>
</div>
</div>
<div class='row'>
<div class='col'>
<div class='badge badge-secondary'>
Move { Math.round(this.state.round / 2) }
</div>
</div>
</div>
</div>
);
}
}
class IsolationContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
strategy: props.strategy || StrategyManager.minimax,
width: props.width || 3,
height: props.height || 3,
treeDepth: props.treeDepth || 25
};
this.onType = this.onType.bind(this);
this.onWidth = this.onWidth.bind(this);
this.onHeight = this.onHeight.bind(this);
this.onTreeDepth = this.onTreeDepth.bind(this);
}
onType(e) {
let strategy;
switch (e.currentTarget.value) {
case 'random': strategy = StrategyManager.random; break;
case 'minimax': strategy = StrategyManager.minimax; break;
case 'none': strategy = StrategyManager.none; break;
};
this.setState({ strategy });
console.log(`Strategy set to ${e.currentTarget.value}.`);
}
onWidth(e) {
this.setState({ width: e.currentTarget.value });
}
onHeight(e) {
this.setState({ height: e.currentTarget.value });
}
onTreeDepth(e) {
this.setState({ treeDepth: e.currentTarget.value });
}
render() {
return (
<div>
<Isolation width={ this.state.width } height={ this.state.height } treeDepth={ this.state.treeDepth } strategy={ this.state.strategy }></Isolation>
<div class="gamePlayOptions">
<div>
<span>Game Play</span>
<input type="radio" name="type" value="minimax" checked={this.state.strategy === StrategyManager.minimax} onChange={ this.onType }/> <span>Minimax</span>
<input type="radio" name="type" value="random" checked={this.state.strategy === StrategyManager.random} onChange={ this.onType }/> <span>Random</span>
<input type="radio" name="type" value="none" checked={!this.state.strategy || this.state.strategy === StrategyManager.none} onChange={ this.onType }/> <span>2 Players</span>
</div>
<div>
<span>Grid Size</span>
<input type="number" id="width" name="width" value={this.state.width} onChange={ this.onWidth }/>
<input type="number" id="height" name="height" value={this.state.height} onChange={ this.onHeight }/>
</div>
<div>
<span>Tree Depth</span>
<input type="number" id="treeDepth" name="treeDepth" value={this.state.treeDepth} onChange={ this.onTreeDepth }/>
</div>
</div>
</div>
);
}
}
.cell {
width: 50px;
height: 50px;
line-height: 0;
border: 1px solid black;
transition: background-color 0.25s;
&.small {
width: 10px;
height: 10px;
}
}
.player {
margin: 3px 0 0 12px;
font-size: 40px;
transition: all 0.25s;
&.small {
margin: 0;
font-size: 10px;
}
}
#graph {
min-height: 200px;
.ml-1 {
margin-left: 20px !important;
}
.ml-2 {
margin-left: 40px !important;
}
.ml-3 {
margin-left: 60px !important;
}
.ml-4 {
margin-left: 80px !important;
}
.ml-5 {
margin-left: 100px !important;
}
}
.gamePlayOptions {
span {
margin-right: 12px;
}
input[type='number'] {
width: 50px;
margin-right: 6px;
}
}
@primaryobjects
Copy link
Author

primaryobjects commented Nov 20, 2019

cap

cap2b

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment