diff options
-rw-r--r-- | controllers/games/gameId.js | 21 | ||||
-rw-r--r-- | controllers/games/root.js | 69 | ||||
-rw-r--r-- | database/000_init.sql | 1 | ||||
-rw-r--r-- | database/001_games.sql | 1 | ||||
-rw-r--r-- | database/games.js | 25 | ||||
-rw-r--r-- | database/users.js | 18 | ||||
-rw-r--r-- | routes/games.js | 5 | ||||
-rw-r--r-- | routes/root.js | 10 | ||||
-rw-r--r-- | utils/board.js | 63 | ||||
-rw-r--r-- | utils/checks.js | 15 | ||||
-rw-r--r-- | views/game.ejs | 6 | ||||
-rw-r--r-- | views/games.ejs | 26 |
12 files changed, 215 insertions, 45 deletions
diff --git a/controllers/games/gameId.js b/controllers/games/gameId.js index c87a857..d3363fd 100644 --- a/controllers/games/gameId.js +++ b/controllers/games/gameId.js @@ -1,17 +1,24 @@ import { getGame } from "../../database/games.js"; -function makePageData(user, cwdata) { +function makePageData(user, game) { return { title: "Jouer", user: user, - CWDATA: cwdata, + data: game, }; } export function gameId_get(req, res) { - const game = getGame(req.params.gameId); - // TODO redirect if null - let cwdata = game; // TODO reformat this object - console.log(cwdata); - return res.render("game", makePageData(req.session.user, cwdata)); + const game = JSON.parse(getGame(req.params.gameId).data); + if (game) { + if (game.player1.id === req.session.user.id) { + game.letters = game.player1.letters; + delete game.player2.letters; + } else { + game.letters = game.player2.letters; + delete game.player1.letters; + } + return res.render("game", makePageData(req.session.user, game)); + } + return res.redirect("/games"); } diff --git a/controllers/games/root.js b/controllers/games/root.js index ece7b74..7406815 100644 --- a/controllers/games/root.js +++ b/controllers/games/root.js @@ -1,13 +1,66 @@ -import { listGames } from "../../database/games.js"; +import { validationResult } from "express-validator"; + +import { getUserByUsername } from "../../database/users.js"; +import { listGames, newGame } from "../../database/games.js"; +import { emptyBoard, makeLettersBag, pickLetters, } from "../../utils/board.js"; + +function makePageData(user) { + return { + title: "Parties", + user: user, + games: listGames(user.id), + formdata: { + name: "", + username: "", + }, + errors: {}, + }; +} export function root_get(req, res) { - const data = { - title: "Liste des parties", - user: req.session.user, - games: listGames(req.session.user.id), + let page = makePageData(req.session.user); + for (let i=0; i<page.games.length; i++) { + page.games[i].data = JSON.parse(page.games[i].data); + } + return res.render("games", page); +} + +function makeNewGameData(name, player1, player2) { + let bag = makeLettersBag(); + return { + board: emptyBoard, + name: name, + player1: { + id: player1.id, + username: player1.username, + score: 0, + letters: pickLetters(bag, 7), + }, + player2: { + id: player2.id, + username: player2.username, + score: 0, + letters: pickLetters(bag, 7), + }, }; - for (let i=0; i<data.games.length; i++) { - data.games[i].data = JSON.parse(data.games[i].data); +} + +export function root_post(req, res) { + let page = makePageData(req.session.user); + page.formdata = req.body; + page.errors = validationResult(req).mapped(); + if (Object.keys(page.errors).length === 0) { + const player2 = getUserByUsername(page.formdata.username); + if (player2) { + const gameId = newGame(req.session.user.id, player2.id, makeNewGameData(page.formdata.name, req.session.user, player2)); + if (gameId) { + return res.redirect(302, `/games/${gameId}`); + } else { + page.errors.mismatch = "Erreur du serveur: la création de partie a échoué"; + } + } else { + page.errors.username = { msg: "L'identifiant n'existe pas." }; + } } - return res.render("games", data); + return res.render("games", page); } diff --git a/database/000_init.sql b/database/000_init.sql index b9f4f23..e4e180c 100644 --- a/database/000_init.sql +++ b/database/000_init.sql @@ -8,3 +8,4 @@ CREATE TABLE users ( email TEXT, created_at DATE DEFAULT (datetime('now')) ); +-- TODO deleted column diff --git a/database/001_games.sql b/database/001_games.sql index be45084..a500aa6 100644 --- a/database/001_games.sql +++ b/database/001_games.sql @@ -10,3 +10,4 @@ CREATE TABLE games ( ); CREATE INDEX idx_games_player1 ON games(player1); CREATE INDEX idx_games_player2 ON games(player2); +CREATE INDEX idx_games_last_move_at ON games(last_move_at); diff --git a/database/games.js b/database/games.js index b378eb2..dae3ce6 100644 --- a/database/games.js +++ b/database/games.js @@ -1,21 +1,14 @@ import db from "./db.js"; -const createGameStatement = db.prepare("INSERT INTO games (player1, player2, data) VALUES (?, ?, ?);"); const getGameStatement = db.prepare("SELECT * from games where id = ?;"); const listGamesStatement = db.prepare("SELECT * from games where player1 = ?1 OR player2 = ?1 ORDER BY last_move_at;"); - -export function createGame(player1, player2, data) { - try { - return createGameStatement.run(player1, player2, data).lastInsertRowId; - } catch { - return null; - } -} +const newGameStatement = db.prepare("INSERT INTO games (player1, player2, data) VALUES (?, ?, ?);"); export function getGame(id) { try { return getGameStatement.get(id); - } catch { + } catch (err) { + console.log(err); return null; } } @@ -23,7 +16,17 @@ export function getGame(id) { export function listGames(userId) { try { return listGamesStatement.all({ 1: userId }); - } catch { + } catch (err) { + console.log(err); return []; } } + +export function newGame(player1, player2, data) { + try { + return newGameStatement.run(player1, player2, JSON.stringify(data)).lastInsertRowid; + } catch (err) { + console.log(err); + return null; + } +} diff --git a/database/users.js b/database/users.js index 4b534b8..b24e3b4 100644 --- a/database/users.js +++ b/database/users.js @@ -5,13 +5,24 @@ import db from "./db.js"; const saltRounds = 10; const createUserStatement = db.prepare("INSERT INTO users (username, hash, email) VALUES (?, ?, ?);"); -const loginStatement = db.prepare("SELECT id, hash, email FROM users WHERE username = ?;"); +const getUserByUsernameStatement = db.prepare("SELECT id, username, email from users WHERE username = ?;"); +const loginStatement = db.prepare("SELECT id, username, hash, email FROM users WHERE username = ?;"); export async function createUser(username, password, email) { const hash = await bcrypt.hash(password, saltRounds); try { return createUserStatement.run(username, hash, email).lastInsertRowid; - } catch { + } catch (err) { + console.log(err); + return null; + } +} + +export function getUserByUsername(username) { + try { + return getUserByUsernameStatement.get(username); + } catch (err) { + console.log(err); return null; } } @@ -19,7 +30,8 @@ export async function createUser(username, password, email) { export async function login(username, password) { try { var user = loginStatement.get(username); - } catch { + } catch (err) { + console.log(err); return null; } const result = await bcrypt.compare(password, user.hash); diff --git a/routes/games.js b/routes/games.js index 67d105d..3a27cdb 100644 --- a/routes/games.js +++ b/routes/games.js @@ -1,15 +1,18 @@ import express from "express"; import { gameId_get } from "../controllers/games/gameId.js"; -import { root_get } from "../controllers/games/root.js"; +import { root_get, root_post } from "../controllers/games/root.js"; +import bodyParser from "../middlewares/formParser.js"; import requireAuth from "../middlewares/requireAuth.js"; import session from "../middlewares/sessions.js"; +import { checkName, checkUsername } from "../utils/checks.js"; const router = express.Router(); router.use(session); router.use(requireAuth); router.get("/", root_get); +router.post("/", [bodyParser, checkName, checkUsername], root_post); router.get("/:gameId(\\d+)", gameId_get); export default router; diff --git a/routes/root.js b/routes/root.js index c41f3e1..863b99d 100644 --- a/routes/root.js +++ b/routes/root.js @@ -1,23 +1,15 @@ import express from "express"; -import { check } from "express-validator"; import { login_get, login_post } from "../controllers/root/login.js"; import { logout_get } from "../controllers/root/logout.js"; import { root_get } from "../controllers/root/root.js"; import bodyParser from "../middlewares/formParser.js"; import session from "../middlewares/sessions.js"; +import { checkUsername, checkPassword } from "../utils/checks.js"; const router = express.Router(); router.use(session); -const checkUsername = check("username") - .trim() - .matches(/^[a-z][-a-z0-9_]+$/i) - .withMessage("Un identifiant d'au moins deux charactères est requis."); -const checkPassword = check("password") - .isStrongPassword() - .withMessage("Veuillez utiliser un mot de passe d'au moins 8 caractères contenant au moins une minuscule, majuscule, chiffre et charactère spécial."); - router.get("/", root_get); router.get("/login", login_get); router.post("/login", [bodyParser, checkUsername, checkPassword], login_post); diff --git a/utils/board.js b/utils/board.js new file mode 100644 index 0000000..ba43c45 --- /dev/null +++ b/utils/board.js @@ -0,0 +1,63 @@ +export const emptyBoard = [ + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ], + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ], + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ], + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ], + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ], + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ], + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ], + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ], + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ], + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ], + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ], + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ], + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ], + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ], + [ "", "", "", "", "","", "", "", "", "", "", "", "", "", "" ] +]; + +export const letters_total = 102; + +export function makeLettersBag () { + return { + letters : { + JOKER:{count:2, points:0 }, + E:{count:15, points:1}, A:{count:9, points:1}, I:{count:9, points:1}, N:{count:6, points:1}, O:{count:6, points:1}, R:{count:6, points:1}, S:{count:6, points:1}, T:{count:6, points:1}, U:{count:6, points:1}, L:{count:5, points:1}, + D:{count:3, points:2}, G:{count:2, points:2}, M:{count:3, points:2}, + B:{count:2, points:3}, C:{count:2, points:3}, P:{count:2, points:3}, + F:{count:2, points:4}, H:{count:2, points:4}, V:{count:2, points:4}, + J:{count:1, points:8}, Q:{count:1, points:8}, + K:{count:1, points:10}, W:{count:1, points:10}, X:{count:1, points:10}, Y:{count:1, points:10}, Z:{count:1, points:10}, + }, + remaining: letters_total, + }; +} + +const allLetters = Object.keys(makeLettersBag().letters); + +export function pickLetters(bag, count) { + if (count > bag.remaining) { + count = bag.remaining; + } + let ret = []; + for (let i=0; i<count; i++) { + let n = Math.floor(Math.random() * bag.remaining) -1; + console.log(n); + let j = 0; + for (;;) { + if (bag.letters[allLetters[j]].count === 0) { + j++; + } else if (bag.letters[allLetters[j]].count < n) { + n -= bag.letters[allLetters[j]].count; + j++; + } else { + n = 0; + break; + } + } + console.log("j:", j); + bag.letters[allLetters[j]].count--; + ret.push(allLetters[j]); + } + return ret; +} diff --git a/utils/checks.js b/utils/checks.js new file mode 100644 index 0000000..85c51df --- /dev/null +++ b/utils/checks.js @@ -0,0 +1,15 @@ +import { check } from "express-validator"; + +export const checkName = check("name") + .trim() + .matches(/^[a-z][-a-z0-9_]+$/i) + .withMessage("Un identifiant d'au moins deux charactères est requis."); + +export const checkPassword = check("password") + .isStrongPassword() + .withMessage("Veuillez utiliser un mot de passe d'au moins 8 caractères contenant au moins une minuscule, majuscule, chiffre et charactère spécial."); + +export const checkUsername = check("username") + .trim() + .matches(/^[a-z][-a-z0-9_]+$/i) + .withMessage("Un identifiant d'au moins deux charactères est requis."); diff --git a/views/game.ejs b/views/game.ejs index e1fab41..7fae405 100644 --- a/views/game.ejs +++ b/views/game.ejs @@ -27,10 +27,10 @@ </aside> </p> <p> -<span id="player_1_name">player one name</span>: <span class="player_name" id="player_1_points">0</span><br> -<span id="player_2_name">player two name</span>: <span class="player_name" id="player_2_points">0</span> +<span id="player_1_name"><%- data.player1.username %></span>: <span class="player_name" id="player_1_points"><%- data.player1.score %></span><br> +<span id="player_2_name"><%- data.player2.username %></span>: <span class="player_name" id="player_2_points"><%- data.player2.score %></span> </p> <p id="remaining_letters"></p> -<script>var CWDATA = <%- CWDATA.data %></script> +<script>var CWDATA = <%- JSON.stringify(data) %></script> <script type="module" src="/static/index.js"></script> <%- include("footer") %> diff --git a/views/games.ejs b/views/games.ejs index 3189be8..9cee7bb 100644 --- a/views/games.ejs +++ b/views/games.ejs @@ -7,11 +7,31 @@ <tr><th>Partie</th><th>Joueur 1</th><th>Joueur 2</th></tr> <% games.forEach((game) => { %> <tr> - <td><a href="/games/<%= game.id %>"><%= game.data.title %></a></td> - <td><a href="/users/<%= game.player1 %>"><%= game.data.player1.name %></a></td> - <td><a href="/users/<%= game.player2 %>"><%= game.data.player2.name %></a></td> + <td><a href="/games/<%= game.id %>"><%= game.data.name %></a></td> + <td><a href="/users/<%= game.player1.id %>"><%= game.data.player1.username %></a></td> + <td><a href="/users/<%= game.player2.id %>"><%= game.data.player2.username %></a></td> </tr> <% }) %> </table> <% } %> +<% if (Object.keys(errors).length === 0) { %> +<h2>Nouvelle partie</h2> +<% } else { %> +<h2>Oops, erreur lors de la création d'une nouvelle partie!</h2> +<% } %> +<form action="/games" method="post"> + <div class="form-field<%= errors.username ? ' form-field-invalid' : '' %>"> + <input type="text" placeholder="Identifiant de votre adversaire" name="username" value="<%= formdata.username %>" required autofocus> + <% if (errors.username) { %> + <label class="error" for="username"><%= errors.username.msg %></label> + <% } %> + </div> + <div class="form-field<%= errors.name ? ' form-field-invalid' : '' %>"> + <input type="text" placeholder="Nom ou titre de cette partie" name="name" value="<%= formdata.name %>" required> + <% if (errors.name) { %> + <label class="error" for="name"><%= errors.name.msg %></label> + <% } %> + </div> + <button type="submit">Démarrer</button> +</form> <%- include("footer") %> |