Skip to content

addition of tictac toe game on quark #125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions code/css/tictactoe.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
* {
box-sizing: border-box;
font-family: "Raleway", Helvetica;
}

body {
background: #1266f1;
}

.game-container {
margin-top: 50px;
}

.game-board {
box-shadow: 0 1px 1px 1px rgba(0, 0, 0, 0.15);
width: 400px;
margin: 0 auto;
font-size: 40px;
text-align: center;
line-height: 133.3333333333px;
border-radius: 3px;
overflow: hidden;
}

.game-board-row {
display: flex;
}
.game-board-row:last-child .game-board-space {
border-bottom: 0;
}

.game-board-space {
flex: 1;
height: 133.3333333333px;
cursor: pointer;
transition: all 0.15s;
background: #eeeeee;
border-width: 0 1px 1px 0;
border-style: solid;
border-color: #bbbbbb;
}
.game-board-space.selected-space, .game-board-space.selected-space:hover {
background-color: #333333;
color: white;
border-color: #262525;
}
.game-board-space:nth-child(3) {
border-right: 0;
}
.game-board-space:hover {
background: #e6e6e6;
}

.game-message {
width: 400px;
margin: 0 auto;
background: rgba(237, 2, 206, 0.95);
color: white;
padding: 20px;
margin-bottom: 20px;
text-align: center;
transition: all 0.05s;
cursor: pointer;
border-radius: 3px;
transform: scale(1);
}
.game-message:hover {
background: #ED02CE;
}
.game-message a {
color: #333333;
}

.game-message:empty {
height: 0;
padding: 0;
border: 0;
transform: scale(0);
}
Binary file added code/games/tictactoe/imgs/robot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
252 changes: 252 additions & 0 deletions code/games/tictactoe/scripts/ai.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/*
* Constructs an action that the ai player could make
* @param pos [Number]: the cell position the ai would make its action in
* made that action
*/
var AIAction = function(pos) {

// public : the position on the board that the action would put the letter on
this.movePosition = pos;

//public : the minimax value of the state that the action leads to when applied
this.minimaxVal = 0;

/*
* public : applies the action to a state to get the next state
* @param state [State]: the state to apply the action to
* @return [State]: the next state
*/
this.applyTo = function(state) {
var next = new State(state);

//put the letter on the board
next.board[this.movePosition] = state.turn;

if(state.turn === "O")
next.oMovesCount++;

next.advanceTurn();

return next;
}
};

/*
* public static function that defines a rule for sorting AIActions in ascending manner
* @param firstAction [AIAction] : the first action in a pairwise sort
* @param secondAction [AIAction]: the second action in a pairwise sort
* @return [Number]: -1, 1, or 0
*/
AIAction.ASCENDING = function(firstAction, secondAction) {
if(firstAction.minimaxVal < secondAction.minimaxVal)
return -1; //indicates that firstAction goes before secondAction
else if(firstAction.minimaxVal > secondAction.minimaxVal)
return 1; //indicates that secondAction goes before firstAction
else
return 0; //indicates a tie
}

/*
* public static function that defines a rule for sorting AIActions in descending manner
* @param firstAction [AIAction] : the first action in a pairwise sort
* @param secondAction [AIAction]: the second action in a pairwise sort
* @return [Number]: -1, 1, or 0
*/
AIAction.DESCENDING = function(firstAction, secondAction) {
if(firstAction.minimaxVal > secondAction.minimaxVal)
return -1; //indicates that firstAction goes before secondAction
else if(firstAction.minimaxVal < secondAction.minimaxVal)
return 1; //indicates that secondAction goes before firstAction
else
return 0; //indicates a tie
}


