├── public ├── app │ ├── js │ │ ├── components │ │ │ ├── user │ │ │ │ ├── userCtrl.js │ │ │ │ └── userService.js │ │ │ ├── aceEditor │ │ │ │ ├── opponentEditorTmpl.html │ │ │ │ ├── userEditorTmpl.html │ │ │ │ ├── opponentEditorDirective.js │ │ │ │ ├── initiateEditor.js │ │ │ │ └── userEditorDirective.js │ │ │ ├── modals │ │ │ │ ├── waitingForOpponent.html │ │ │ │ ├── winnerModal.html │ │ │ │ ├── opponentLeft.html │ │ │ │ ├── loserModal.html │ │ │ │ └── timer.js │ │ │ └── socket │ │ │ │ └── socketFactory.js │ │ ├── app.js │ │ └── appController.js │ ├── .DS_Store │ ├── images │ │ ├── .DS_Store │ │ ├── favicon.ico │ │ ├── codercombat.png │ │ ├── cc_placeholder.png │ │ ├── coder_combat2.png │ │ ├── coder-combat-first.png │ │ └── coder-combat-small.png │ ├── styles │ │ ├── landing-page.css │ │ └── app-styles.css │ └── app.html └── index.html ├── .gitignore ├── server-assets ├── routes.js ├── questionData.js └── gameLogic.js ├── package.json ├── _SpecRunner.html ├── server.js ├── Gruntfile.js └── README.md /public/app/js/components/user/userCtrl.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/app/js/components/user/userService.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/app/js/components/aceEditor/opponentEditorTmpl.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | .sass-cache 5 | app/bower_components 6 | tests 7 | -------------------------------------------------------------------------------- /public/app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylermcginnis/CoderCombat/HEAD/public/app/.DS_Store -------------------------------------------------------------------------------- /server-assets/routes.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = function(app) { 4 | 5 | } -------------------------------------------------------------------------------- /public/app/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylermcginnis/CoderCombat/HEAD/public/app/images/.DS_Store -------------------------------------------------------------------------------- /public/app/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylermcginnis/CoderCombat/HEAD/public/app/images/favicon.ico -------------------------------------------------------------------------------- /public/app/images/codercombat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylermcginnis/CoderCombat/HEAD/public/app/images/codercombat.png -------------------------------------------------------------------------------- /public/app/images/cc_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylermcginnis/CoderCombat/HEAD/public/app/images/cc_placeholder.png -------------------------------------------------------------------------------- /public/app/images/coder_combat2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylermcginnis/CoderCombat/HEAD/public/app/images/coder_combat2.png -------------------------------------------------------------------------------- /public/app/images/coder-combat-first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylermcginnis/CoderCombat/HEAD/public/app/images/coder-combat-first.png -------------------------------------------------------------------------------- /public/app/images/coder-combat-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylermcginnis/CoderCombat/HEAD/public/app/images/coder-combat-small.png -------------------------------------------------------------------------------- /public/app/js/components/aceEditor/userEditorTmpl.html: -------------------------------------------------------------------------------- 1 |
function theAlgorithm(input){ 2 | {{question}} 3 | return input; 4 | }
5 | -------------------------------------------------------------------------------- /public/app/js/components/modals/waitingForOpponent.html: -------------------------------------------------------------------------------- 1 | 4 | 8 | -------------------------------------------------------------------------------- /public/app/js/components/modals/winnerModal.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/app/js/components/modals/opponentLeft.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/app/js/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('CC', ['ui.bootstrap']); 2 | 3 | app.run(function(initiateEditor){ 4 | initiateEditor.getQuestions() 5 | .then(function(data){ 6 | initiateEditor.setQuestionData(data); 7 | }, function(data){ 8 | console.log(data); 9 | }) 10 | }) 11 | 12 | -------------------------------------------------------------------------------- /public/app/js/components/modals/loserModal.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CoderCombat2.0", 3 | "version": "0.0.1", 4 | "devDependencies": { 5 | "grunt": "~0.4.5", 6 | "grunt-contrib-jshint": "~0.10.0", 7 | "grunt-contrib-jasmine": "~0.6.4", 8 | "grunt-contrib-watch": "~0.6.1", 9 | "express": "~4.2.0", 10 | "socket.io": "~0.9.16" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /public/app/js/components/modals/timer.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('CC'); 2 | 3 | app.directive('timer', function($interval){ 4 | return { 5 | restrict: 'E', 6 | link: function(scope, ele, attrs){ 7 | $interval(function(){ 8 | ele.html(parseInt(ele.html() -1, 10)); 9 | }, 1000); 10 | } 11 | } 12 | }) -------------------------------------------------------------------------------- /public/app/js/components/socket/socketFactory.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('CC'); 2 | app.factory('socket', function ($rootScope) { 3 | var socket = io.connect('http://localhost'); 4 | return { 5 | on: function (eventName, callback) { 6 | socket.on(eventName, function () { 7 | var args = arguments; 8 | $rootScope.$apply(function () { 9 | callback.apply(socket, args); 10 | }); 11 | }); 12 | }, 13 | emit: function (eventName, data, callback) { 14 | socket.emit(eventName, data, function () { 15 | var args = arguments; 16 | $rootScope.$apply(function () { 17 | if (callback) { 18 | callback.apply(socket, args); 19 | } 20 | }); 21 | }) 22 | } 23 | }; 24 | }); -------------------------------------------------------------------------------- /_SpecRunner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /public/app/js/appController.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('CC'); 2 | app.controller('mainCtrl', function($scope, socket, $timeout, initiateEditor, $modalStack){ 3 | var closeAllModals = function(){ 4 | socket.emit('closeLoserModal'); 5 | $modalStack.dismissAll(); 6 | } 7 | 8 | $scope.data = {}; 9 | 10 | $scope.checkAnswer = function(){ 11 | if(initiateEditor.validateCode($scope.returnEditorText())){ 12 | $modalStack.dismissAll(); 13 | socket.emit('showWinnerModal'); 14 | socket.emit('clearAllEditors'); 15 | socket.emit('showLoserModal'); 16 | $timeout(function(){ 17 | closeAllModals(); 18 | socket.emit('initializeNewQuestion'); 19 | }, 5000); 20 | } else { 21 | alert('wrong') 22 | } 23 | }; 24 | 25 | socket.on('joinedRoom', function(obj){ 26 | closeAllModals(); 27 | console.log('New player joined the room'); 28 | }); 29 | 30 | socket.on('leftRoom', function(obj){ 31 | console.log('someone left the room') 32 | }); 33 | }); -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var http = require('http'); 3 | var app = express(); 4 | var path = require('path') 5 | var server = require('http').createServer(app); 6 | var io = require('socket.io').listen(server, { log: false }); 7 | var gameLogic = require('./server-assets/gameLogic'); 8 | var questionData = require('./server-assets/questionData'); 9 | 10 | var port = process.env.PORT || 3000; 11 | server.listen(port); 12 | 13 | app.use(express.static(path.join(__dirname, 'public'))); 14 | 15 | app.get('/api/questions', function(req, res){ 16 | res.send(questionData.getQuestionData()); 17 | }); 18 | 19 | io.sockets.on('connection', function (socket) { 20 | gameLogic.initUser(socket, io, 'Tyler', questionData.getQuestionData().length); 21 | 22 | socket.on('userChangedEditor', function(newText){ 23 | gameLogic.sendTextUpdate(socket, newText); 24 | }); 25 | 26 | socket.on('disconnect', function(){ 27 | gameLogic.leaveRoom(socket, io, questionData.getQuestionData().length); 28 | }) 29 | }); -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | jshint: { 4 | all: ['src/**/*.js', 'test/**/*.js'], 5 | options: { 6 | globals: { 7 | _: false, 8 | $: false, 9 | jasmine: false, 10 | describe: false, 11 | it: false, 12 | expect: false, 13 | beforeEach: false 14 | }, 15 | browser: true, 16 | devel: true 17 | } 18 | }, 19 | jasmine: { 20 | unit: { 21 | src: 'src/**/*.js', 22 | options: { 23 | specs: ['test/**/*.js'], 24 | vendor: [ 25 | 'node_modules/lodash/lodash.js', 26 | 'node_modules/jquery/dist/jquery.js' 27 | ] } 28 | } 29 | }, 30 | watch: { 31 | all: { 32 | files: ['src/**/*.js', 'test/**/*.js'], 33 | tasks: ['default'] 34 | } 35 | } 36 | }); 37 | grunt.loadNpmTasks('grunt-contrib-jshint'); 38 | grunt.loadNpmTasks('grunt-contrib-jasmine'); 39 | grunt.loadNpmTasks('grunt-contrib-watch'); 40 | grunt.registerTask('default', ['jshint', 'jasmine']); 41 | }; -------------------------------------------------------------------------------- /public/app/styles/landing-page.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | height: 100%; 3 | width: 100%; 4 | } 5 | .main-container { 6 | height: 100%; 7 | width: 100%; 8 | } 9 | .cc-blue { 10 | background: #3b9bdb; 11 | color: white; 12 | } 13 | 14 | .cc-blue:hover { 15 | background: rgba(59, 155, 219, 0.87); 16 | color: white; 17 | } 18 | 19 | .navbar-brand, 20 | .navbar-nav li a { 21 | line-height: 75px; 22 | height: 75px; 23 | padding-top: 0; 24 | font-size: 20px; 25 | } 26 | 27 | .jumbotron { 28 | text-align: center; 29 | background-color: transparent; 30 | padding: 20px 0; 31 | } 32 | 33 | .jumbotron h1 { 34 | font-weight: 200; 35 | } 36 | 37 | .emphasis { 38 | font-weight: 300; 39 | font-style: italic; 40 | font-size: 72px; 41 | } 42 | 43 | .vs { 44 | text-align: center; 45 | font-size: 66px; 46 | } 47 | 48 | .example-photo { 49 | width: 100%; 50 | } 51 | 52 | .up { 53 | position: relative; 54 | bottom: 40px; 55 | } 56 | 57 | .space { 58 | /*width: 290px;*/ 59 | width: 148px; 60 | margin: 0px auto; 61 | } 62 | 63 | .l-gray:hover { 64 | background: rgba(219, 219, 219, 0.85); 65 | } 66 | 67 | .btn-success a{ 68 | color: #fff; 69 | text-decoration: none; 70 | } -------------------------------------------------------------------------------- /public/app/js/components/aceEditor/opponentEditorDirective.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('CC'); 2 | 3 | app.directive('opponentEditor', function(socket, initiateEditor) { 4 | return { 5 | restrict: 'E', 6 | template: "

