diff --git a/frontend/static/js/index.js b/frontend/static/js/index.js index 349c8a9..e590f35 100644 --- a/frontend/static/js/index.js +++ b/frontend/static/js/index.js @@ -4,8 +4,7 @@ import Search from "./views/Search.js"; import HomeView from "./views/HomeView.js"; import LogoutView from "./views/accounts/LogoutView.js"; import GameOfflineView from "./views/GameOfflineView.js"; -import GameView2D from "./views/GameView.js"; -import GameView3D from "./views/GameView3D.js"; +import GameView from "./views/GameView.js"; import PageNotFoundView from './views/PageNotFoundView.js' ; @@ -92,7 +91,7 @@ const router = async(uri) => { { path: "/matchmaking", view: MatchMakingView }, { path: "/games/offline", view: GameOfflineView }, { path: "/tictactoe", view: TicTacToeView }, - { path: "/games/pong/:id", view: GameView2D }, + { path: "/games/pong/:id", view: GameView }, { path: "/games/tictactoe/:id", view: TicTacToeOnlineView }, ]; diff --git a/frontend/static/js/views/GameView.js b/frontend/static/js/views/GameView.js index 6d9dd39..011d6aa 100644 --- a/frontend/static/js/views/GameView.js +++ b/frontend/static/js/views/GameView.js @@ -3,272 +3,400 @@ import { Game } from "../api/game/Game.js"; import AbstractView from "./abstracts/AbstractView.js"; import { MyPlayer } from "../api/game/MyPlayer.js"; import { Player } from "../api/game/Player.js"; +import { initShaderProgram, shaderInfos } from "../3D/shaders.js" +import { initBuffers } from "../3D/buffers.js" +import "../3D/maths/gl-matrix-min.js" import "../chartjs/chart.umd.min.js"; import { get_labels, transformData } from "../utils/graph.js"; import { sleep } from "../utils/sleep.js"; export default class extends AbstractView { - constructor(params) + constructor(params) { - super(params, "Game"); - this.game_id = params.id; + super(params, "Game"); + this.game_id = params.id; + this.ctx = null; + this.shader_prog = null; + this.buffers = null; + this.cam_pos = [0, 400, 0]; + this.cam_target = [0, 0, 0]; + this.cam_up = [0, 0, -1]; + this.game_mode = 1; // 1 is 2D, 2 is 3D } + initWebGL() + { + let canva = document.createElement("canvas"); + canva.height = this.game.config.size_x; + canva.width = this.game.config.size_y; + canva.id = "canva"; + document.getElementById("app").appendChild(canva); + + this.ctx = canva.getContext("webgl"); + + if(this.ctx === null) + { + alert("Unable to initialize WebGL. Your browser or machine may not support it. You may also be a bozo"); + return; + } + + this.shader_prog = initShaderProgram(this.ctx); + this.buffers = initBuffers(this.ctx); + + this.ctx.enable(this.ctx.CULL_FACE); + this.ctx.cullFace(this.ctx.BACK); + } + + init2D() + { + let canva = document.createElement("canvas"); + canva.height = this.game.config.size_x; + canva.width = this.game.config.size_y; + canva.id = "canva"; + document.getElementById("app").appendChild(canva); + + this.ctx = canva.getContext('2d'); + } + keyReleaseHandler(event) - { + { const idx = this.keys_pressed.indexOf(event.key); if (idx != -1) this.keys_pressed.splice(idx, 1); } - keyPressHandler(event) - { + keyPressHandler(event) + { if (!this.keys_pressed.includes(event.key)) this.keys_pressed.push(event.key); } - render_game() - { - const canva = document.getElementById('canva'); + setNormalAttribute() + { + const numComponents = 3; + const type = this.ctx.FLOAT; + const normalize = false; + const stride = 0; + const offset = 0; + this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.buffers.normal); + this.ctx.vertexAttribPointer( + shaderInfos.attribLocations.vertexNormal, + numComponents, + type, + normalize, + stride, + offset, + ); + this.ctx.enableVertexAttribArray(shaderInfos.attribLocations.vertexNormal); + } - if (canva === null) - return 1; + setPositionAttribute() + { + const numComponents = 3; + const type = this.ctx.FLOAT; + const normalize = false; + const stride = 0; + const offset = 0; + this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.buffers.vertex); + this.ctx.bindBuffer(this.ctx.ELEMENT_ARRAY_BUFFER, this.buffers.index); + this.ctx.vertexAttribPointer( + shaderInfos.attribLocations.vertexPosition, + numComponents, + type, + normalize, + stride, + offset + ); + this.ctx.enableVertexAttribArray(shaderInfos.attribLocations.vertexPosition); + } - /** - * @type {CanvasRenderingContext2D} - */ - let ctx = canva.getContext('2d'); + render_game() + { + const canva = document.getElementById('canva'); + if (canva === null) + return 1; - ctx.beginPath(); + if(this.ctx instanceof CanvasRenderingContext2D) + { + this.ctx.beginPath(); + this.game.render(this.ctx); + this.ctx.strokeStyle = "#000000"; + this.ctx.lineWidth = 1; + this.ctx.stroke(); + } + else if(this.ctx instanceof WebGLRenderingContext) + { + this.ctx.clearColor(0.1, 0.1, 0.1, 1.0); + this.ctx.clearDepth(1.0); + this.ctx.enable(this.ctx.DEPTH_TEST); + this.ctx.depthFunc(this.ctx.LEQUAL); + this.ctx.clear(this.ctx.COLOR_BUFFER_BIT | this.ctx.DEPTH_BUFFER_BIT); - this.game.render(ctx); + const projectionMatrix = mat4.create(); + const viewMatrix = mat4.create(); - ctx.strokeStyle = "#000000"; - ctx.lineWidth = 1; - ctx.stroke(); - } + mat4.perspective(projectionMatrix, (90 * Math.PI) / 180, this.ctx.canvas.clientWidth / this.ctx.canvas.clientHeight, 0.1, 10000000.0); + mat4.lookAt(viewMatrix, this.cam_pos, this.cam_target, this.cam_up); - /** - * @param {Player} player - * @returns { Promise } - */ - async on_goal(player) - { - document.getElementById(`goal-${player.id}`).innerText = player.score.length; - } + this.setPositionAttribute(); + this.setNormalAttribute(); - /** - * @param {*} data - * @returns { Promise } - */ - async on_finish(data) - { - await reloadView(); - } + this.ctx.useProgram(shaderInfos.program); - render() - { - let loop_id = setInterval(() => { - if (this.game === undefined) - clearInterval(loop_id); - this.my_player?.update_paddle(this.keys_pressed); - this.render_game(); - this.game?.time?.new_frame(); - //clearInterval(loop_id); - // 1 sec fps - }, 1000 / 60); - } + this.ctx.uniformMatrix4fv(shaderInfos.uniformLocations.projectionMatrix, false, projectionMatrix); + this.ctx.uniformMatrix4fv(shaderInfos.uniformLocations.viewMatrix, false, viewMatrix); - register_key() - { + this.game.render(this.ctx); + } + else + { + alert('Unknown rendering context type'); + } + } + + /** + * @param {Player} player + * @returns { Promise } + */ + async on_goal(player) + { + document.getElementById(`goal-${player.id}`).innerText = player.score.length; + } + + /** + * @param {*} data + * @returns { Promise } + */ + async on_finish(data /* unused */) + { + await reloadView(); + } + + render() + { + let loop_id = setInterval(() => { + if (this.game === undefined) + clearInterval(loop_id); + this.my_player?.update_paddle(this.keys_pressed); + this.render_game(); + this.game?.time?.new_frame(); + //clearInterval(loop_id); + // 1 sec fps + }, 1000 / 60); + } + + async toggleGameMode() + { + if(this.game_mode === 1) // 3D + { + this.game_mode = 2; + document.getElementById("game-mode").value = "Switch to 2D"; + } + else if(this.game_mode === 2) // 2D + { + this.game_mode = 1; + document.getElementById("game-mode").value = "Switch to 3D"; + } + const canva = document.getElementById('canva'); + if (canva === null) + return; + document.getElementById("app").removeChild(canva); + this.createGameBoard(this.game_mode); + } + + register_key() + { this.keyPressHandler = this.keyPressHandler.bind(this); this.keyReleaseHandler = this.keyReleaseHandler.bind(this); - document.addEventListener('keydown', this.keyPressHandler); + document.addEventListener('keydown', this.keyPressHandler); document.addEventListener('keyup', this.keyReleaseHandler); - } + } - unregister_key() - { - document.removeEventListener('keydown', this.keyPressHandler); + unregister_key() + { + document.removeEventListener('keydown', this.keyPressHandler); document.removeEventListener('keyup', this.keyReleaseHandler); - } + } - createGameBoard() - { - let canva = document.createElement("canvas"); + /** + * @param {int} game_mode + * @returns { Cramptex } + */ + createGameBoard(game_mode) + { + if(game_mode === 1) + this.init2D(); + else if(game_mode === 2) + this.initWebGL(); + } - canva.height = this.game.config.size_x; - canva.width = this.game.config.size_y; - canva.id = "canva"; + createMyPlayer() + { + let index = this.game.players.findIndex((player) => player.id === client.me.id); - document.getElementById("app").appendChild(canva); - } + if (index !== -1) + { + let my_player = this.game.players[index]; + this.my_player = new MyPlayer(client, + this.game, + my_player.score, + my_player.rail, + my_player.position, + ); + this.game.players[index] = this.my_player; + } + } - createMyPlayer() - { - let index = this.game.players.findIndex((player) => player.id === client.me.id); + async join_game() + { + document.getElementById("game-mode").onclick = this.toggleGameMode.bind(this); + await this.game.join(); + this.createGameBoard(1); // create the board for 2D game by default. Can switch to 3D with a toggle + this.createMyPlayer(); + this.displayPlayersList(); + this.register_key(); + this.render(); + } - if (index !== -1) - { - let my_player = this.game.players[index]; - this.my_player = new MyPlayer(client, - this.game, - my_player.score, - my_player.rail, - my_player.position, - ); - this.game.players[index] = this.my_player; - } - } + async update_game_state() + { + document.getElementById("game-state").innerText = this.game.state; - async join_game() - { - await this.game.join(); + if (this.game.finished === false) + await this.join_game(); + else + { + this.createGraph(); + let toggle = document.getElementById("game-mode"); + if(toggle !== null) + toggle.remove(); + } + } - this.createGameBoard(); + createGraph() + { + let players = this.game.players; - this.createMyPlayer(); + if (players === undefined) + return; - this.displayPlayersList(); + let graph = document.createElement("canvas"); - this.register_key(); + graph.height = 450; + graph.width = 800; + graph.id = "graph"; - this.render(); - } + document.getElementById("app").appendChild(graph); - async update_game_state() - { - document.getElementById("game-state").innerText = this.game.state; + if (graph === null) + return; - if (this.game.finished === false) - await this.join_game(); - else - this.createGraph(); - } + let datasets = []; - createGraph() - { - let players = this.game.players; - - if (players === undefined) - return; - - let graph = document.createElement("canvas"); + players.forEach(player => { - graph.height = 450; - graph.width = 800; - graph.id = "graph"; + let data = transformData(player.score); - document.getElementById("app").appendChild(graph); + data = [{x: 0, y: 0}, ...data]; - if (graph === null) - return; - - let datasets = []; + data.push({x: Math.round((this.game.stop_timestamp - this.game.start_timestamp) / 1000), + y: data[data.length - 1].y}); - players.forEach(player => { + datasets.push({ + data: data, + label: player.username, + borderColor: `#${Math.floor(Math.random() * 16777215).toString(16)}`, + fill: false, + }); + }); - let data = transformData(player.score); + this.chart = new Chart(graph, { + type: "line", + data: { + labels: get_labels(datasets), + datasets: datasets, + } + }); + } - data = [{x: 0, y: 0}, ...data]; - - data.push({x: Math.round((this.game.stop_timestamp - this.game.start_timestamp) / 1000), - y: data[data.length - 1].y}); + displayPlayersList() + { + let table = document.createElement("table"), + thead = document.createElement("thead"), + tr = document.createElement("tr"), + usernameTitle = document.createElement("th"), + goalTitle = document.createElement("th"), + playersList = document.createElement("tbody") + ; - datasets.push({ - data: data, - label: player.username, - borderColor: `#${Math.floor(Math.random()*16777215).toString(16)}`, - fill: false, - }); - }); + usernameTitle.innerText = lang.get("gamePlayersListName"); + goalTitle.innerText = lang.get("gameGoalTaken"); - this.chart = new Chart(graph, { - type: "line", - data: { - labels: get_labels(datasets), - datasets: datasets, - } - }); - } + tr.appendChild(usernameTitle); + tr.appendChild(goalTitle); - displayPlayersList() - { - let table = document.createElement("table"), - thead = document.createElement("thead"), - tr = document.createElement("tr"), - usernameTitle = document.createElement("th"), - goalTitle = document.createElement("th"), - playersList = document.createElement("tbody") - ; - - usernameTitle.innerText = lang.get("gamePlayersListName"); - goalTitle.innerText = lang.get("gameGoalTaken"); + table.appendChild(thead); + table.appendChild(playersList); - tr.appendChild(usernameTitle); - tr.appendChild(goalTitle); + document.getElementById("app").appendChild(table); - table.appendChild(thead); - table.appendChild(playersList); + this.game.players.forEach(player => { - document.getElementById("app").appendChild(table); + let tr = document.createElement("tr"); + let name = document.createElement("td"); + let goal = document.createElement("td"); - this.game.players.forEach(player => { + name.id = `username-${player.id}`; + name.innerText = player.username; - let tr = document.createElement("tr"); - let name = document.createElement("td"); - let goal = document.createElement("td"); + goal.id = `goal-${player.id}`; + goal.innerText = player.score.length; - name.id = `username-${player.id}`; - name.innerText = player.username; + tr.appendChild(name); + tr.appendChild(goal); - goal.id = `goal-${player.id}`; - goal.innerText = player.score.length; + playersList.appendChild(tr); + }); + } - tr.appendChild(name); - tr.appendChild(goal); - - playersList.appendChild(tr); - }); - } - - async on_disconnect() - { - sleep(500); - await reloadView(); - } + async on_disconnect() + { + sleep(500); + await reloadView(); + } async postInit() { - this.game = new Game(client, this.game_id, this.on_disconnect, this.on_goal, this.on_finish); - this.keys_pressed = []; - this.my_player = undefined; + this.game = new Game(client, this.game_id, this.on_disconnect, this.on_goal, this.on_finish); + this.keys_pressed = []; + this.my_player = undefined; let error_code = await this.game.init(); - if (error_code) - return error_code; + if (error_code) + return error_code; - await this.update_game_state(); + await this.update_game_state(); } - async leavePage() - { - if (this.game.finished === false) - { - this.game.leave(); - this.game = undefined; - } - this.game = undefined; - this.unregister_key(); - } - - async getHtml() + async leavePage() { - return /* HTML */ ` - -

- `; - } + if (this.game.finished === false) + { + this.game.leave(); + this.game = undefined; + } + this.game = undefined; + this.unregister_key(); + } + + async getHtml() + { + return /* HTML */ ` + +

+ + `; + } } diff --git a/frontend/static/js/views/GameView3D.js b/frontend/static/js/views/GameView3D.js deleted file mode 100644 index d78a442..0000000 --- a/frontend/static/js/views/GameView3D.js +++ /dev/null @@ -1,266 +0,0 @@ -import { client } from "../index.js"; -import { Game } from "../api/game/Game.js"; -import AbstractView from "./abstracts/AbstractView.js"; -import { initShaderProgram, shaderInfos } from "../3D/shaders.js" -import { initBuffers } from "../3D/buffers.js" -import "../3D/maths/gl-matrix-min.js" -import { MyPlayer } from "../api/game/MyPlayer.js"; -import { lang } from "../index.js"; - -export default class extends AbstractView -{ - constructor(params) - { - super(params, "Game"); - this.game = new Game(client, params.id, this.update_goal); - this.keys_pressed = []; - this.my_player = undefined; - this.gl = null; - this.shader_prog = null; - this.buffers = null; - this.cam_pos = [0, 400, 0]; - this.cam_target = [0, 0, 0]; - this.cam_up = [0, 0, -1]; - } - - keyReleaseHandler(event) - { - let idx = this.keys_pressed.indexOf(event.key); - if(idx != -1) - this.keys_pressed.splice(idx, 1); - } - - keyPressHandler(event) - { - if(!this.keys_pressed.includes(event.key)) - this.keys_pressed.push(event.key); - } - - initGL() - { - const canvas = document.getElementById('canva'); - this.gl = canvas.getContext("webgl"); - - if(this.gl === null) - { - alert("Unable to initialize WebGL. Your browser or machine may not support it. You may also be a bozo"); - return; - } - - this.shader_prog = initShaderProgram(this.gl); - this.buffers = initBuffers(this.gl); - - this.gl.enable(this.gl.CULL_FACE); - this.gl.cullFace(this.gl.BACK); - } - - update_goal(data) - { - document.getElementById(`goal-${data.player}`).innerText = data.nb_goal; - } - - register_key() - { - this.keyPressHandler = this.keyPressHandler.bind(this); - this.keyReleaseHandler = this.keyReleaseHandler.bind(this); - document.addEventListener('keydown', this.keyPressHandler); - document.addEventListener('keyup', this.keyReleaseHandler); - } - - unregister_key() - { - document.removeEventListener('keydown', this.keyPressHandler); - document.removeEventListener('keyup', this.keyReleaseHandler); - } - - async join_game() - { - await this.game.join(); - - let canvas = document.createElement("canvas"); - - canvas.height = this.game.config.size_x; - canvas.width = this.game.config.size_y; - canvas.id = "canva"; - - document.getElementById("app").appendChild(canvas); - - let index = this.game.players.findIndex((player) => player.id === client.me.id); - if (index !== -1) - { - let my_player = this.game.players[index]; - this.my_player = new MyPlayer(client, - this.game, - my_player.rail, - my_player.nb_goal, - my_player.position, - ); - this.game.players[index] = this.my_player; - } - - this.register_key(); - - this.initGL(); - - this.render_game(); - } - - draw() - { - const canvas = document.getElementById('canva'); - if(canvas === null) - return 1; - - this.gl.clearColor(0.1, 0.1, 0.1, 1.0); - this.gl.clearDepth(1.0); - this.gl.enable(this.gl.DEPTH_TEST); - this.gl.depthFunc(this.gl.LEQUAL); - this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); - - const projectionMatrix = mat4.create(); - const viewMatrix = mat4.create(); - - mat4.perspective(projectionMatrix, (90 * Math.PI) / 180, this.gl.canvas.clientWidth / this.gl.canvas.clientHeight, 0.1, 10000000.0); - mat4.lookAt(viewMatrix, this.cam_pos, this.cam_target, this.cam_up); - - this.setPositionAttribute(); - this.setNormalAttribute(); - - this.gl.useProgram(shaderInfos.program); - - this.gl.uniformMatrix4fv(shaderInfos.uniformLocations.projectionMatrix, false, projectionMatrix); - this.gl.uniformMatrix4fv(shaderInfos.uniformLocations.viewMatrix, false, viewMatrix); - - this.game.render(this.gl); - } - - setNormalAttribute() - { - const numComponents = 3; - const type = this.gl.FLOAT; - const normalize = false; - const stride = 0; - const offset = 0; - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.normal); - this.gl.vertexAttribPointer( - shaderInfos.attribLocations.vertexNormal, - numComponents, - type, - normalize, - stride, - offset, - ); - this.gl.enableVertexAttribArray(shaderInfos.attribLocations.vertexNormal); - } - - setPositionAttribute() - { - const numComponents = 3; - const type = this.gl.FLOAT; - const normalize = false; - const stride = 0; - const offset = 0; - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.vertex); - this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.index); - this.gl.vertexAttribPointer( - shaderInfos.attribLocations.vertexPosition, - numComponents, - type, - normalize, - stride, - offset - ); - this.gl.enableVertexAttribArray(shaderInfos.attribLocations.vertexPosition); - } - - render_game() - { - let loop_id = setInterval(() => { - if (this.game === undefined) - clearInterval(loop_id); - if (this.my_player) - this.my_player.update_paddle(this.keys_pressed); - this.draw(); - this.game?.time.new_frame(); - }, 1000 / 60); - } - - async update_game_state() - { - document.getElementById("game-state").innerText = this.game.state; - - if (this.game.finished === false) - await this.join_game(); - } - - display_players_list() - { - let players_list = document.getElementById("players_list"); - - this.game.players.forEach(player => { - - let tr = document.createElement("tr"); - let name = document.createElement("td"); - let goal = document.createElement("td"); - - name.id = `username-${player.id}`; - goal.id = `goal-${player.id}`; - - name.innerText = player.username; - goal.innerText = player.nb_goal; - - tr.appendChild(name); - tr.appendChild(goal); - - players_list.appendChild(tr); - }); - } - - toggle2D() - { - window.location.replace(location.href.substring(0, location.href.lastIndexOf('/')) + "/0"); - } - - async postInit() - { - let error_code = await this.game.init(); - - if (error_code) - return error_code; - - await this.update_game_state(); - this.display_players_list(); - document.getElementById("game-mode").onclick = this.toggle2D; - } - - async leavePage() - { - if (this.game.finished === false) - { - this.game.leave(); - this.game = undefined; - } - this.unregister_key(); - } - - async getHtml() - { - return /* HTML */ ` - -

- - - - - - - - - - - -
${lang.get("gamePlayersListName")}${lang.get("gameGoalTaken")}
- `; - } -} -