/*
* Constructs an AI player with a specific level of intelligence
* @param level [String]: the desired level of intelligence
*/
var AI = function(level) {

//private attribute: level of intelligence the player has
var levelOfIntelligence = level;

//private attribute: the game the player is playing
var game = {};

/*
* private recursive function that computes the minimax value of a game state
* @param state [State] : the state to calculate its minimax value
* @returns [Number]: the minimax value of the state
*/
function minimaxValue(state) {
if(state.isTerminal()) {
//a terminal game state is the base case
return Game.score(state);
}
else {
var stateScore; // this stores the minimax value we'll compute

if(state.turn === "X")
// X wants to maximize --> initialize to a value smaller than any possible score
stateScore = -1000;
else
// O wants to minimize --> initialize to a value larger than any possible score
stateScore = 1000;

var availablePositions = state.emptyCells();

//enumerate next available states using the info form available positions
var availableNextStates = availablePositions.map(function(pos) {
var action = new AIAction(pos);

var nextState = action.applyTo(state);

return nextState;
});

/* calculate the minimax value for all available next states
* and evaluate the current state's value */
availableNextStates.forEach(function(nextState) {
var nextScore = minimaxValue(nextState);
if(state.turn === "X") {
// X wants to maximize --> update stateScore iff nextScore is larger
if(nextScore > stateScore)
stateScore = nextScore;
}
else {
// O wants to minimize --> update stateScore iff nextScore is smaller
if(nextScore < stateScore)
stateScore = nextScore;
}
});

return stateScore;
}
}

/*
* private function: make the ai player take a blind move
* that is: choose the cell to place its symbol randomly
* @param turn [String]: the player to play, either X or O
*/
function takeABlindMove(turn) {
var available = game.currentState.emptyCells();
var randomCell = available[Math.floor(Math.random() * available.length)];
var action = new AIAction(randomCell);

var next = action.applyTo(game.currentState);

ui.insertAt(randomCell, turn);

game.advanceTo(next);
}

/*
* private function: make the ai player take a novice move,
* that is: mix between choosing the optimal and suboptimal minimax decisions
* @param turn [String]: the player to play, either X or O
*/
function takeANoviceMove(turn) {
var available = game.currentState.emptyCells();

//enumerate and calculate the score for each available actions to the ai player
var availableActions = available.map(function(pos) {
var action = new AIAction(pos); //create the action object
var nextState = action.applyTo(game.currentState); //get next state by applying the action

action.minimaxVal = minimaxValue(nextState); //calculate and set the action's minimax value

return action;
});

//sort the enumerated actions list by score
if(turn === "X")
//X maximizes --> sort the actions in a descending manner to have the action with maximum minimax at first
availableActions.sort(AIAction.DESCENDING);
else
//O minimizes --> sort the actions in an ascending manner to have the action with minimum minimax at first
availableActions.sort(AIAction.ASCENDING);

/*
* take the optimal action 40% of the time, and take the 1st suboptimal action 60% of the time
*/
var chosenAction;
if(Math.random()*100 <= 40) {
chosenAction = availableActions[0];
}
else {
if(availableActions.length >= 2) {
//if there is two or more available actions, choose the 1st suboptimal
chosenAction = availableActions[1];
}
else {
//choose the only available actions
chosenAction = availableActions[0];
}
}
var next = chosenAction.applyTo(game.currentState);

ui.insertAt(chosenAction.movePosition, turn);

game.advanceTo(next);
};

/*
* private function: make the ai player take a master move,
* that is: choose the optimal minimax decision
* @param turn [String]: the player to play, either X or O
*/
function takeAMasterMove(turn) {
var available = game.currentState.emptyCells();

//enumerate and calculate the score for each avaialable actions to the ai player
var availableActions = available.map(function(pos) {
var action = new AIAction(pos); //create the action object
var next = action.applyTo(game.currentState); //get next state by applying the action

action.minimaxVal = minimaxValue(next); //calculate and set the action's minmax value

return action;
});

//sort the enumerated actions list by score
if(turn === "X")
//X maximizes --> sort the actions in a descending manner to have the action with maximum minimax at first
availableActions.sort(AIAction.DESCENDING);
else
//O minimizes --> sort the actions in an ascending manner to have the action with minimum minimax at first
availableActions.sort(AIAction.ASCENDING);


//take the first action as it's the optimal
var chosenAction = availableActions[0];
var next = chosenAction.applyTo(game.currentState);

ui.insertAt(chosenAction.movePosition, turn);

game.advanceTo(next);
}


/*
* public method to specify the game the ai player will play
* @param _game [Game] : the game the ai will play
*/
this.plays = function(_game){
game = _game;
};

/*
* public function: notify the ai player that it's its turn
* @param turn [String]: the player to play, either X or O
*/
this.notify = function(turn) {
switch(levelOfIntelligence) {
//invoke the desired behavior based on the level chosen
case "blind": takeABlindMove(turn); break;
case "novice": takeANoviceMove(turn); break;
case "master": takeAMasterMove(turn); break;
}
};
};
63 changes: 63 additions & 0 deletions code/games/tictactoe/scripts/control.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* object to contain all items accessable to all control functions
*/
var globals = {};

/*
* choosing difficulty level (onclick span.level) behavior and control
* when a level is clicked, it becomes highlighted and the "ai.level" variable
* is set to the chosen level
*/
$(".level").each(function() {
var $this = $(this);
$this.click(function() {
$('.selected').toggleClass('not-selected');
$('.selected').toggleClass('selected');
$this.toggleClass('not-selected');
$this.toggleClass('selected');

ai.level = $this.attr("id");
});
});

/*
* start game (onclick div.start) behavior and control
* when start is clicked and a level is chosen, the game status changes to "running"
* and UI view to swicthed to indicate that it's human's trun to play
*/
$(".start").click(function() {
var selectedDiffeculty = $('.selected').attr("id");
if(typeof selectedDiffeculty !== "undefined") {
var aiPlayer = new AI(selectedDiffeculty);
globals.game = new Game(aiPlayer);

aiPlayer.plays(globals.game);

globals.game.start();
}
});

/*
* click on cell (onclick div.cell) behavior and control
* if an empty cell is clicked when the game is running and its the human player's trun
* get the indecies of the clickd cell, craete the next game state, upadet the UI, and
* advance the game to the new created state
*/
$(".cell").each(function() {
var $this = $(this);
$this.click(function() {
if(globals.game.status === "running" && globals.game.currentState.turn === "X" && !$this.hasClass('occupied')) {
var indx = parseInt($this.data("indx"));

var next = new State(globals.game.currentState);
next.board[indx] = "X";

ui.insertAt(indx, "X");

next.advanceTurn();

globals.game.advanceTo(next);

}
})
});
Loading