", 7 | link: function(scope, ele, attr) { 8 | var editor = ace.edit("oEditor"); 9 | editor.setTheme("ace/theme/eclipse"); 10 | editor.setReadOnly(true); 11 | editor.renderer.setShowGutter(false); 12 | editor.setFontSize(15); 13 | 14 | var session = editor.getSession(); 15 | session.setUseWrapMode(true); 16 | session.setMode("ace/mode/javascript"); 17 | scope.oppSession = session; 18 | 19 | editor.on('focus', function(){ 20 | //refactor later 21 | alert('No one likes a cheater'); 22 | }); 23 | 24 | editor.on('copy', function(){ 25 | //refactor later 26 | alert('No one likes a cheater'); 27 | }); 28 | 29 | socket.on('updateText', function (newTxt) { 30 | session.setValue(newTxt); 31 | }); 32 | 33 | scope.oEditor = editor; 34 | 35 | socket.on('initializeQuestion', function(randomNum){ 36 | initiateEditor.setUpEditor(randomNum, scope); 37 | }); 38 | 39 | socket.on('cleanEditor', function(){ 40 | initiateEditor.clearEditor(scope); 41 | }); 42 | } 43 | } 44 | }); -------------------------------------------------------------------------------- /public/app/js/components/aceEditor/initiateEditor.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('CC'); 2 | 3 | app.service('initiateEditor', function($http, $q, socket){ 4 | var questions = {}; 5 | var singleQuestion = {}; 6 | 7 | this.setQuestionData = function(data){ 8 | questions.data = data; 9 | }; 10 | 11 | this.getRandomQuestion = function(randomInt){ 12 | return questions.data[randomInt]; 13 | }; 14 | 15 | this.getQuestions = function(){ 16 | var d = $q.defer(); 17 | $http({ 18 | method: 'GET', 19 | url: '/api/questions' 20 | }).success(function(data){ 21 | d.resolve(data); 22 | }).error(function(){ 23 | d.error('There was an error retrieving the questions'); 24 | }); 25 | return d.promise; 26 | }; 27 | 28 | this.setUpEditor = function(randomNum, scope){ 29 | scope.questionData = this.getRandomQuestion(randomNum); 30 | singleQuestion = scope.questionData; 31 | scope.userSession.setValue(scope.questionData.fn); 32 | scope.oppSession.setValue(scope.questionData.fn); 33 | }; 34 | 35 | this.clearEditor = function(scope){ 36 | scope.questionData = {}; 37 | singleQuestion = {}; 38 | scope.oppSession.setValue(''); 39 | scope.userSession.setValue(''); 40 | }; 41 | 42 | this.validateCode = function(submittedCode){ 43 | var userFn = submittedCode; 44 | modifiedUserFn = '(' + userFn + '( "' + singleQuestion.parameter + '"))'; 45 | var result = eval(modifiedUserFn); 46 | if(result === singleQuestion.answer){ 47 | return true; 48 | } else { 49 | return false 50 | } 51 | } 52 | }); -------------------------------------------------------------------------------- /server-assets/questionData.js: -------------------------------------------------------------------------------- 1 | var questionData = [ 2 | { 3 | question: 'Have the function "reverse" take a string as the parameter and return that string after it\'s been reversed', 4 | fn: 'function reverse(str){\n\n}', 5 | parameter: 'this is the answer', 6 | answer: 'rewsna eht si siht' 7 | }, 8 | { 9 | question: 'Have the function "longest" take a string as the parameter and returns the longest word in that string.', 10 | fn: 'function longest(str){\n\n}', 11 | parameter: 'spaceship is the longest word', 12 | answer: 'spaceship' 13 | }, 14 | { 15 | question: 'Have the function "dyslexicYoda" take a string as the parameter and return that string after you reverse the order of the words. ie "I enjoy eating cold meatballs" becomes "meatballs cold eating enjoy I"', 16 | fn: 'function dyslexicYoda(str){\n\n}', 17 | parameter: 'Leo enjoys his toys', 18 | answer: 'toys his enjoys Leo' 19 | }, 20 | { 21 | question: 'Have the function "vowelCount" take a string and return how many vowels are in that string', 22 | fn: 'function vowelCount(str){\n\n}', 23 | parameter: 'How many vowels are in this sentence?', 24 | answer: 11 25 | }, 26 | { 27 | question: 'Have the function "palindrome" take a string and return true if that string is a palindrome (written the same forward and backwards)', 28 | fn: 'function palindrome(str){\n\n}', 29 | parameter: 'kayak', 30 | answer: true 31 | }, 32 | { 33 | question: 'Have the function "titleMaker" take a string and return that string after capitalizing the first letter in every word.', 34 | fn: 'function titleMaker(str){\n\n}', 35 | parameter: 'peter piper picked peppers and run rocked rhymes', 36 | answer: 'Peter Piper Picked Peppers And Run Rocked Rhymes' 37 | } 38 | ]; 39 | 40 | module.exports.getQuestionData = function(){ 41 | return questionData; 42 | } -------------------------------------------------------------------------------- /public/app/js/components/aceEditor/userEditorDirective.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('CC'); 2 | 3 | app.directive('userEditor', function(socket, initiateEditor, $modal, $modalStack){ 4 | return { 5 | restrict: 'E', 6 | template: "

