From 3a91a33d746fa41a130bbaf3dce81821c52be252 Mon Sep 17 00:00:00 2001 From: Thomas FORGIONE Date: Tue, 4 Aug 2015 18:59:24 +0200 Subject: [PATCH] Here it is... --- controllers/before-begin/views/index.jade | 2 +- controllers/prototype/dbrequests.js | 265 +++++++++++++++++- controllers/prototype/index.js | 53 ++-- controllers/prototype/urls.js | 5 +- .../views/prototype_recommendation.jade | 25 ++ .../apps/prototype/tutorial/TutorialSteps.js | 4 +- js/l3d/src/cameras/PointerCamera.js | 4 + js/l3d/src/scenes/initScene.js | 16 +- js/l3d/src/utils/Logger.js | 4 +- posts/keyboard-event/index.js | 9 +- sql/backup.pgsql | 103 ++----- 11 files changed, 352 insertions(+), 138 deletions(-) create mode 100644 controllers/prototype/views/prototype_recommendation.jade diff --git a/controllers/before-begin/views/index.jade b/controllers/before-begin/views/index.jade index 4a3d3a1..4fdfda9 100644 --- a/controllers/before-begin/views/index.jade +++ b/controllers/before-begin/views/index.jade @@ -10,4 +10,4 @@ block content saw in the tutorial : they are here to help you navigate in the scene. Use them or ignore them at your convenience. - p You can start when you're ready ! + p You can start when you're ready ! diff --git a/controllers/prototype/dbrequests.js b/controllers/prototype/dbrequests.js index 7a8df74..f752f57 100644 --- a/controllers/prototype/dbrequests.js +++ b/controllers/prototype/dbrequests.js @@ -2,7 +2,28 @@ var pg = require('pg'); var pgc = require('../../private.js'); var Log = require('../../lib/NodeLog.js'); +// Shuffle array +function shuffle(array) { + var currentIndex = array.length, temporaryValue, randomIndex ; + + // While there remain elements to shuffle... + while (0 !== currentIndex) { + + // Pick a remaining element... + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex -= 1; + + // And swap it with the current element. + temporaryValue = array[currentIndex]; + array[currentIndex] = array[randomIndex]; + array[randomIndex] = temporaryValue; + } + + return array; +} + /** + * * @namespace */ var DBReq = {}; @@ -506,11 +527,10 @@ DBReq.UserCreator.prototype.finish = function() { * @memberof DBReq * @constructor */ -DBReq.ExpCreator = function(user_id, scene_id, template, finishAction) { +DBReq.ExpCreator = function(user_id, finishAction) { this.finishAction = finishAction; this.user_id = user_id; - this.scene_id = scene_id; - this.template = template; + this.finalResult = {}; // Connect to db var self = this; @@ -524,16 +544,156 @@ DBReq.ExpCreator = function(user_id, scene_id, template, finishAction) { /** * Executes the SQL request and calls the callback */ +// Abandon all hope ye who enter here DBReq.ExpCreator.prototype.execute = function() { var self = this; this.client.query( - "INSERT INTO experiment(user_id, scene_id, template) VALUES($1,$2,$3);", - [self.user_id, self.scene_id, self.template], + "SELECT DISTINCT RecommendationStyle.name, CoinCombination.scene_id, CoinCombination.id, coin_1, coin_2, coin_3, coin_4, coin_5, coin_6, coin_7, coin_8\n" + + "FROM CoinCombination, Experiment, Users U, Users Other, RecommendationStyle, Scene\n" + + "WHERE\n" + + " Scene.id = CoinCombination.scene_id AND\n" + + " Scene.name != 'peachcastle' AND\n" + + " CoinCombination.id = Experiment.coin_combination_id AND\n" + + " Other.id = Experiment.user_id AND\n" + + " Other.rating = U.rating AND\n" + + " Other.id != U.id AND\n" + + " U.id = $1 AND\n" + + " RecommendationStyle.name NOT IN (\n" + + " SELECT DISTINCT Experiment.recommendation_style\n" + + " FROM CoinCombination, Experiment, Users U, Users Other\n" + + " WHERE\n" + + " CoinCombination.id = Experiment.coin_combination_id AND\n" + + " Other.id = Experiment.user_id AND\n" + + " Other.rating = U.rating AND\n" + + " Other.id != U.id AND\n" + + " CoinCombination.scene_id = Scene.id\n" + + " ) AND\n" + + " RecommendationStyle.name NOT IN (\n" + + " SELECT DISTINCT Experiment.recommendation_style\n" + + " FROM Experiment\n" + + " WHERE Experiment.user_id = $1 AND Experiment.recommendation_style != ''\n" + + " ) AND\n" + + " CoinCombination.scene_id NOT IN (\n" + + " SELECT DISTINCT scene_id\n" + + " FROM Experiment, CoinCombination\n" + + " WHERE Experiment.coin_combination_id = CoinCombination.id AND Experiment.user_id = $1\n" + + " )\n" + + "LIMIT 1;", + [self.user_id], function(err, result) { - self.client.query("SELECT MAX(id) AS id FROM experiment;", function(err, result) { - self.finalResult = result.rows[0].id; - self.finish(); - }); + console.log(result.rows); + if (result.rows.length > 0) { + // Set the result + self.finalResult.coin_combination_id = result.rows[0].id; + self.finalResult.scene_id = result.rows[0].scene_id; + self.finalResult.recommendation_style = result.rows[0].name; + self.finalResult.coins = [ + result.rows[0].coin_1, + result.rows[0].coin_2, + result.rows[0].coin_3, + result.rows[0].coin_4, + result.rows[0].coin_5, + result.rows[0].coin_6, + result.rows[0].coin_7, + result.rows[0].coin_8 + ]; + + // There is a suggested experiment : create it + self.client.query( + "INSERT INTO Experiment(user_id, coin_combination_id, recommendation_style)\n" + + "VALUES($1,$2,$3)\n" + + "RETURNING id", + [self.user_id, result.rows[0].id, result.rows[0].name], + function(err, result) { + self.finalResult.exp_id = result.rows[0].id; + self.finish(); + } + ); + + + } else { + // Find the scene / recommendation style for the new experiment + self.client.query( + "SELECT RecommendationStyle.name, Scene.id as scene_id\n" + + "FROM RecommendationStyle, Scene\n" + + "WHERE\n" + + " RecommendationStyle.name NOT IN(\n" + + " SELECT Experiment.recommendation_style AS name\n" + + " FROM Experiment\n" + + " WHERE Experiment.user_id = $1\n" + + " AND Experiment.recommendation_style != ''\n" + + " ) AND\n" + + " Scene.id NOT IN(\n" + + " SELECT DISTINCT CoinCombination.scene_id AS id\n" + + " FROM Experiment, CoinCombination\n" + + " WHERE\n" + + " Experiment.coin_combination_id = CoinCombination.id AND\n" + + " user_id = $1\n" + + " ) AND\n" + + " Scene.name != 'peachcastle'\n" + + "\n" + + "ORDER BY RANDOM()\n" + + "LIMIT 1;", + [self.user_id], + function(err, result) { + if (result.rows.length > 0) { + self.finalResult.scene_id = result.rows[0].scene_id; + self.finalResult.recommendation_style = result.rows[0].name; + + // Generate random coins + self.client.query( + "SELECT generate_series AS id\n" + + "FROM Scene, generate_series(0,Scene.coin_number-1)\n" + + "WHERE Scene.id = $1\n" + + "ORDER BY RANDOM()\n" + + "LIMIT 8;", + [self.finalResult.scene_id], + function(err, result) { + self.finalResult.coins = []; + for (var i = 0; i < 8; i++) { + self.finalResult.coins.push(result.rows[i].id); + } + + // And then, create the CoinCombination + self.client.query( + "INSERT INTO CoinCombination(scene_id, coin_1, coin_2, coin_3, coin_4, coin_5, coin_6, coin_7, coin_8)\n" + + "VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9)\n" + + "RETURNING id;", + [ + self.finalResult.scene_id, + self.finalResult.coins[0], + self.finalResult.coins[1], + self.finalResult.coins[2], + self.finalResult.coins[3], + self.finalResult.coins[4], + self.finalResult.coins[5], + self.finalResult.coins[6], + self.finalResult.coins[7], + ], + function(err, result) { + self.finalResult.coin_combination_id = result.rows[0].id; + + // And create the experiment + self.client.query( + "INSERT INTO Experiment(user_id, coin_combination_id, recommendation_style)\n" + + "VALUES($1,$2,$3)\n" + + "RETURNING id;", + [self.user_id, self.finalResult.coin_combination_id, self.finalResult.recommendation_style], + function(err, result) { + self.finalResult.exp_id = result.rows[0].id; + self.finish(); + } + ); + } + ); + } + ); + } else { + self.finish(); + } + } + ); + } } ); }; @@ -546,7 +706,13 @@ DBReq.ExpCreator.prototype.finish = function() { this.client = null; this.release = null; - this.finishAction(this.finalResult); + this.finishAction( + this.finalResult.exp_id, + this.finalResult.coin_combination_id, + this.finalResult.scene_id, + this.finalResult.recommendation_style, + this.finalResult.coins + ); }; /** @@ -731,6 +897,77 @@ DBReq.ExpGetter.prototype.finish = function() { this.finishAction(this.finalResult); }; +DBReq.TutorialCreator = function(id, finishAction) { + this.id = id; + this.finishAction = finishAction; + + var self = this; + pg.connect(pgc.url, function(err, client, release) { + self.client = client; + self.release = release; + self.execute(); + }); +}; + +DBReq.TutorialCreator.prototype.execute = function() { + var self = this; + this.client.query( + // Generate random coins + "SELECT Scene.id AS scene_id, generate_series AS id\n" + + "FROM Scene, generate_series(1,Scene.coin_number)\n" + + "WHERE Scene.name = 'peachcastle'\n" + + "ORDER BY RANDOM()\n" + + "LIMIT 8;", + [], + function(err, result) { + self.coins = []; + for (var i = 0; i < 8; i++) { + self.coins.push(result.rows[i].id); + } + + // Create CoinCombination + self.client.query( + "INSERT INTO CoinCombination(scene_id, coin_1, coin_2, coin_3, coin_4, coin_5, coin_6, coin_7, coin_8)\n" + + "VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9)\n" + + "RETURNING id;", + [ + result.rows[0].scene_id, + result.rows[0].id, + result.rows[1].id, + result.rows[2].id, + result.rows[3].id, + result.rows[4].id, + result.rows[5].id, + result.rows[6].id, + result.rows[7].id + ], + function(err, result) { + // Create experiment + self.client.query( + "INSERT INTO Experiment(user_id, coin_combination_id)\n" + + "VALUES($1,$2)\n" + + "RETURNING id;", + [self.id, result.rows[0].id], + function(err, result) { + console.log(err); + self.finalResult = result.rows[0].id; + self.finish(); + } + ); + } + ); + } + ); +}; + +DBReq.TutorialCreator.prototype.finish = function() { + this.release(); + this.release = null; + this.client = null; + + this.finishAction(this.finalResult); +} + /** * Try to get a user by id, and creates it if it doesn't exists * @param id {Number} id to test @@ -777,8 +1014,12 @@ DBReq.createUser = function(callback) { * @param scene_id {Number} id of the scene on which the experiment is * @param callback {function} callback called on the new experiment id */ -DBReq.createExp = function(id, scene_id, template, callback) { - new DBReq.ExpCreator(id, scene_id, template, callback); +DBReq.createExp = function(id, callback) { + new DBReq.ExpCreator(id, callback); +}; + +DBReq.createTutorial = function(id, callback) { + new DBReq.TutorialCreator(id, callback); }; /** diff --git a/controllers/prototype/index.js b/controllers/prototype/index.js index 976bc61..d331567 100644 --- a/controllers/prototype/index.js +++ b/controllers/prototype/index.js @@ -70,7 +70,7 @@ var generateRecommendationStyle = function(req, res) { } return req.session.recos.shift(); -} +}; var sceneToFunction = function(scene) { switch (scene) { @@ -85,40 +85,39 @@ var sceneToFunction = function(scene) { } }; -var protoHelper = function(template) { +module.exports.game = function(req, res) { - return function(req, res) { + db.tryUser(req.session.user_id, function(id) { - template = generateRecommendationStyle(req, res); + var scene = generateSceneNumber(req, res); + res.locals.scene = sceneToFunction(scene); + res.locals.recommendationStyle = 'L3D.ArrowRecommendation'; + req.session.user_id = id; - if (template === undefined) { - res.redirect('/feedback'); - return; - } + db.createExp(id, function(expId, coinCombinationId, sceneId, recommendationStyle, coins) { - db.tryUser(req.session.user_id, function(id) { - // Get random scene number - var scene = generateSceneNumber(req, res); - res.locals.scene = sceneToFunction(scene); - req.session.user_id = id; + if (expId === undefined) { - db.createExp(id, req.session.scenes[req.session.currentSceneIndex], template, function(id) { - req.session.exp_id = id; - req.session.save(); - res.setHeader('Content-Type','text/html'); - res.render(template, res.locals, function(err, result) { - res.send(result); - }); + res.redirect('/feedback'); + return; + + } + + req.session.exp_id = expId; + req.session.save(); + + res.locals.scene = sceneToFunction(sceneId); + res.locals.recommendationStyle = recommendationStyle; + res.locals.coins = coins; + + res.setHeader('Content-Type','text/html'); + res.render('prototype_recommendation.jade', res.locals, function(err, result) { + res.send(result); }); }); - }; + }); }; -module.exports.arrows = protoHelper('prototype_arrows.jade'); -module.exports.viewports = protoHelper('prototype_viewports.jade'); -module.exports.reverse = protoHelper('prototype_reverse.jade'); -module.exports.empty = protoHelper('prototype_empty.jade'); - module.exports.sponza = function(req, res) { res.setHeader('Content-Type', 'text/html'); @@ -174,7 +173,7 @@ module.exports.tutorial = function(req, res) { req.session.user_id = id; // 1 is the ID of peach scene - db.createExp(id, 1, null, function(id) { + db.createTutorial(id, function(id) { req.session.exp_id = id; req.session.save(); diff --git a/controllers/prototype/urls.js b/controllers/prototype/urls.js index 62616e1..4a829c7 100644 --- a/controllers/prototype/urls.js +++ b/controllers/prototype/urls.js @@ -1,9 +1,6 @@ module.exports = { '/prototype': 'index', - '/prototype/arrows': 'arrows', - '/prototype/viewports': 'viewports', - '/prototype/empty': 'empty', - '/prototype/reverse': 'reverse', + '/prototype/game': 'game', '/prototype/replay': 'replay_index', '/prototype/replay/:id': 'replay', '/prototype/replay_info/:id': 'replay_info', diff --git a/controllers/prototype/views/prototype_recommendation.jade b/controllers/prototype/views/prototype_recommendation.jade new file mode 100644 index 0000000..a7ecbc8 --- /dev/null +++ b/controllers/prototype/views/prototype_recommendation.jade @@ -0,0 +1,25 @@ +extends ./prototype_interactive + +block title + title #{title} - Prototype - Arrows + +block configjs + script Recommendation = #{recommendationStyle}; coinsId = [#{coins}]; + script. + Coin.onLastCoin = function() { + $('#next').show(); + $('#next').click(function() { + window.location = '/prototype/game'; + }); + }; + +block lastbutton + button#next.btn.btn-success.navbar-btn(style={'margin-right':'10px', 'margin-bottom':'10px', 'display':'none'}) + span Go to the next step + +//-block preciseDescription + p + | Recommended views are displayed with a + | transparent blue arrow. They disappear when you + | come closer to them, and shows the motion between + | your current position and the recommendation. diff --git a/js/l3d/apps/prototype/tutorial/TutorialSteps.js b/js/l3d/apps/prototype/tutorial/TutorialSteps.js index 9666af9..ce3c309 100644 --- a/js/l3d/apps/prototype/tutorial/TutorialSteps.js +++ b/js/l3d/apps/prototype/tutorial/TutorialSteps.js @@ -159,8 +159,8 @@ TutorialSteps.prototype.nextStep = function() { } // Block camera - for (var key in camera.motion) { - camera.motion[key] = false; + for (var key in this.camera.motion) { + this.camera.motion[key] = false; } Coin.domElement.style.display = ""; diff --git a/js/l3d/src/cameras/PointerCamera.js b/js/l3d/src/cameras/PointerCamera.js index 90c4b38..6dd8177 100644 --- a/js/l3d/src/cameras/PointerCamera.js +++ b/js/l3d/src/cameras/PointerCamera.js @@ -582,6 +582,8 @@ L3D.PointerCamera.prototype.onKeyEvent = function(event, toSet) { // Log any change var e = new L3D.DB.Event.KeyboardEvent(); e.camera = this; + e.keypressed = toSet; + e.keycode = event.keyCode; e.send(); } }; @@ -667,6 +669,8 @@ L3D.PointerCamera.prototype.onMouseUp = function(event) { if (this.dragging && this.mouseMoved && !this.moving && !this.movingHermite) { var e = new L3D.DB.Event.KeyboardEvent(); e.camera = this; + e.keypressed = false; + e.keycode = -1; e.send(); } diff --git a/js/l3d/src/scenes/initScene.js b/js/l3d/src/scenes/initScene.js index 773b018..2db4e83 100644 --- a/js/l3d/src/scenes/initScene.js +++ b/js/l3d/src/scenes/initScene.js @@ -219,9 +219,17 @@ L3D.generateCoins = function(totalCoins, coin_ids) { var i = 0; - if (coin_ids === undefined) - L3D.shuffle(totalCoins); - else if (coin_ids === null) { + if (coin_ids === undefined) { + var tmp = []; + // Use global variable coinsId + for (i = 0; i < totalCoins.length; i++) { + tmp.push(totalCoins[i]); + } + totalCoins.length = 0; + for (var i = 0; i < 8; i++) { + totalCoins.push(tmp[coinsId[i]]); + } + } else if (coin_ids === null) { return []; } else { @@ -247,7 +255,9 @@ L3D.generateCoins = function(totalCoins, coin_ids) { var bound = (coin_ids instanceof Array && coin_ids.length === 0) ? totalCoins.length : 8; + console.log(totalCoins); for (i = 0; i < bound; i++) { + console.log(i + '/' + totalCoins.length); coins.push(totalCoins[i].coin); totalCoins[i].coin.id = totalCoins[i].id; indices.push(totalCoins[i].id); diff --git a/js/l3d/src/utils/Logger.js b/js/l3d/src/utils/Logger.js index 9365eb8..2219a4f 100644 --- a/js/l3d/src/utils/Logger.js +++ b/js/l3d/src/utils/Logger.js @@ -158,7 +158,9 @@ L3D.DB.Event.KeyboardEvent.prototype.send = function() { var url = "/posts/keyboard-event"; var data = { - camera: L3D.DB.Private.compactCamera(this.camera) + camera: L3D.DB.Private.compactCamera(this.camera), + keycode: this.keycode, // -1 represents mouse event + keypressed: this.keypressed // mousepressed if keycode === -1 }; L3D.DB.Private.sendData(url, data); diff --git a/posts/keyboard-event/index.js b/posts/keyboard-event/index.js index 9dc7749..37685f2 100644 --- a/posts/keyboard-event/index.js +++ b/posts/keyboard-event/index.js @@ -6,8 +6,8 @@ module.exports.index = function(req, res) { pg.connect(secret.url, function(err, client, release) { client.query( - "INSERT INTO keyboardevent(exp_id, camera, time)" + - "VALUES($1, ROW(ROW($2,$3,$4),ROW($5,$6,$7)), to_timestamp($8));" , + "INSERT INTO keyboardevent(exp_id, camera, time, keycode, keypressed)" + + "VALUES($1, ROW(ROW($2,$3,$4),ROW($5,$6,$7)), to_timestamp($8), $9, $10);" , [ req.session.exp_id, req.body.camera.position.x, @@ -18,7 +18,10 @@ module.exports.index = function(req, res) { req.body.camera.target.y, req.body.camera.target.z, - req.body.time + req.body.time, + + req.body.keycode, + req.body.keypressed ], function(err, result) { if (err !== null) diff --git a/sql/backup.pgsql b/sql/backup.pgsql index 5f4adaf..556a49f 100644 --- a/sql/backup.pgsql +++ b/sql/backup.pgsql @@ -13,6 +13,7 @@ DROP TABLE IF EXISTS PointerLocked CASCADE; DROP TABLE IF EXISTS SwitchedLockOption CASCADE; DROP TABLE IF EXISTS Coin CASCADE; DROP TABLE IF EXISTS CoinCombination CASCADE; +DROP TABLE IF EXISTS RecommendationStyle CASCADE; DROP TYPE IF EXISTS VECTOR3 CASCADE; DROP TYPE IF EXISTS CAMERA CASCADE; @@ -45,6 +46,12 @@ CREATE TABLE Users( ); CREATE TABLE Scene( + id SERIAL PRIMARY KEY, + name CHAR(50), + coin_number INTEGER +); + +CREATE TABLE RecommendationStyle( id SERIAL PRIMARY KEY, name CHAR(50) ); @@ -66,7 +73,7 @@ CREATE TABLE Experiment( id SERIAL PRIMARY KEY, user_id SERIAL REFERENCES Users (id), coin_combination_id SERIAL REFERENCES CoinCombination (id), - template VARCHAR(30) + recommendation_style VARCHAR(30) ); CREATE TABLE Coin( @@ -75,10 +82,14 @@ CREATE TABLE Coin( ); -- Init scene table -INSERT INTO Scene(name) VALUES ('peachcastle'); -INSERT INTO Scene(name) VALUES ('bobomb'); -INSERT INTO Scene(name) VALUES ('coolcoolmountain'); -INSERT INTO Scene(name) VALUES ('whomp'); +INSERT INTO Scene(name, coin_number) VALUES ('peachcastle' , 41); +INSERT INTO Scene(name, coin_number) VALUES ('bobomb' , 44); +INSERT INTO Scene(name, coin_number) VALUES ('coolcoolmountain', 69); +INSERT INTO Scene(name, coin_number) VALUES ('whomp' , 50); + +INSERT INTO RecommendationStyle(name) VALUES('L3D.EmptyRecommendation'); +INSERT INTO RecommendationStyle(name) VALUES('L3D.ArrowRecommendation'); +INSERT INTO RecommendationStyle(name) VALUES('L3D.ViewportRecommendation'); -- Events CREATE TABLE ArrowClicked( @@ -99,6 +110,8 @@ CREATE TABLE KeyboardEvent( id SERIAL PRIMARY KEY, exp_id SERIAL REFERENCES Experiment (id), time TIMESTAMP DEFAULT NOW(), + keycode INTEGER, + keypressed BOOLEAN, camera CAMERA ); @@ -144,83 +157,3 @@ CREATE TABLE SwitchedLockOption( time TIMESTAMP DEFAULT NOW(), locked BOOLEAN ); - --- Fill with example -INSERT INTO Users(rating) VALUES(3); -INSERT INTO Users(rating) VALUES(3); -INSERT INTO Users(rating) VALUES(3); -INSERT INTO Users(rating) VALUES(3); - -INSERT INTO CoinCombination(scene_id) VALUES (2); -INSERT INTO CoinCombination(scene_id) VALUES (3); -INSERT INTO CoinCombination(scene_id) VALUES (4); -INSERT INTO CoinCombination(scene_id) VALUES (4); - -INSERT INTO Experiment(user_id, coin_combination_id, template) VALUES(1, 1, '1'); -INSERT INTO Experiment(user_id, coin_combination_id, template) VALUES(2, 1, '2'); --- INSERT INTO Experiment(user_id, coin_combination_id, template) VALUES(2, 1, '3'); - -INSERT INTO Experiment(user_id, coin_combination_id, template) VALUES(1, 2, '2'); -INSERT INTO Experiment(user_id, coin_combination_id, template) VALUES(2, 2, '3'); - -INSERT INTO Experiment(user_id, coin_combination_id, template) VALUES(3, 4, '3'); - -SELECT * FROM Experiment; - --- PROTO request -SELECT DISTINCT CoinCombination.id AS id, all_template as template, CoinCombination.scene_id as scene_id -FROM Experiment, CoinCombination, - (SELECT * FROM generate_series(1,3) all_template) ali - -WHERE - Experiment.coin_combination_id = CoinCombination.id AND - CoinCombination.scene_id IN( - - SELECT CoinCombination.scene_id as scene_id - - FROM Users, Experiment, CoinCombination - - WHERE - Experiment.user_id = Users.id AND - Experiment.coin_combination_id = CoinCombination.id AND - Users.rating = 3 AND - Users.id != 3 AND - all_template NOT IN ( - SELECT CAST(template AS INTEGER) - FROM Experiment, Users - WHERE Experiment.user_id = Users.id AND Users.id = 3 - ) AND - CoinCombination.scene_id NOT IN( - SELECT scene_id FROM Experiment, Users - WHERE Experiment.user_id = Users.id AND Users.id = 3 - ) - - GROUP BY CoinCombination.scene_id - - HAVING COUNT(DISTINCT Experiment.template) < 3 - -) - -EXCEPT - -SELECT CoinCombination.id AS id, CAST(Experiment.template AS INTEGER) as template, CoinCombination.scene_id as scene_id -FROM Experiment, CoinCombination -WHERE - Experiment.coin_combination_id = CoinCombination.id AND - CoinCombination.scene_id IN( - - SELECT CoinCombination.scene_id as scene_id - - FROM Users, Experiment, CoinCombination - - WHERE - Experiment.user_id = Users.id AND - Experiment.coin_combination_id = CoinCombination.id AND - Users.rating = 3 AND - Users.id != 3 - - GROUP BY CoinCombination.scene_id - - HAVING COUNT(DISTINCT Experiment.template) < 3 - -);