diff --git a/build/css/app.css b/build/css/app.css new file mode 100644 index 0000000..d726163 --- /dev/null +++ b/build/css/app.css @@ -0,0 +1,84 @@ +body { + font-family: 'Mallanna', sans-serif; +} +main div { + background-color: #0abab5; + height: 140px; + width: 140px; + margin: 1% 1% 1% 1%; + padding: 1% 1% 1% 1%; + font-size: 75px; + display: inline-block; + vertical-align: top; + transition-duration: 0.4s; +} + +main div:hover { + opacity: 0.5; +} + +#game, #score, td, h1, main div { + text-align: center; +} + +#score { + width: 100%; +} + +#score div{ + font-size: 15px; + display: inline-block; + margin: 0 30px 0 30px; +} + +#board, h1, h3 { + margin: auto; +} + +#board { + height: 500px; + width: 500px; +} + +h1 { + font-size: 75px; +} + +h3 { + font-size: 25px; +} + +th, td { + font-size: 20px; +} + +button { + background-color: white; + font-family: 'Mallanna', sans-serif; + font-size: 20px; + border: 3px solid black; + margin: 0 5px 10px 5px; + transition-duration: 0.4s; + width: 125px; +} + +button:hover { + background-color: black; + color: white; +} + +#clear { + border: 3px solid #99ffcd; +} + +#clear:hover { + background-color: #99ffcd; +} + +.win { + color: green; +} + +.lose { + color: red; +} diff --git a/build/index.html b/build/index.html index 00eb541..2aa0a71 100644 --- a/build/index.html +++ b/build/index.html @@ -3,8 +3,37 @@ Tic-Tac-Toe + + +

Tic-Tac-Toe

+
+
+ + + +
+
+ + + + diff --git a/spec/game.spec.js b/spec/game.spec.js new file mode 100644 index 0000000..3522e3f --- /dev/null +++ b/spec/game.spec.js @@ -0,0 +1,149 @@ +import Game from "app/models/game"; + +describe('Game', function() { + + var testGame; + beforeEach(function() { + testGame = new Game("elle", "jessica"); + }); + + + describe('Game', function() { + it('should return player name', function() { + expect(testGame.player1.get("name")).toEqual("ELLE"); + expect(testGame.player2.get("name")).toEqual("JESSICA"); + }); + + it('should assign letter X to player 1 and letter O to player 2', function() { + expect(testGame.player1.get("letter")).toEqual("X"); + expect(testGame.player2.get("letter")).toEqual("O"); + expect(testGame.player1.get("letter")).not.toEqual("O"); + expect(testGame.player2.get("letter")).not.toEqual("X"); + }); + + }); + + + describe('winCheck', function() { + it('should return true if there is a winner', function() { + expect(testGame.winCheck(["X", "X", "X", " ", " ", " ", " ", " ", " "])).toEqual(true); + }); + + it('should return false with an empty board', function() { + expect(testGame.winCheck([" ", " ", " ", " ", " ", " ", " ", " ", " "])).toEqual(false); + }); + + it('should return false if there is not a winner yet', function() { + expect(testGame.winCheck(["X", "X", " ", "O", "O", " ", " ", " ", " "])).toEqual(false); + }); + + it('should return false when the game ends in draw', function() { + expect(testGame.winCheck(["X", "X", "O", "O", "O", "X", "X", "O", "X"])).toEqual(false); + }); + }); // describe winCheck end + + + describe('play', function() { + it('should return move in board', function() { + expect(testGame.play(2)).toEqual([" ", " ", "X", " ", " ", " ", " ", " ", " "]); + expect(testGame.play(1)).toEqual([" ", "O", "X", " ", " ", " ", " ", " ", " "]); + }); + + it('should throw an error if move made in spot that is taken', function() { + expect(testGame.play(2)).toEqual([" ", " ", "X", " ", " ", " ", " ", " ", " "]); + expect(function() { testGame.play(2)}).toThrow(TypeError("Please choose a valid move.")); + }); + + it('should throw an error if move outside 0-8 are used', function() { + expect(function() { testGame.play(10)}).toThrow(TypeError("Please choose a valid move.")); + }); + + it('should throw an error if move is a string', function() { + expect(function() { testGame.play("a")}).toThrow(TypeError("Please choose a valid move.")); + }); + + it('should return the board after a win', function() { + testGame.play(6); + testGame.play(4); + testGame.play(7); + testGame.play(5); + expect(testGame.play(8)).toEqual([" ", " ", " ", " ", "O", "O", "X", "X", "X"]) + }); + + it('should return the board after a draw', function() { + testGame.play(0); + testGame.play(2); + testGame.play(1); + testGame.play(3); + testGame.play(5); + testGame.play(4); + testGame.play(6); + testGame.play(7); + expect(testGame.play(8)).toEqual(["X", "X", "O", "O", "O", "X", "X", "O", "X"]); + }); + }); //describe play end + + + describe('turnHandler', function() { + it('should increment turnCounter', function() { + expect(testGame.turnHandler()).toEqual(1); + expect(testGame.turnHandler()).toEqual(2); + }); + + it('should swap activePlayer', function() { + expect(testGame.activePlayer.get("name")).toEqual("ELLE"); + expect(testGame.turnHandler()).toEqual(1); + expect(testGame.activePlayer.get("name")).toEqual("JESSICA"); + expect(testGame.turnHandler()).toEqual(2); + expect(testGame.turnHandler()).toEqual(3); + }); + }); + + + describe('scoreKeeper', function() { + it('should increment scorecard when a player wins/player loses', function() { + // currentBoard = [" ", " ", " ", " ", "O", "O", "X", "X", "X"]; + testGame.play(6); + testGame.play(4); + testGame.play(7); + testGame.play(5); + testGame.play(8); + expect(testGame.player1.get("scorecard").win).toEqual(1); + expect(testGame.player1.get("scorecard").lose).toEqual(0); + expect(testGame.player2.get("scorecard").lose).toEqual(1); + expect(testGame.player2.get("scorecard").win).toEqual(0); + }); + + it('should increment scorecards when game ends in draw', function() { + // currentBoard = ["X", "X", "O", "O", "O", "X", "X", "O", "X"] + testGame.play(0); + testGame.play(2); + testGame.play(1); + testGame.play(3); + testGame.play(5); + testGame.play(4); + testGame.play(6); + testGame.play(7); + testGame.play(8); + expect(testGame.player1.get("scorecard").draw).toEqual(1); + expect(testGame.player2.get("scorecard").draw).toEqual(1); + expect(testGame.player1.get("scorecard").draw).not.toEqual(0); + expect(testGame.player2.get("scorecard").draw).not.toEqual(0); + }); + }); + + + describe('newGame', function() { + it('should restart a new game with a clean board', function() { + testGame.play(6); + testGame.play(4); + testGame.play(7); + testGame.play(5); + expect(testGame.play(8)).toEqual([" ", " ", " ", " ", "O", "O", "X", "X", "X"]); + testGame.newGame(); + expect(testGame.play(1)).toEqual([" ", "X", " ", " ", " ", " ", " ", " ", " "]); + expect(testGame.turnHandler()).toEqual(2); + }); + }); + +}); diff --git a/spec/player.spec.js b/spec/player.spec.js new file mode 100644 index 0000000..6a8abac --- /dev/null +++ b/spec/player.spec.js @@ -0,0 +1,28 @@ +import Player from "app/models/player"; + +describe('Player', function() { + var player1; + var player2; + beforeEach(function() { + player1 = new Player("elle", "X"); + player2 = new Player("jessica", "O"); + }); + + describe('get name assigned to player', function() { + it('should return the name of the player', function() { + expect(player1.get("name")).toEqual("ELLE"); + expect(player1.get("letter")).toEqual("X"); + expect(player2.get("name")).toEqual("JESSICA"); + expect(player2.get("letter")).toEqual("O"); + }); + }); + + describe('get scorecard assigned to player with defaults of 0', function() { + it('should return the player scorecard', function() { + expect(player1.get("scorecard")).toEqual({ win:0, lose: 0, draw: 0}); + expect(player1.get("scorecard").win).toEqual(0); + }); + }); + + +}); diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..4ff0225 --- /dev/null +++ b/src/app.js @@ -0,0 +1,17 @@ +import $ from 'jquery'; +import Game from 'app/models/game'; +import GameView from 'app/views/game_view'; + +$(document).ready(function() { + +var game = new Game("player 1", "player 2"); +game.fetch(); + +var gameView = new GameView({ + el: '#game', + model: game +}); + +gameView.render(); + +}); diff --git a/src/app/collections/board.js b/src/app/collections/board.js new file mode 100644 index 0000000..e69de29 diff --git a/src/app/models/game.js b/src/app/models/game.js new file mode 100644 index 0000000..6e30c1a --- /dev/null +++ b/src/app/models/game.js @@ -0,0 +1,90 @@ +import Player from "app/models/player"; +import Backbone from 'backbone'; + +const Game = Backbone.Model.extend({ + + url: 'http://localhost:3000/api/v1/games', + + initialize: function(player1, player2) { + this.player1 = new Player(player1, "X"); + this.player2 = new Player(player2, "O"); + this.activePlayer = this.player1; + this.inactivePlayer = this.player2; + + this.set("board", [" ", " ", " ", " ", " ", " ", " ", " ", " "]); + this.set("players", [this.player1.get("name"), this.player2.get("name")]); + this.set("outcome", null); + this.turnCounter = 0; + }, + + play: function(move) { + if (move >= 0 && move < 9 && this.get("board")[move] == " " && this.winCheck(this.get("board")) == false && this.turnCounter < 9) { + this.get("board")[move] = this.activePlayer.get("letter"); + this.scoreKeeper(); + this.turnHandler(); + return this.get("board"); + } else { + throw new TypeError("Please choose a valid move."); + } + }, + + winCheck: function(board) { + if (board[0] == board[1] && board[1] == board[2] && board[0] != " ") { + return true; + } else if (board[3] == board[4] && board[4] == board[5] && board[3] != " ") { + return true; + } else if (board[6] == board[7] && board[7] == board[8] && board[6] != " ") { + return true; + } else if (board[0] == board[3] && board[3] == board[6] && board[0] != " ") { + return true; + } else if (board[1] == board[4] && board[4] == board[7] && board[1] != " ") { + return true; + } else if (board[2] == board[5] && board[5] == board[8] && board[2] != " ") { + return true; + } else if (board[2] == board[4] && board[4] == board[6] && board[2] != " ") { + return true; + } else if (board[0] == board[4] && board[4] == board[8] && board[0] != " ") { + return true; + } else { + return false; + } + }, + + turnHandler: function () { + this.turnCounter += 1; + if (this.turnCounter % 2 == 0) { + this.activePlayer = this.player1; + this.inactivePlayer = this.player2; + } else { + this.activePlayer = this.player2; + this.inactivePlayer = this.player1; + } + return this.turnCounter; + }, + + scoreKeeper: function() { + if (this.winCheck(this.get("board")) == true) { + this.activePlayer.get("scorecard").win += 1; + this.inactivePlayer.get("scorecard").lose += 1; + this.set("outcome", this.activePlayer.get("letter")); + } else if (this.turnCounter == 8 && this.winCheck(this.get("board")) == false) { + this.activePlayer.get("scorecard").draw += 1; + this.inactivePlayer.get("scorecard").draw += 1; + this.set("outcome", "draw"); + } + }, + + newGame: function() { + this.set("board", [" ", " ", " ", " ", " ", " ", " ", " ", " "]); + this.turnCounter = 0; + this.activePlayer = this.player1; + this.inactivePlayer = this.player2; + }, + + saveGame: function() { + this.save(); + } + +}); + +export default Game; diff --git a/src/app/models/player.js b/src/app/models/player.js new file mode 100644 index 0000000..7da8157 --- /dev/null +++ b/src/app/models/player.js @@ -0,0 +1,11 @@ +import Backbone from 'backbone'; + +const Player = Backbone.Model.extend({ + initialize: function(name, letter) { + this.set("name", name.toUpperCase()); + this.set("letter", letter); + this.set("scorecard", { win:0, lose: 0, draw: 0}); + } +}); + +module.exports = Player; diff --git a/src/app/views/board_view.js b/src/app/views/board_view.js new file mode 100644 index 0000000..8146b23 --- /dev/null +++ b/src/app/views/board_view.js @@ -0,0 +1,29 @@ +import $ from 'jquery'; +import Backbone from 'backbone'; +import SpaceView from 'app/views/space_view'; + +const BoardView = Backbone.View.extend({ + initialize: function() { + }, + + render: function() { + this.trigger('updateScore', this.model); + const board = this.$el; + var self = this; + var moveSpace = 0; + board.empty(); + this.model.get("board").forEach(function(input) { + const space = new SpaceView({ + model: self.model, + move: moveSpace, + letter: input + }); + moveSpace += 1; + this.listenTo(space, 'updateBoard', this.render); + board.append(space.render().$el); + }, this); + }, + +}) + +export default BoardView; diff --git a/src/app/views/game_view.js b/src/app/views/game_view.js new file mode 100644 index 0000000..1eacadc --- /dev/null +++ b/src/app/views/game_view.js @@ -0,0 +1,60 @@ +import $ from 'jquery'; +import Backbone from 'backbone'; +import BoardView from 'app/views/board_view'; +import PlayerView from 'app/views/player_view'; + +const GameView = Backbone.View.extend({ + initialize: function() { + + }, + + render: function() { + const boardView = new BoardView({ + model: this.model, + el: this.$('main') + }); + boardView.render(); + + this.listenTo(boardView, 'updateScore', this.render); + this.$('#score').empty(); + const playerOne = new PlayerView({ + model: this.model.player1, + }); + this.$('#score').append(playerOne.render().$el) + + const playerTwo = new PlayerView({ + model: this.model.player2, + }); + this.$('#score').append(playerTwo.render().$el) + + if(this.model.activePlayer == this.model.player1) { + playerOne.render().$('h3').css({"border-top": "1px solid black", "border-bottom": "1px solid black"}) + } else { + playerTwo.render().$('h3').css({"border-top": "1px solid black", "border-bottom": "1px solid black"}) + } + + if(this.model.winCheck(this.model.get("board")) == true) { + alert(this.model.inactivePlayer.get("name") + " IS THE WINNER!"); + this.model.saveGame(); + } else if(this.model.turnCounter == 9 && this.model.winCheck(this.model.get("board")) == false) { + alert("IT IS A DRAW!"); + this.model.saveGame(); + } + }, + + events: { + 'click #clear': 'newGame' + // 'click #save': 'saveGame' + }, + + newGame: function(){ + this.model.newGame(); + this.render(); + } + + // saveGame: function(){ + // console.log('saved ya'); + // } +}) + +export default GameView; diff --git a/src/app/views/player_view.js b/src/app/views/player_view.js new file mode 100644 index 0000000..74a0aa3 --- /dev/null +++ b/src/app/views/player_view.js @@ -0,0 +1,18 @@ +import $ from 'jquery'; +import Backbone from 'backbone'; +import _ from 'underscore'; + +const PlayerView = Backbone.View.extend({ + initialize: function(options){ + this.playerTemplate = _.template($('#tmpl-player-details').html()); + }, + + render: function(){ + var html = this.playerTemplate(this.model.attributes); + this.$el.html(html); + return this; + } + +}); + +export default PlayerView; diff --git a/src/app/views/space_view.js b/src/app/views/space_view.js new file mode 100644 index 0000000..dd5430f --- /dev/null +++ b/src/app/views/space_view.js @@ -0,0 +1,26 @@ +import $ from 'jquery'; +import Backbone from 'backbone'; + +const SpaceView = Backbone.View.extend({ + initialize: function(options) { + this.letter = options.letter; + this.move = options.move; + }, + + render: function() { + this.$el.html(this.letter); + return this; + }, + + events: { + 'click': 'getMove' + }, + + getMove: function(event){ + this.model.play(this.move); + this.trigger('updateBoard', this.model); + } + +}) + +export default SpaceView;