Commit 2949d3cd authored by BsmhDev's avatar BsmhDev

add games model - main helpers and methods

parent 8498e51c
......@@ -8,6 +8,10 @@
@import "{}/imports/ui/stylesheets/create-quiz.less";
@import "{}/imports/ui/stylesheets/quizes.less";
@import "{}/imports/ui/stylesheets/view-quiz.less";
@import "{}/imports/ui/stylesheets/question.less";
@import "{}/imports/ui/stylesheets/leaders.less";
// @import "{}/imports/ui/stylesheets/answer-count-font.ttf";
.clickable {
cursor: pointer;
......
......@@ -27,6 +27,7 @@ export default Class.create({
},
],
},
order: Number,
points: {
type: Number,
default: 0,
......
// import Meteor from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { Class, Union } from 'meteor/jagi:astronomy';
import { countBy, groupBy, sortBy, mapObject, pairs } from 'underscore';
import Quiz from '../quizes/quizes.js';
const calculateTimeDelta = (t1, t2) => {
const datesDelta = t1.getTime() - t2.getTime(); // TODO: Check if we need the getTime method.
const secondsBetweenTime = datesDelta / 1000;
const secondsBetweenDates = Math.abs(secondsBetweenTime);
return secondsBetweenDates;
};
const calculateScore = (deltaTime, score, questionTime) => {
// y = mx + n
const timeFunc = deltaTime / questionTime;
const mx = (Math.abs(score) * -1) / timeFunc;
const finalScore = score > 0 ? mx + score : mx;
return finalScore;
};
const GameInit = Class.create({
name: 'GameInit',
createdAt: {
......@@ -91,7 +108,7 @@ const GameEnd = Class.create({
});
const GameEvent = Union.create({
name: 'GameInit',
name: 'GameEvent',
types: [
GameInit,
PlayerReg,
......@@ -103,7 +120,7 @@ const GameEvent = Union.create({
],
});
export const Games = new Mongo.Collection('games');
const Games = new Mongo.Collection('games');
export default Class.create({
name: 'Game',
......@@ -114,20 +131,23 @@ export default Class.create({
gameLog: {
type: [GameEvent],
default() {
return [GameInit];
return [new GameInit()];
},
},
state: Boolean,
time: Number,
createdAt: {
value: Date,
default: Date.Now(),
type: Date,
default() {
return new Date();
},
},
lastUpdate: {
value: Date,
default: Date.Now(),
type: Date,
default() {
return new Date();
},
},
},
meteorMethods: {
PlayerReg(userId) {
const isUserExist = this.players.find(user => user === userId);
......@@ -143,21 +163,26 @@ export default Class.create({
return isUserExist && registerPlayer;
},
StartGame() {
const q = this.Quiz.questions.find(this.Quiz.questions.order === 1);
// Starting game
const gameStarted = new GameStarted();
this.gameLog = this.gameLog.concat(gameStarted);
this.save();
// Starting first question
const firstQuestion = this.Quiz.questions.find(q => q.order === 1);
const questionStarted = new QuestionStart({
questionId: q._id,
questionId: firstQuestion._id,
});
this.gameLog = this.gameLog.concat(questionStarted);
this.save();
// Ending question
const questionEnd = new QuestionEnd({
questionId: q._id,
questionId: firstQuestion._id,
});
const questionEndToLog = () => {
this.gameLog = setTimeout(this.gameLog.concat(questionEnd), q.time);
this.gameLog = this.gameLog.concat(questionEnd);
this.save();
};
this.gameLog = this.gameLog.concat(gameStarted, questionStarted);
this.save();
questionEndToLog();
setTimeout(questionEndToLog, firstQuestion.time);
return true;
},
PlayerAnswer(uId, qId, aId) {
......@@ -170,23 +195,115 @@ export default Class.create({
this.save();
return true;
},
NextQuestion (qId) {
NextQuestion(qId) {
// TODO: get the Id
// qId = this.LastQuestionToEndId();
const questionStarted = new QuestionStart({
questionId: qId,
});
const questionEnd = new QuestionEnd({
questionId: qId,
});
const q = this.quiz.questions.find(ques => ques._id === qId);
const questionEndToLog = () => {
const q = this.Quiz.questions.find(this.Quiz.questions.order === 1);
this.gameLog = setTimeout(this.gameLog.concat(questionEnd), q.time);
this.gameLog = this.gameLog.concat(questionEnd);
this.save();
};
this.gameLog = this.gameLog.concat(questionStarted);
this.gameLog = this.gameLog.concat(questionStarted);
this.save();
questionEndToLog();
setTimeout(questionEndToLog, q.time);
return true;
},
},
helpers: {
// getLastQuestionId() {},
answersGroupCount() {
const lastQuestionId = this.LastQuestionToStartId();
const getAnswerOrder = id =>
this.quiz.questions
.find(q => q._id === lastQuestionId)
.answers.find(a => a._id === id).order;
const playersAnswerEvents = this.getQuestionAnswers()
.map(e => e.answerId)
.map(getAnswerOrder);
const questionsAnswers = countBy(playersAnswerEvents, o => o);
return questionsAnswers;
},
answerCount() {
const playersAnswerEvents = this.getQuestionAnswers();
return playersAnswerEvents.length;
},
getQuestionAnswers() {
const lastQuestionId = this.LastQuestionToStartId();
const playersAnswerEvents = this.gameLog
.filter(e => e instanceof PlayerAnswer)
.filter(e => e.questionId === lastQuestionId);
return playersAnswerEvents;
},
scoreList() {
const playersAnswers = this.gameLog
.filter(e => e instanceof PlayerAnswer) // => [PlayerAnswer, PlayerAnswer, PlayerAnswer...]
.map(({ userId, answerId, createdAt, questionId }) => ({
userId,
timeDelta: calculateTimeDelta(
createdAt,
this.getQuestionStartTime(questionId),
),
answerScore: this.getAnswerScore(questionId, answerId),
questionTime: this.getQuestionTime(questionId),
})) // => [{userId, timeDelta: t, answerScore: a, questionTime: q}, {...}...]
.map(({ userId, timeDelta, answerScore, questionTime }) => ({
userId,
userScore: calculateScore(timeDelta, answerScore, questionTime),
})); // => [{userId, userScore}, {userId, userScore},...]
const scoresByUser = groupBy(playersAnswers, 'userId'); // => {userId: [score, score, score], userId: [score, score, score],...}
const finalScoreByUser = mapObject(scoresByUser, (val, key) =>
val.reduce((a, b) => a + b, 0),
); // => {userId: finalScore, ...}
const scoreByUser = pairs(finalScoreByUser).map(a => ({
userName: 'Meteor.users.findOne(u => u._id === a[0]).name',
userScore: a[1],
})); // => [{userName: id, userScore: score}, {userName: id, userScore: score}, ...]
const scoreByUserNamesSorted = sortBy(scoreByUser, 'userScore').first(5);
return scoreByUserNamesSorted; // => [{userName, score}, {userName: score},...] - 5 elements
},
getQuestionStartTime(qId) {
const questionStartEvent = this.gameLog
.filter(e => e instanceof QuestionStart)
.filter(e => e.questionId === qId);
const questionStartTime = questionStartEvent.createdAt;
return questionStartTime;
},
getAnswerScore(qId, aId) {
const question = this.quiz.questions.find(q => q._id === qId);
const answer = question.answers.find(a => a._id === aId);
const score = answer.points;
return score;
},
getQuestionTime(qId) {
const question = this.quiz.questions.find(q => q._id === qId);
const time = question.time;
return time;
},
LastQuestionToStartId() {
const questionLog = this.gameLog.filter(e => e instanceof QuestionStart);
const orderedQuestionsLog = sortBy(questionLog, 'createdAt');
const lastQuestionEvents =
orderedQuestionsLog[orderedQuestionsLog.length - 1];
const qId = lastQuestionEvents.questionId;
return qId;
},
LastQuestionToEndId() {
const questionLog = this.gameLog.filter(e => e instanceof QuestionEnd);
const orderedQuestionsLog = sortBy(questionLog, 'createdAt');
const lastQuestionEvents =
orderedQuestionsLog[orderedQuestionsLog.length - 1];
const qId = lastQuestionEvents.questionId;
return qId;
},
// NextQuestionId() {
// const qId = this.LastQuestionToEndId === this.LastQuestionToStartId ?
// this.LastQuestionToStartId + 1 : null;
// },
},
});
......@@ -8,7 +8,7 @@ const User = Class.create({
collection: Users,
fields: {
name: {
type:String,
type: String,
validators: [{
type: 'minLength',
param: 3
......
......@@ -16,6 +16,7 @@ import Search from '../../ui/pages/search/search.js';
import ViewQuiz from '../../ui/pages/view-quiz/view-quiz';
import Management from '../../ui/pages/management/management.js';
import NotFound from '../../ui/pages/not-found/not-found';
import Question from '../../ui/pages/question/question';
// Set up all routes in the app
FlowRouter.route('/', {
......@@ -74,6 +75,13 @@ FlowRouter.route('/ViewQuiz/:_id', {
},
});
FlowRouter.route('/Question/:_id', {
name: 'Manage.Question',
action(params) {
mount(ManageLayout, { main: <Question id={params._id} /> });
},
});
FlowRouter.notFound = {
action() {
mount(GameLayout, { main: <NotFound /> });
......
import React from 'react';
const Answer = ({ answer, index, actions }) => (
<div className="col-xs-6 col-sm-6 col-md-6 col-lg-6 col-xl-6 buttons-col" key={answer.id}>
<div className="answer-btn-area">
<button type="button" className={`btn btn-answer-${index} answer-button`} onClick={actions.selectAnswer(answer._id)}>
<i className={`fa fa-answer-${index} answer-icon`} />
<h3>{answer.text}</h3>
</button>
</div>
</div>
);
export default Answer;
import React from 'react';
import Answer from '../answer/answer';
const Answers = ({ answers, actions }) => (
<div className="container">
<div className="row">
{answers.map((answer, index) => (
<Answer
answer={answer}
index={index + 1}
action={actions.selectAnswer}
/>
))}
</div>
</div>
);
export default Answers;
import React from 'react';
import { Bar } from 'react-chartjs';
const glyphIconsJson = {
0: '\f004',
1: '\f111',
2: '\f0c8',
3: '\f005',
};
const colorsJson = {
0: '#d9534f',
1: '#5bc0de',
2: '#f0ad4e',
3: '#5cb85c',
};
const BarChart = ({ answers }) => {
const orders = Object.keys(answers);
const options = {
// Boolean - Whether grid lines are shown across the chart
scaleShowGridLines: false,
// Boolean - Whether to show horizontal lines (except X axis)
scaleShowHorizontalLines: false,
// Boolean - Whether to show vertical lines (except Y axis)
scaleShowVerticalLines: false,
};
const getData = (order, count) => {
const data = {
labels: [glyphIconsJson[order]],
datasets: [{
fillColor: colorsJson[order],
data: [count],
}],
};
return data;
};
const barsChart = orders.map(o => <Bar data={getData(o, answers[o])} options={options} />);
return barsChart;
};
export default BarChart;
import React from 'react';
const GameNavbar = () => (
const GameNavbar = ({text, num}) => (
<nav className="navbar navbar-default navbar-fixed-top navbar-game">
<div className="container-fluid navabr-message-area">
<div className="navbar-bis-header">
<p className="">
<span>
כנס לכתובת <strong>zahool.idf</strong> והירשם למשחק בעזרת הקוד:
{text}
</span>
<strong>675123</strong>
<strong>{num}</strong>
</p>
</div>
</div>
......
import React from 'react';
const ManagerAnswers = ({ firstAnswer, secondAnswer, thirdAnswer, fourthAnswer }) => (
<div className="container">
<div className="row">
<div className="col-xs-6 col-sm-6 col-md-6 col-lg-6 col-xl-6 answers-col">
<div className="answer-manager-panel-area">
<div className="panel panel-danger answer-manager-panel">
<div className="panel-body answer-font-color" id="first-answer">
<h3><i className="fa fa-heart answer-manager-icon" /></h3>
<h5>{firstAnswer}</h5>
</div>
</div>
</div>
</div>
<div className="col-xs-6 col-sm-6 col-md-6 col-lg-6 col-xl-6 answers-col">
<div className="answer-manager-panel-area">
<div className="panel panel-info answer-manager-panel">
<div className="panel-body answer-font-color" id="second-answer">
<h3><i className="fa fa-circle answer-manager-icon" /></h3>
<h5>{secondAnswer}</h5>
</div>
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-xs-6 col-sm-6 col-md-6 col-lg-6 col-xl-6 answers-col">
<div className="answer-manager-panel-area">
<div className="panel panel-warning answer-manager-panel">
<div className="panel-body answer-font-color" id="third-answer">
<h3><i className="fa fa-square answer-manager-icon" /></h3>
<h5>{thirdAnswer}</h5>
</div>
</div>
</div>
</div>
<div className="col-xs-6 col-sm-6 col-md-6 col-lg-6 col-xl-6 answers-col">
<div className="answer-manager-panel-area">
<div className="panel panel-success answer-manager-panel">
<div className="panel-body answer-font-color" id="fourth-answer">
<h3><i className="fa fa-star answer-manager-icon" /></h3>
<h5>{fourthAnswer}</h5>
</div>
</div>
</div>
</div>
</div>
</div>
);
export default ManagerAnswers;
import React from 'react';
const PlayersAnswers = ({ firstAnswer, secondAnswer, thirdAnswer, fourthAnswer }) => (
<div className="container">
<div className="row">
<div className="col-xs-6 col-sm-6 col-md-6 col-lg-6 col-xl-6 buttons-col">
<div className="answer-btn-area">
<button type="button" className="btn btn-danger answer-button">
<i className="fa fa-heart answer-icon" />
<h5>{firstAnswer}</h5>
</button>
</div>
</div>
<div className="col-xs-6 col-sm-6 col-md-6 col-lg-6 col-xl-6 buttons-col">
<div className="answer-btn-area">
<button type="button" className="btn btn-info answer-button">
<i className="fa fa-circle answer-icon" />
<h5>{secondAnswer}</h5>
</button>
</div>
</div>
</div>
<div className="row">
<div className="col-xs-6 col-sm-6 col-md-6 col-lg-6 col-xl-6 buttons-col">
<div className="answer-btn-area">
<button type="button" className="btn btn-warning answer-button">
<i className="fa fa-square answer-icon" />
<h5>{thirdAnswer}</h5>
</button>
</div>
</div>
<div className="col-xs-6 col-sm-6 col-md-6 col-lg-6 col-xl-6 buttons-col">
<div className="answer-btn-area">
<button type="button" className="btn btn-success answer-button">
<i className="fa fa-star answer-icon" />
<h5>{fourthAnswer}</h5>
</button>
</div>
</div>
</div>
</div>
);
export default PlayersAnswers;
import React from 'react';
const ScoreBoard = ({scoreList}) => (
<table className="table">
<tbody>
<tr>
{scoreList.map(s => (
<td>{Object.keys(s)}</td>
<td>{s[0]}</td>
))}
</tr>
</tbody>
</table>
);
export default ScoreBoard;
\ No newline at end of file
......@@ -6,9 +6,10 @@ import QuizForm from '../../components/quiz-form/quiz-form.js';
// Utilities
const newQuestion = () => {
const answers = [1, 2, 3, 4].map(() => ({
const answers = [1, 2, 3, 4].map(i => ({
_id: uuidV4(),
text: '',
order: i,
points: 0,
}));
return {
......@@ -20,7 +21,11 @@ const newQuestion = () => {
};
const normalizeTagName = str =>
[str].map(s => s.trim()).map(s => s.toLocaleLowerCase()).map(s => s.replace(/\s+/g, '-')).pop();
[str]
.map(s => s.trim())
.map(s => s.toLocaleLowerCase())
.map(s => s.replace(/\s+/g, '-'))
.pop();
// React Page
class CreateQuiz extends React.Component {
......@@ -53,7 +58,10 @@ class CreateQuiz extends React.Component {
const removeQuestion = id => () => {
const quiz = this.state.quiz;
const quiz$ = { ...quiz, questions: quiz.questions.filter(q => q._id !== id) };
const quiz$ = {
...quiz,
questions: quiz.questions.filter(q => q._id !== id),
};
this.setState({ quiz: quiz$ });
};
......@@ -88,7 +96,9 @@ class CreateQuiz extends React.Component {
const changeQuestionText = id => (e) => {
const quiz = this.state.quiz;
const text$ = e.target.value;
const questions$ = quiz.questions.map(q => (q._id !== id ? q : { ...q, text: text$ }));
const questions$ = quiz.questions.map(
q => (q._id !== id ? q : { ...q, text: text$ }),
);
const quiz$ = { ...quiz, questions: questions$ };
this.setState({ quiz: quiz$ });
};
......@@ -96,7 +106,9 @@ class CreateQuiz extends React.Component {
const changeQuestionTime = id => (e) => {
const quiz = this.state.quiz;
const time$ = e.target.value;
const questions$ = quiz.questions.map(q => (q._id !== id ? q : { ...q, time: time$ }));
const questions$ = quiz.questions.map(
q => (q._id !== id ? q : { ...q, time: time$ }),
);
const quiz$ = { ...quiz, questions: questions$ };
this.setState({ quiz: quiz$ });
};
......@@ -107,7 +119,9 @@ class CreateQuiz extends React.Component {
const answers$ = quiz.questions
.find(q => q._id === qId)
.answers.map(a => (a._id !== aId ? a : { ...a, text: text$ }));
const questions$ = quiz.questions.map(q => (q._id !== qId ? q : { ...q, answers: answers$ }));
const questions$ = quiz.questions.map(
q => (q._id !== qId ? q : { ...q, answers: answers$ }),
);
const quiz$ = { ...quiz, questions: questions$ };
this.setState({ quiz: quiz$ });
};
......@@ -118,7 +132,9 @@ class CreateQuiz extends React.Component {
const answers$ = quiz.questions
.find(q => q._id === qId)
.answers.map(a => (a._id !== aId ? a : { ...a, points: points$ }));
const questions$ = quiz.questions.map(q => (q._id !== qId ? q : { ...q, answers: answers$ }));
const questions$ = quiz.questions.map(
q => (q._id !== qId ? q : { ...q, answers: answers$ }),
);
const quiz$ = { ...quiz, questions: questions$ };
this.setState({ quiz: quiz$ });
};
......@@ -153,7 +169,11 @@ class CreateQuiz extends React.Component {
};
return (
<QuizForm quiz={this.state.quiz} validate={this.state.validate} actions={actions} />
<QuizForm
quiz={this.state.quiz}
validate={this.state.validate}
actions={actions}
/>
);
}
}
......
......@@ -8,9 +8,10 @@ import QuizForm from '../../components/quiz-form/quiz-form.js';
// Utilities
const newQuestion = () => {
const answers = [1, 2, 3, 4].map(() => ({
const answers = [1, 2, 3, 4].map(i => ({
_id: uuidV4(),
text: '',
order: i,
points: 0,
}));
return {
......@@ -23,7 +24,11 @@ const newQuestion = () => {
};
const normalizeTagName = str =>
[str].map(s => s.trim()).map(s => s.toLocaleLowerCase()).map(s => s.replace(/\s+/g, '-')).pop();
[str]
.map(s => s.trim())
.map(s => s.toLocaleLowerCase())
.map(s => s.replace(/\s+/g, '-'))
.pop();
// React Page
class EditQuiz extends React.Component {
......@@ -50,7 +55,10 @@ class EditQuiz extends React.Component {
const removeQuestion = id => () => {
const quiz = this.state.quiz;
const quiz$ = { ...quiz, questions: quiz.questions.filter(q => q._id !== id) };
const quiz$ = {
...quiz,
questions: quiz.questions.filter(q => q._id !== id),
};
this.setState({ quiz: quiz$ });
};
......@@ -85,7 +93,9 @@ class EditQuiz extends React.Component {
const changeQuestionText = id => (e) => {
const quiz = this.state.quiz;
const text$ = e.target.value;
const questions$ = quiz.questions.map(q => (q._id !== id ? q : { ...q, text: text$ }));
const questions$ = quiz.questions.map(
q => (q._id !== id ? q : { ...q, text: text$ }),
);
const quiz$ = { ...quiz, questions: questions$ };
this.setState({ quiz: quiz$ });
};
......@@ -93,7 +103,9 @@ class EditQuiz extends React.Component {
const changeQuestionTime = id => (e) => {
const quiz = this.state.quiz;
const time$ = e.target.value;
const questions$ = quiz.questions.map(q => (q._id !== id ? q : { ...q, time: time$ }));
const questions$ = quiz.questions.map(
q => (q._id !== id ? q : { ...q, time: time$ }),
);
const quiz$ = { ...quiz, questions: questions$ };
this.setState({ quiz: quiz$ });
};
......@@ -104,7 +116,9 @@ class EditQuiz extends React.Component {
const answers$ = quiz.questions
.find(q => q._id === qId)
.answers.map(a => (a._id !== aId ? a : { ...a, text: text$ }));
const questions$ = quiz.questions.map(q => (q._id !== qId ? q : { ...q, answers: answers$ }));
const questions$ = quiz.questions.map(
q => (q._id !== qId ? q : { ...q, answers: answers$ }),
);
const quiz$ = { ...quiz, questions: questions$ };
this.setState({ quiz: quiz$ });
};
......@@ -115,7 +129,9 @@ class EditQuiz extends React.Component {
const answers$ = quiz.questions
.find(q => q._id === qId)
.answers.map(a => (a._id !== aId ? a : { ...a, points: points$ }));
const questions$ = quiz.questions.map(q => (q._id !== qId ? q : { ...q, answers: answers$ }));
const questions$ = quiz.questions.map(
q => (q._id !== qId ? q : { ...q, answers: answers$ }),
);
const quiz$ = { ...quiz, questions: questions$ };
this.setState({ quiz: quiz$ });
};
......@@ -148,7 +164,11 @@ class EditQuiz extends React.Component {
};
return (
<QuizForm quiz={this.state.quiz} validate={this.state.validate} actions={actions} />
<QuizForm
quiz={this.state.quiz}
validate={this.state.validate}
actions={actions}
/>
);
}
}
......
import React from 'react';
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {