├── 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 |
5 | Finding you an opponent...
6 |
7 |
8 |
--------------------------------------------------------------------------------
/public/app/js/components/modals/winnerModal.html:
--------------------------------------------------------------------------------
1 |
4 |
5 | You Won! Congratulations.
6 |
A new round will start in 5 seconds.
7 |
--------------------------------------------------------------------------------
/public/app/js/components/modals/opponentLeft.html:
--------------------------------------------------------------------------------
1 |
4 |
5 | Your opponent left. Finding you a new opponent...
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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 |
5 | Your opponent finished the problem. You lose.
6 |
A new round will start in 5 seconds.
7 |
--------------------------------------------------------------------------------
/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 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Technical Description
28 | Coder Combat utilizes various technologies including
29 |
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 |
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 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
63 |
64 |
65 |
99 |
100 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------