", 7 | link: function(scope, ele, attr) { 8 | var editor = ace.edit("uEditor"); 9 | editor.setTheme("ace/theme/eclipse"); 10 | editor.setFontSize(19); 11 | 12 | var session = editor.getSession(); 13 | session.setUseWrapMode(true); 14 | session.setMode("ace/mode/javascript"); 15 | 16 | editor.on('change', function(){ 17 | var currentText = session.getValue(); 18 | socket.emit('userChangedEditor', currentText); 19 | }); 20 | scope.userSession = session; 21 | 22 | editor.on('focus', function(){ 23 | scope.$apply(scope.highlightSubmit = true); 24 | }) 25 | 26 | scope.editor = editor; 27 | 28 | scope.returnEditorText = function(){ 29 | return session.getValue(); 30 | } 31 | 32 | socket.on('openWinnerModal', function(){ 33 | $modalStack.dismissAll(); 34 | scope.winnerModal = $modal.open({ 35 | templateUrl: 'js/components/modals/winnerModal.html', 36 | controller: 'mainCtrl' 37 | }); 38 | }); 39 | 40 | socket.on('openLoserModal', function(){ 41 | $modalStack.dismissAll(); 42 | scope.loserModal = $modal.open({ 43 | templateUrl: 'js/components/modals/loserModal.html', 44 | controller: 'mainCtrl' 45 | }); 46 | }); 47 | 48 | socket.on('destroyLoserModal', function(){ 49 | scope.loserModal && scope.loserModal.close(); 50 | $modalStack.dismissAll(); 51 | }) 52 | 53 | socket.on('waitingForOpponent', function(){ 54 | scope.waitingForOpponentModal = $modal.open({ 55 | templateUrl: 'js/components/modals/waitingForOpponent.html', 56 | controller: 'mainCtrl' 57 | }); 58 | }); 59 | 60 | socket.on('initializeQuestion', function(randomNum){ 61 | scope.highlightSubmit = false; 62 | initiateEditor.setUpEditor(randomNum, scope); 63 | }); 64 | 65 | socket.on('cleanEditor', function(){ 66 | initiateEditor.clearEditor(scope); 67 | }); 68 | 69 | 70 | } 71 | } 72 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CoderCombat 2 | =========== 3 |

Brief Non-Technical Description

4 |

Coder Combat is a real time one on one programming competition over the web. 5 | It attempts to simulate the emotions felt in a technical interview while also improving your programming skills. 6 | Competitors are paired up in a room and are then given an algorithm to solve. During the match each player will 7 | be able to see up to date progress on where their competitor is at at all times. Once someone solves the algorithm, 8 | the winner and loser will both be notified and a ten second countdown will prepare both competitors for the next match. 9 |

10 | 11 | Coder Combat Searching 12 |
13 |
14 |
15 | Coder Combat Room 16 |
17 |
18 |
19 | CoderCombatWinner 20 |
21 |
22 |
23 | CoderCombatWinner 24 |
25 |
26 |
27 |

Technical Description

28 |

Coder Combat utilizes various technologies including 29 | Coder Combat Tech Stack 30 |

31 |

I use Angular.js on the front end with the two text editors being Directives, and a Socket.IO, Http, and CountDown 32 | service. My schema for Mongo involves a Title, Question, Parameter, and Answer. When a user clicks submit, an ng-click 33 | event is fired, I then take the code that was in his or her editor, parse it to be in the correct format, pass in the 34 | Parameter from the DB, evaluate it, then compare the result to the actual answer.

35 |

Socket.IO is the main contributor of this application. I used Sockets in order to keep a constant live stream 36 | going between both users and their editors.

