Skip to content

Instantly share code, notes, and snippets.

@primaryobjects
Created October 22, 2019 19:04
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/2b1ecf7ff12bfb695c18a1b4ed9a9fc8 to your computer and use it in GitHub Desktop.
Save primaryobjects/2b1ecf7ff12bfb695c18a1b4ed9a9fc8 to your computer and use it in GitHub Desktop.
.cell {
width: 50px;
height: 50px;
border: 1px solid black;
transition: background-color 0.25s;
}
.player {
font-size: 40px;
position: absolute;
transition: left 0.25s, top 0.25s;
}
<div class='container'>
<div id='root'></div>
</div>
// Main React render hook.
$(function() {
ReactDOM.render(
<div>
<Isolation width="6" height="6"></Isolation>
</div>,
document.getElementById('root')
);
});
class Cell extends React.Component {
constructor(props) {
super(props);
this.state = {
color: 'white'
};
this.onClick = this.onClick.bind(this);
}
setColor(color) {
// Update the cell color.
this.setState({ color });
};
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' style={{width: this.props.width || '', height: this.props.height || '', backgroundColor: this.state.color }} onClick={ this.onClick }>
</div>
);
};
};
class Grid extends React.Component {
constructor(props) {
super(props);
// Populate the grid values with zeros.
const values = [];
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
};
this.onClick = this.onClick.bind(this);
this.setValue = this.setValue.bind(this);
}
onClick(cell, x, y) {
console.log(`${x},${y}`);
// Callback handler for cell click event.
this.props.onClick(x, y, cell, this.state.values);
}
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.props.height; y++) {
const cols = []
for (let x=0; x<this.props.width; x++) {
cols.push(<td><Cell x={x} y={y} onClick={ this.onClick }></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 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: 0,
players: [ { x: -1, y: -1, moves: props.width * props.height }, { x: -1, y: -1, moves: props.width * props.height - 1 } ],
};
this.grid = React.createRef();
this.onGrid = this.onGrid.bind(this);
this.availableMoves = this.availableMoves.bind(this);
}
onGrid(x, y, cell, values) {
const playerIndex = this.state.playerIndex;
const players = this.state.players;
if (this.isValidMove(x, y, playerIndex, players, values)) {
// 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[playerIndex].moves = this.availableMoves(playerIndex, players, values);
players[!playerIndex ? 1 : 0].moves = this.availableMoves(!playerIndex ? 1 : 0, players, values);
this.setState({ round: this.state.round + 1, playerIndex: !playerIndex ? 1 : 0, players });
// Update cell color for the player.
cell.setColor(!playerIndex ? 'gray' : 'silver');
// Update cell value in the grid.
this.grid.current.setValue(x, y, playerIndex + 1);
}
}
isValidMove(x, y, playerIndex, players, values) {
const activePlayer = players[playerIndex];
const opponentPlayer = players[!playerIndex ? 1 : 0];
let isValid = activePlayer.x === -1; // Initialize the first-move to valid.
// Verify this cell is not already used (i.e., it's value is 0).
if (values[y][x]) {
console.log(`Cell ${x},${y} is already taken.`);
}
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) {
let moves = 0;
const activePlayer = players[playerIndex];
if (activePlayer.x !== -1) {
// Up.
for (let y=activePlayer.y - 1; y>=0; y--) {
const count = this.isValidMove(activePlayer.x, y, playerIndex, players, values) ? 1 : 0;
moves += count;
if (!count) {
// Path is blocked from going further.
break;
}
}
// Down.
for (let y=activePlayer.y + 1; y<this.grid.current.props.height; y++) {
const count = this.isValidMove(activePlayer.x, y, playerIndex, players, values) ? 1 : 0;
moves += count;
if (!count) {
// Path is blocked from going further.
break;
}
}
// Left.
for (let x=activePlayer.x - 1; x>=0; x--) {
const count = this.isValidMove(x, activePlayer.y, playerIndex, players, values) ? 1 : 0;
moves += count;
if (!count) {
// Path is blocked from going further.
break;
}
}
// Right.
for (let x=activePlayer.x + 1; x<this.grid.current.props.width; x++) {
const count = this.isValidMove(x, activePlayer.y, playerIndex, players, values) ? 1 : 0;
moves += count;
if (!count) {
// Path is blocked from going further.
break;
}
}
// Up-left.
let x = activePlayer.x;
for (let y=activePlayer.y - 1; y>=0; y--) {
x--;
if (x === -1) {
break;
}
const count = this.isValidMove(x, y, playerIndex, players, values) ? 1 : 0;
moves += count;
if (!count) {
// Path is blocked from going further.
break;
}
}
// Up-right.
x = activePlayer.x;
for (let y=activePlayer.y - 1; y>=0; y--) {
x++;
if (x >= this.grid.current.props.width) {
break;
}
const count = this.isValidMove(x, y, playerIndex, players, values) ? 1 : 0;
moves += count;
if (!count) {
// Path is blocked from going further.
break;
}
}
// Down-left.
x = activePlayer.x;
for (let y=activePlayer.y + 1; y<this.grid.current.props.height; y++) {
x--;
if (x === -1) {
break;
}
const count = this.isValidMove(x, y, playerIndex, players, values) ? 1 : 0;
moves += count;
if (!count) {
// Path is blocked from going further.
break;
}
}
// Down-right.
x = activePlayer.x;
for (let y=activePlayer.y + 1; y<this.grid.current.props.height; y++) {
x++;
if (x >= this.grid.current.props.width) {
break;
}
const count = this.isValidMove(x, y, playerIndex, players, values) ? 1 : 0;
moves += count;
if (!count) {
// Path is blocked from going further.
break;
}
}
}
else {
// First move, all spaces are available. Second move, all spaces but 1 are available.
moves = this.grid.current.props.width * this.grid.current.props.height - (!playerIndex ? 0 : 1);
}
return moves;
}
render() {
const isGameOver = this.state.players.find(player => !player.moves);
const winnerIndex = this.state.players.findIndex(player => player.moves);
return (
<div id='app' ref={ this.container }>
<Grid width={ this.props.width } height={ this.props.height } player={ this.state.player } onClick={ this.onGrid } ref={ this.grid }></Grid>
<Player width="50" height="50" x={ this.state.players[0].x } y={ this.state.players[0].y } color="blue"></Player>
<Player width="50" height="50" x={ this.state.players[1].x } y={ this.state.players[1].y } color="orange"></Player>
<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'>{ this.state.players[this.state.playerIndex].moves } Moves Available</div>
</div>
<div class='col col-auto'>
<div class={ `badge badge-success ${isGameOver ? '' : 'd-none'}` }>Player {winnerIndex + 1} 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>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment