<div class="title">Tic Tac Toe</div>
<div id="content" class="container"></div>
// Google Fonts
@import url(https://fonts.googleapis.com/css?family=Roboto+Condensed|Roboto+Slab);
/// Font Variables
$primary: 'Roboto Condensed', sans-serif;
$secondary: 'Roboto Slab', serif;
//Color Variables
$white: #FFFFFF;
$black: #000000;
$grey: #E4DBBF;
$red: #e74c3c;
$dark: #383127;
$belizeHole: #2980b9;
$nephiritis: #27ae60;
$emerald: #2ecc71;
$wetAsphalt: #34495e;
$midnightBlue: #2c3e50;
/* Cell variables */
//cell border
$fieldBorder: 1px solid $emerald;
//cell size
$fieldSize: 100px;
body{
color: $midnightBlue;
font-family: $primary;
background-color: $grey;
}
.title{
padding: 15px;
margin: 0;
text-align: center;
font-family: $secondary;
font-weight: bold;
font-size: 32px;
}
#content{
.game{
display: table;
margin: 0 auto;
.board{
margin-bottom: 10px;
border: $fieldBorder;
.field{
float: left;
width: $fieldSize;
height: $fieldSize;
line-height: $fieldSize;
background-color: $nephiritis;
text-align: center;
border: $fieldBorder;
&:nth-child(3n+1){
clear: left;
}
@media screen and (max-width: 340px) {
width: 80px;
height: 80px;
}
.mark{
font-size: 50px;
color: $white;
}
}
}
.settings{
}
.resultPanel{
.result{
font-size: 26px;
font-weight: bold;
text-align: center;
}
.startAgain{
@extend .button;
display: block;
}
}
}
}
fieldset {
border: 2px solid $wetAsphalt;
background: #ddd;
border-radius: 5px;
padding: 10px;
legend {
background: $wetAsphalt;
color: #fff;
padding: 5px 10px;
font-size: 16px;
border-radius: 5px;
box-shadow: 0 0 0 5px #ddd;
margin-left: 10px;
}
input[type="radio"]{
margin: 0 10px 0 10px;
}
}
.button{
background-color: $belizeHole;
color: $white;
font-weight: bold;
width: 100%;
margin: 5px 0;
padding: 10px;
border: none;
border-radius: 5px;
&:hover{
cursor: pointer;
}
&.selectedButton{
background-color: $black;
color: $white;
}
}
View Compiled
var timer;
var Field = React.createClass({
shouldComponentUpdate: function(nextProps, nextState) {
return nextProps.mark !== this.props.mark;
},
markField: function(){
this.props.markField(this.props.index);
},
render: function() {
return (
<div className="field" onClick={this.markField}>
<span className="mark">{this.props.mark}</span>
</div>
);
}
});
var Board = React.createClass({
render: function() {
return (
<div className="board">
{
this.props.gameBoard.map(function(mark, i){
return (
<Field mark={mark} key={i} index={i} markField={this.props.markField}/>
)
}, this)
}
<div style={{"clear":"both"}}></div>
</div>
);
}
});
var StartAgain = React.createClass({
shouldComponentUpdate: function() {
return false;
},
render: function(){
return (
<button className="startAgain" onClick={this.props.startAgain}>{this.props.children}</button>
);
}
});
var ResultPanel = React.createClass({
shouldComponentUpdate: function(nextProps, nextState) {
return nextProps.result !== this.props.result;
},
render: function() {
return (
<div className="resultPanel">
<p className="result">{this.props.result}</p>
<StartAgain startAgain={this.props.startAgain.bind(null, true)}>Start Again</StartAgain>
<StartAgain startAgain={this.props.startAgain.bind(null, false)}>Start Again With New Settings</StartAgain>
</div>
);
}
});
var Settings = React.createClass({
getInitialState: function() {
return {
mark: "x",
starter: "you",
mode: "easy"
};
},
onMarkChanged: function(e){
this.setState({
mark: e.currentTarget.value
});
},
onStarterChanged: function(e){
this.setState({
starter: e.currentTarget.value
});
},
onModeChanged: function(e){
this.setState({
mode: e.currentTarget.value
});
},
handleSubmit: function(e){
e.preventDefault();
this.props.handleSubmittedSettings(this.state.mark === "x", this.state.starter === "you", this.state.mode === "hard");
},
render: function() {
return (
<form className="settings" onSubmit={this.handleSubmit}>
<fieldset>
<legend>Please select your mark</legend>
<input type="radio" name="mark" id="x" value="x" checked={this.state.mark === "x"} onChange={this.onMarkChanged}/><label htmlFor="x">X</label>
<input type="radio" name="mark" id="o" value="o" checked={this.state.mark === "o"} onChange={this.onMarkChanged}/><label htmlFor="o">O</label>
</fieldset>
<fieldset>
<legend>Who should start first?</legend>
<input type="radio" name="starter" id="you" value="you" checked={this.state.starter === "you"} onChange={this.onStarterChanged}/><label htmlFor="you">You</label>
<input type="radio" name="starter" id="cpu" value="cpu" checked={this.state.starter === "cpu"} onChange={this.onStarterChanged}/><label htmlFor="cpu">CPU</label>
</fieldset>
<fieldset>
<legend>Please choose the level of difficulty</legend>
<input type="radio" name="mode" id="easy" value="easy" checked={this.state.mode === "easy"} onChange={this.onModeChanged}/><label htmlFor="easy">Easy</label>
<input type="radio" name="mode" id="hard" value="hard" checked={this.state.mode === "hard"} onChange={this.onModeChanged}/><label htmlFor="hard">Hard</label>
</fieldset>
<button className="button">Start</button>
</form>
);
}
});
var Game = React.createClass({
getInitialState: function() {
return {
playerMarkIsX: false,
gameIsRunning: false,
playerTurn: false,
playerStartsFirst: false,
gameBoard: ["", "","","","","","","",""],
result: "",
hardMode: false
};
},
mark(isPlayer){
return isPlayer ? (this.state.playerMarkIsX ? "x" : "o") : (this.state.playerMarkIsX ? "o" : "x");
},
playerMark: function(){
return this.mark(true);
},
cpuMark: function(){
return this.mark(false);
},
startAgain: function(theSameAsPrevious){
clearTimeout(timer);
if(theSameAsPrevious){
this.setState({
gameBoard: ["", "","","","","","","",""],
result: "",
playerTurn: this.state.playerStartsFirst
}, function(){
this.startGame(this.state.playerStartsFirst);
});
}
else{
this.setState(this.getInitialState());
}
},
winMove: function(field, board){
var gameBoard = board.slice();
var hor = 3 * Math.floor(field/3);
var result = false;
if(gameBoard[hor] == gameBoard[hor + 1] && gameBoard[hor] == gameBoard[hor + 2]){
result = true;
}
var ver = field % 3;
if(gameBoard[ver] == gameBoard[ver + 3] && gameBoard[ver] == gameBoard[ver + 6]){
result = true;
}
if(field == 0 || field == 4 || field == 8){
if(gameBoard[0] == gameBoard[4] && gameBoard[0] == gameBoard[8]){
result = true;
}
}
if(field == 2 || field == 4 || field == 6){
if(gameBoard[2] == gameBoard[4] && gameBoard[2] == gameBoard[6]){
result = true;
}
}
return result;
},
isBoardEmpty: function(board){
var gameBoard = board.slice();
for(var k=0; k < gameBoard.length; k++){
if(gameBoard[k] !== ""){
return false;
}
}
return true;
},
fullBoard: function(board){
var gameBoard = board.slice();
for(var k=0; k < gameBoard.length; k++){
if(gameBoard[k] == ""){
return false;
}
}
return true;
},
isMarked: function(field){
return this.state.gameBoard[field] != "";
},
checkChanges(isPlayer, field){
if(this.winMove(field, this.state.gameBoard)){
this.setState({
playerTurn: false,
result: isPlayer ? "You Won!" : "CPU Won!"
});
}
else if(this.fullBoard(this.state.gameBoard)){
this.setState({
playerTurn: false,
result: "Draw"
});
}
else{
if(isPlayer){
timer=setTimeout(this.compChoice, 1000);
}
}
},
markField: function(field){
if(this.state.playerTurn){
var gameBoard = this.state.gameBoard.slice();
if(!this.isMarked(field)){
gameBoard[field] = this.playerMark();
this.setState({
gameBoard: gameBoard,
playerTurn: false
}, function(){
this.checkChanges(true, field);
});
}
}
},
compChoiceEasyMode(){
console.log("easy choice");
var freeIndexArray = [];
var gameBoard = this.state.gameBoard.slice();
for(var i=0; i < gameBoard.length; i++){
if(gameBoard[i] == "") freeIndexArray.push(i);
}
var random = Math.floor(Math.random() * freeIndexArray.length);
var k = freeIndexArray[random];
gameBoard[k] = this.cpuMark();
this.setState({
gameBoard: gameBoard,
playerTurn: true
}, function(){
this.checkChanges(false, k);
});
},
compChoiceHardMode(){
console.log("hard choice");
function minmax(field, maximizingPlayer, self){
if(self.winMove(field, board)){
if(maximizingPlayer){
return 10;
}
return -10;
}
if(self.fullBoard(board)){
return 0;
}
if(maximizingPlayer){
var bestValue = Infinity;
for(var i=0; i < 9; i++){
if(board[i] == ""){
board[i] = self.playerMark();
var val = minmax(i, false, self);
bestValue = Math.min(bestValue, val);
board[i] = "";
}
}
return bestValue;
}
else {
var bestValue = -Infinity;
for(var i=0; i < 9; i++){
if(board[i] == ""){
board[i] = self.cpuMark();
var val = minmax(i, true, self);
bestValue = Math.max(bestValue, val);
board[i] = "";
}
}
return bestValue;
}
}
var board = this.state.gameBoard.slice();
var indexMax = -1;
var max = -Infinity;
if(this.state.playerStartsFirst === false && this.isBoardEmpty(board)){
var firstChoiceArray = [0, 2, 4, 6, 8];
var random = Math.floor(Math.random() * firstChoiceArray.length);
var indexMax = firstChoiceArray[random];
}
else{
var results = [];
for(var i=0; i < 9; i++){
if(board[i] == ""){
board[i] = this.cpuMark();
var res = minmax(i, true, this);
results.push(res);
if(res > max){
max = res;
indexMax = i;
}
board[i] = "";
}
}
}
board[indexMax] = this.cpuMark();
this.setState({
gameBoard: board,
playerTurn: true
}, function(){
this.checkChanges(false, indexMax);
});
},
compChoice: function(){
this.state.hardMode ? this.compChoiceHardMode() : this.compChoiceEasyMode();
},
startGame: function(playerStarts){
if(!playerStarts){
timer=setTimeout(this.compChoice, 1000);
}
},
handleSubmittedSettings: function(playerMarkIsX, playerStarts, hardMode){
this.setState({
playerMarkIsX: playerMarkIsX,
playerTurn: playerStarts,
playerStartsFirst: playerStarts,
gameIsRunning: true,
hardMode: hardMode
}, function(){
this.startGame(playerStarts);
});
},
render: function() {
var resultPanelOrSettings = !this.state.gameIsRunning ? <Settings handleSubmittedSettings={this.handleSubmittedSettings} /> : <ResultPanel result={this.state.result} startAgain={this.startAgain}/>
return (
<div className="game">
<Board gameBoard={this.state.gameBoard} markField={this.markField} />
{resultPanelOrSettings}
</div>
);
}
});
ReactDOM.render(
<Game />,
document.getElementById('content')
);
View Compiled
This Pen doesn't use any external CSS resources.