Created
October 22, 2019 19:04
-
-
Save primaryobjects/2b1ecf7ff12bfb695c18a1b4ed9a9fc8 to your computer and use it in GitHub Desktop.
Isolation game https://codepen.io/primaryobjects/pen/QWWGgmR
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class='container'> | |
<div id='root'></div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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