37 |

38 |

Challenges

39 |

The biggest challenges I faced were definitely related to Sockets and more specifically the use of rooms with 40 | sockets. For every connection that is made, I categorize that user into a specific room based on a certain algorithm. 41 | Being a live game, the user has the option to stay, disconnect, or refresh. This leads to a vast array or 42 | certain behavior that could happen in one specific room. This behavior needed to be somehow connected with other rooms 43 | and the behavior of the users in that room. For example, if there are two rooms each containing two users, the game 44 | needs to be able to detect if both rooms have someone disconnect, and then pair those remaining users. 45 |

46 |
47 | CoderCombat Waiting 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /server-assets/gameLogic.js: -------------------------------------------------------------------------------- 1 | var rooms = {}; 2 | var users = {}; 3 | 4 | var returnRandomInt = function(min, max){ 5 | return Math.floor(Math.random() * (max - min + 1)) + min; 6 | }; 7 | 8 | var joinRoom = function(skt, io, rm, questionArrLength){ 9 | skt.join(rm); 10 | rooms[rm] = rooms[rm] || []; 11 | rooms[rm].push(skt.id); 12 | users[skt.id].room = rm; 13 | users[skt.id].socket = skt; 14 | if(rooms[rm].length > 1){ 15 | skt.broadcast.to(rm).emit('joinedRoom', {text: users[skt.id].name + ' joined room ' + users[skt.id].room}); 16 | io.sockets.in(rm).emit('initializeQuestion', returnRandomInt(0, questionArrLength-1)); 17 | //maybe a 3,2,1 Go! modal here. 18 | } else { 19 | io.sockets.socket(skt.id).emit('waitingForOpponent'); 20 | } 21 | 22 | skt.on('clearAllEditors', function(){ 23 | io.sockets.in(rm).emit('cleanEditor'); 24 | }); 25 | 26 | skt.on('showWinnerModal', function(){ 27 | io.sockets.socket(skt.id).emit('openWinnerModal'); 28 | }); 29 | 30 | skt.on('showLoserModal', function(){ 31 | skt.broadcast.emit('openLoserModal') 32 | }); 33 | 34 | skt.on('initializeNewQuestion', function(){ 35 | io.sockets.in(rm).emit('initializeQuestion', returnRandomInt(0, questionArrLength -1)); 36 | }); 37 | 38 | skt.on('closeLoserModal', function(){ 39 | skt.broadcast.to(rm).emit('destroyLoserModal'); 40 | }); 41 | }; 42 | 43 | var placeInRoom = function(skt, io, questionArrLength){ 44 | var keys = Object.keys(rooms); 45 | var howManyRooms = keys.length; 46 | var lastRoomNumber = howManyRooms ? parseInt(keys[keys.length - 1], 10) : 0; 47 | var lastRoomArray = howManyRooms ? rooms[lastRoomNumber] : []; 48 | if(lastRoomArray.length > 1){ 49 | lastRoomNumber += 1; 50 | } 51 | joinRoom(skt, io, lastRoomNumber + 1, questionArrLength) 52 | }; 53 | 54 | module.exports.initUser = function(socket, io, name, questionArrLength){ 55 | users[socket.id] = {}; 56 | users[socket.id].name = name; 57 | placeInRoom(socket, io, questionArrLength); 58 | }; 59 | 60 | module.exports.leaveRoom = function(skt, io, questionArrLength){ 61 | var room = users[skt.id].room; 62 | var userName = users[skt.id].name; 63 | delete users[skt.id]; 64 | var usersRoomIndex = rooms[room].indexOf(skt.id); 65 | rooms[room].splice(usersRoomIndex, 1); 66 | if(!rooms[room].length){ 67 | delete rooms[room]; 68 | } else { 69 | var opponentId = rooms[room][0]; 70 | var opponentSocket = users[opponentId].socket; 71 | skt.broadcast.to(room).emit('leftRoom', {text: userName + ' left room '+ room}); 72 | skt.broadcast.to(room).emit('cleanEditor'); 73 | skt.leave(room); 74 | delete rooms[room]; 75 | placeInRoom(opponentSocket, io, questionArrLength); 76 | } 77 | }; 78 | 79 | module.exports.sendTextUpdate = function(skt, newText){ 80 | var room = users[skt.id].room; 81 | skt.broadcast.to(room).emit('updateText', newText); 82 | } 83 | -------------------------------------------------------------------------------- /public/app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 28 |
29 |
30 |
31 | 32 | 33 | 34 |
35 |
36 |
37 | {{questionData.question}} 38 |
39 |
40 | 41 | 42 | 43 |
44 |
45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /public/app/styles/app-styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | .container-fluid{ 6 | height: 95%; 7 | width: 100%; 8 | padding: 0; 9 | margin: 0; 10 | } 11 | .row { 12 | height: 100%; 13 | width: 100%; 14 | margin: 0; 15 | padding: 0; 16 | } 17 | .navbar { 18 | margin-bottom: 0; 19 | border-bottom: 2px solid #e7e7e7; 20 | } 21 | .cc-blue { 22 | background: #3b9bdb; 23 | color: white; 24 | } 25 | .cc-blue:hover { 26 | background: rgba(59, 155, 219, 0.87); 27 | color: white; 28 | } 29 | .navbar-brand, 30 | .navbar-nav li a { 31 | line-height: 60px; 32 | height: 60px; 33 | padding-top: 0; 34 | font-size: 16px; 35 | } 36 | 37 | .navbar-nav.navbar-right:last-child { 38 | margin-right: 0; 39 | } 40 | 41 | .players-editor { 42 | height: 100%; 43 | width: 60%; 44 | border-right: 2px solid #e7e7e7; 45 | } 46 | 47 | .right-side { 48 | height: 100%; 49 | width: 40%; 50 | } 51 | 52 | .question-container { 53 | height: 50%; 54 | width: 100%; 55 | font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; 56 | font-size: 22px; 57 | } 58 | 59 | .opponents-editor { 60 | height: 50%; 61 | width: 100%; 62 | border-top: 2px solid #e7e7e7; 63 | } 64 | 65 | .modal-header { 66 | background: #f8f8f8; 67 | -webkit-border-top-left-radius: 5px; 68 | -webkit-border-top-right-radius: 5px; 69 | -moz-border-radius-topleft: 5px; 70 | -moz-border-radius-topright: 5px; 71 | border-top-left-radius: 5px; 72 | border-top-right-radius: 5px; 73 | } 74 | 75 | .modal-header img { 76 | width: 201px; 77 | margin: 0 auto; 78 | display: block; 79 | } 80 | .modal-body { 81 | text-align: center; 82 | font-size: 29px; 83 | font-weight: 200; 84 | } 85 | body .modal-content { 86 | box-shadow: 0 0 0 0; 87 | border: 0; 88 | } 89 | 90 | 91 | /*Overrides Bootstrap*/ 92 | .mute { 93 | color: rgba(119, 119, 119, 0.54) !important; 94 | } 95 | .not-mute { 96 | color: #333 !important; 97 | background: #E7E7E7 !important; 98 | } 99 | .not-mute:hover{ 100 | background: #E4E4E4 !important 101 | } 102 | 103 | 104 | /* Spinner */ 105 | .spinner { 106 | width: 120px; 107 | height: 120px; 108 | position: relative; 109 | border-radius: 50%; 110 | border: 2px solid #3b9bdb; 111 | margin: 10px auto; 112 | } 113 | .spinner:after { 114 | content: ""; 115 | position: absolute; 116 | width: 40px; 117 | height: 2px; 118 | background: #4f5b63; 119 | top: 57px; 120 | left: 57px; 121 | -ms-transform-origin: 1px 1px; 122 | -webkit-transform-origin: 1px 1px; 123 | -moz-transform-origin: 1px 1px; 124 | transform-origin: 1px 1px; 125 | -webkit-animation: spin 2s infinite linear; 126 | -moz-animation: spin 2s infinite linear; 127 | -ms-animation: spin 2s infinite linear; 128 | animation: spin 2s infinite linear; 129 | } 130 | .spinner:before { 131 | content: ""; 132 | position: absolute; 133 | left: 56px; 134 | top: 56px; 135 | height: 5px; 136 | width: 5px; 137 | background: #4f5b63; 138 | border-radius: 50%; 139 | -webkit-animation: expand 1s infinite linear alternate; 140 | -moz-animation: expand 1s infinite linear alternate; 141 | -ms-animation: expand 1s infinite linear alternate; 142 | animation: expand 1s infinite linear alternate; 143 | }; 144 | @keyframes spin {from {transform: rotate(0);}to {transform: rotate(360deg);}} 145 | @keyframes expand {from {transform: scale(1);}to {transform: scale(1.5);}} 146 | @-moz-keyframes spin {from {-moz-transform: rotate(0);}to {-moz-transform: rotate(360deg);}} 147 | @-moz-keyframes expand {from {-moz-transform: scale(1);}to {-moz-transform: scale(1.5);}} 148 | @-webkit-keyframes spin {from {-webkit-transform: rotate(0);}to {-webkit-transform: rotate(360deg);}} 149 | @-webkit-keyframes expand {from {-webkit-transform: scale(1);}to {-webkit-transform: scale(1.5);}} 150 | @-ms-keyframes spin {from {-ms-transform: rotate(0);}to {-webkit-transform: rotate(360deg);}} 151 | @-ms-keyframes expand {from {-ms-transform: scale(1);}to {-webkit-transform: scale(1.5);}} 152 | 153 | 154 | .inner-timer { 155 | text-align: center; 156 | font-size: 50px; 157 | margin-top: 20px; 158 | } 159 | 160 | /* Ace Editor Overrides */ 161 | .ace_editor { 162 | height: 100%; 163 | } 164 | .ace_print-margin { 165 | display: none; 166 | } 167 | .ace-eclipse .ace_marker-layer .ace_active-line { 168 | background: transparent !important; 169 | } 170 | .oEditor .ace_content { 171 | cursor: not-allowed; 172 | } 173 | body .ace-eclipse .ace_gutter { 174 | background: #f8f8f8; 175 | border-right: 2px solid #e7e7e7; 176 | color: #777; 177 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Coder Combat 5 | 6 | 7 | 8 | 9 | 10 | 30 |
31 |
32 |

Code AGAINST Friends

33 |

Coder Combat is a real-time, one-one-one programming competition that simulates both the pace and intensity of a technical interview. During a match, players see each others’ progress in real-time and race to pass a suite of pre-determined tests.

34 |
35 |
36 |
37 |
38 |
39 |

VS

40 |
41 |
42 |
43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |
53 |
54 | 57 | 60 |
61 |
62 |
63 | 64 | 65 | 99 | 100 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | --------------------------------------------------------------------------------