Compare commits

...

3 commits

Author SHA1 Message Date
lagaffe
5057de5572 add game logic for fridge 2026-04-18 20:29:19 +02:00
lagaffe
8fc1a7b887 add notes.md with game ideas 2026-04-18 14:55:55 +02:00
lagaffe
885ceca5f0 change file organization, add a generic class Game as parent class for all mini games 2026-04-18 14:38:54 +02:00
14 changed files with 295 additions and 124 deletions

29
notes.md Normal file
View file

@ -0,0 +1,29 @@
### mini game ideas
#### fridge
drag drop des clube mate dans le frigo
- évolution de difficulté:
- moins de temps
- moins de place dans le frigo
- emplacements plus scatter
- truc à enlever du frigo pour faire de la place
- drop une club mate la casse et met un pénalité de temps
#### opinator
réparer opinator en secouant le fil
- évolution de difficulté
- besoins de secouer plus
#### ranger le hs
drag drop entre trash et materiel
- évolution de difficulté
- moins de temps
- plus de truc à trier
#### smart monday
mash le clavier pour écrire des message aux participants
- évolution de la difficulté
- moins de temps
- ajout de plusieurs zone de text correspondant aux different participants
- ajout d'un boutton "rappel" pour chaque participant avec un cool down à clique jusque à ce que tous les participants soit ok

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

BIN
src/assets/img/uradibou.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View file

@ -1,5 +1,3 @@
let appt = { app: undefined };
function onDragMove(node, event, shift = { x: 0, y: 0 }) { function onDragMove(node, event, shift = { x: 0, y: 0 }) {
// shift is a tuple representing the shift between the coords of the node and the pointer // shift is a tuple representing the shift between the coords of the node and the pointer
const dragPoint = event.global; const dragPoint = event.global;
@ -13,12 +11,6 @@ function onDragMove(node, event, shift = { x: 0, y: 0 }) {
const right = const right =
parent.layout?.computedLayout.width - parent.layout?.computedLayout.width -
node.layout?.computedLayout.width; node.layout?.computedLayout.width;
console.log(
node.x,
right,
parent.width,
node.layout?.computedLayout.width,
);
if (newPos.x > right) { if (newPos.x > right) {
newPos.x = right; newPos.x = right;
} }
@ -38,11 +30,10 @@ function onDragMove(node, event, shift = { x: 0, y: 0 }) {
} }
function onDragEnd(node, onDrop) { function onDragEnd(node, onDrop) {
console.log("drop");
node.off("pointerup"); node.off("pointerup");
node.off("pointerupoutside"); node.off("pointerupoutside");
node.parent.off("mousemove"); node.parent.off("mousemove");
onDrop(); onDrop(node);
} }
function onDragStart(node, onDrop, event) { function onDragStart(node, onDrop, event) {
@ -60,11 +51,14 @@ function onDragStart(node, onDrop, event) {
} }
function makeDragable(node, onDrop = () => {}) { function makeDragable(node, onDrop = () => {}) {
console.log("make drag");
node.eventMode = "static"; node.eventMode = "static";
node.on("pointerdown", (event) => { node.on("pointerdown", (event) => {
onDragStart(node, onDrop, event); onDragStart(node, onDrop, event);
}); });
} }
export { makeDragable, appt }; function makeUnDragable(node, onDrop = () => {}) {
node.off("pointerdown");
}
export { makeDragable, makeUnDragable };

24
src/common/helpers.js Normal file
View file

@ -0,0 +1,24 @@
import { Rectangle } from "pixi.js";
export function getIntersection(a, b, out = new Rectangle()) {
const x1 = Math.max(a.x, b.x);
const y1 = Math.max(a.y, b.y);
const x2 = Math.min(a.x + a.width, b.x + b.width);
const y2 = Math.min(a.y + a.height, b.y + b.height);
if (x2 <= x1 || y2 <= y1) {
// no overlap → empty rect
out.x = 0;
out.y = 0;
out.width = 0;
out.height = 0;
return out;
}
out.x = x1;
out.y = y1;
out.width = x2 - x1;
out.height = y2 - y1;
return out;
}

3
src/common/index.js Normal file
View file

@ -0,0 +1,3 @@
export { backgroundLayout } from "./layouts";
export { makeDragable, makeUnDragable } from "./dragAndDrop";
export { getIntersection } from "./helpers";

6
src/common/layouts.js Normal file
View file

@ -0,0 +1,6 @@
export const backgroundLayout = {
width: "100%",
height: "100%",
objectFit: "cover",
position: "absolute",
};

129
src/games/fridge.js Normal file
View file

@ -0,0 +1,129 @@
import { Application, Assets, Container, Sprite, Rectangle } from "pixi.js";
import {
makeDragable,
backgroundLayout,
getIntersection,
makeUnDragable,
} from "../common";
import { Game } from "./game";
class DropZone {
constructor(sprite) {
this.sprite = sprite;
this.full = false;
}
drop(bottle) {
if (!this.full) {
this.full = true;
bottle.x = this.sprite.x;
bottle.y = this.sprite.y;
}
}
}
export class Fridge extends Game {
constructor() {
super();
this._bottleCount = 6;
this._bottlePlaced = 0;
const fridgeContainer = new Container();
fridgeContainer.layout = { width: "50%" };
const fridge = Sprite.from("frigo");
fridge.layout = backgroundLayout;
fridgeContainer.addChild(fridge);
const bottleContainer = new Container();
bottleContainer.layout = {
width: "50%",
flexDirection: "row",
flexWrap: "wrap",
};
const crate = Sprite.from("mate_caisse");
crate.layout = backgroundLayout;
bottleContainer.addChild(crate);
this.gameContainer.addChild(fridgeContainer);
this.gameContainer.addChild(bottleContainer);
const bottles = [];
this._dropZones = [];
const widthPercent = 0.12;
this.gameContainer.on("layout", (event) => {
const layoutBox = event.computedLayout;
for (const bottle of bottles) {
const width = layoutBox.width * widthPercent;
bottle.width = width;
bottle.height = width; // aspectRatio = 1
if (bottle.x + bottle.width > layoutBox.width) {
bottle.x = layoutBox.width - bottle.width;
}
if (bottle.y + bottle.height > layoutBox.height) {
bottle.y = layoutBox.height - bottle.height;
}
}
let i = 0;
for (const zone of this._dropZones) {
const width = layoutBox.width * (widthPercent + 0.01);
zone.sprite.width = width;
zone.sprite.height = width; // aspectRatio = 1
zone.sprite.x = i * width;
zone.sprite.y = 0;
i++;
}
});
for (let i = 0; i < this._bottleCount; i++) {
const sprite = Sprite.from("clubmate_grey");
this._dropZones.push(new DropZone(sprite));
this.gameContainer.addChild(sprite);
sprite.x = 0;
sprite.y = 0;
}
for (let i = 0; i < this._bottleCount; i++) {
const newBottle = Sprite.from("clubmate");
makeDragable(newBottle, (node) => {
this._onBottleDrop(node);
if (this._bottlePlaced >= this._bottleCount) {
console.log("fridge game won");
}
});
this.gameContainer.addChild(newBottle);
bottles.push(newBottle);
newBottle.x = 800;
newBottle.y = 0;
}
}
reset() {}
_onBottleDrop(bottle) {
console.log("dropped at", bottle.position);
const intersection = new Rectangle();
for (const dropZone of this._dropZones) {
getIntersection(
dropZone.sprite.getBounds(),
bottle.getBounds(),
intersection,
);
const intersectArea = intersection.width * intersection.height;
const bottleArea = bottle.width * bottle.height;
if (intersectArea > 0.5 * bottleArea) {
if (!dropZone.full) {
dropZone.drop(bottle);
this._bottlePlaced++;
makeUnDragable(bottle); // maybe not needed
return;
}
}
}
}
}

13
src/games/game.js Normal file
View file

@ -0,0 +1,13 @@
import { Container } from "pixi.js";
export class Game {
constructor() {
this._difficulty = 1;
this.won = false;
this.gameContainer = new Container(); // main container, to add the game to a node, add this container as a child
this.gameContainer.layout = true;
}
/* should reset the game to its initial position*/
reset() {}
}

2
src/games/index.js Normal file
View file

@ -0,0 +1,2 @@
export { Fridge } from "./fridge.js";
export { Opinator } from "./opinator.js";

65
src/games/opinator.js Normal file
View file

@ -0,0 +1,65 @@
import { Application, Assets, Container, Sprite } from "pixi.js";
import { backgroundLayout } from "../common";
import { Game } from "./game";
export class Opinator extends Game {
constructor() {
super();
this._inMove = false; // indicates if player has grabed the wire (is mouse down)
this._currentScore = 0;
this._pixelGoalScore = undefined;
this._goalScore = 10 * this._difficulty; // goal to match, measured in play area, 10 = moving the mouse on the equivalent of 10 play areas
this._lastMousePos = undefined; // use to compute the travelled distance
const backgroundSprite = Sprite.from("opinator");
this._pixelGoalScore = backgroundSprite.width * this._goalScore; // beware of resize mid game
backgroundSprite.eventMode = "static";
backgroundSprite.on("pointerdown", (event) => {
console.log("Opinator grabbed");
this._inMove = true;
this._lastMousePos = event.client.clone();
});
backgroundSprite.on("pointerupoutside", () => {
console.log("Opinator released");
this._inMove = false;
this._lastMousePos = undefined;
});
backgroundSprite.on("pointerup", () => {
console.log("Opinator released");
this._inMove = false;
this._lastMousePos = undefined;
});
backgroundSprite.on("mousemove", (event) => {
if (this._inMove && !this.won) {
const newMousePos = event.client;
const progress = Math.sqrt(
(newMousePos.x - this._lastMousePos.x) ** 2 +
(newMousePos.y - this._lastMousePos.y) ** 2,
);
if (progress > 0) {
this._lastMousePos = newMousePos.clone();
this._currentScore += progress;
console.log(
"mouse move, current score",
this._currentScore,
"goal",
this._pixelGoalScore,
);
if (this._pixelGoalScore < this._currentScore) {
console.log("game won");
this.won = true;
}
} //else ignore, not enough move to get some distance
}
});
backgroundSprite.layout = backgroundLayout;
this.gameContainer.addChild(backgroundSprite);
}
reset() {}
}

View file

@ -1,16 +1,10 @@
import "@pixi/layout"; import "@pixi/layout";
import { Application, Assets, Container, Sprite } from "pixi.js"; import { Application, Assets, Container, Sprite } from "pixi.js";
import { Fridge, Opinator } from "./games";
import { backgroundLayout } from "./common";
import { makeDragable, appt } from "./dragAndDrop.js";
const app = new Application(); const app = new Application();
let root = new Container(); let root = new Container();
appt.app = app;
const backgroundLayout = {
width: "100%",
height: "100%",
objectFit: "cover",
position: "absolute",
};
async function setup() { async function setup() {
await app.init({ background: "#1099bb", resizeTo: window }); await app.init({ background: "#1099bb", resizeTo: window });
@ -49,6 +43,10 @@ async function preload() {
alias: "clubmate", alias: "clubmate",
src: "assets/img/clubmate.png", src: "assets/img/clubmate.png",
}, },
{
alias: "clubmate_grey",
src: "assets/img/clubmate_grey.png",
},
{ {
alias: "mate_caisse", alias: "mate_caisse",
src: "assets/img/caisse.jpg", src: "assets/img/caisse.jpg",
@ -57,108 +55,6 @@ async function preload() {
await Assets.load(assets); await Assets.load(assets);
} }
class Opinator {
constructor(gameContainer) {
this.inMove = false; // indicates if player has grabed the wire (is mouse down)
this.currentScore = 0;
this.pixelGoalScore = undefined;
this.goalScore = 10; // goal to match, measured in play area, 10 = moving the mouse on the equivalent of 10 play areas
this.lastMousePos = undefined; // use to compute the travelled distance
this.won = false;
const backgroundSprite = Sprite.from("opinator");
this.pixelGoalScore = backgroundSprite.width * this.goalScore; // beware of resize mid game
backgroundSprite.eventMode = "static";
backgroundSprite.on("pointerdown", (event) => {
console.log("Opinator grabbed");
this.inMove = true;
this.lastMousePos = event.client.clone();
});
backgroundSprite.on("pointerupoutside", () => {
console.log("Opinator released");
this.inMove = false;
this.lastMousePos = undefined;
});
backgroundSprite.on("pointerup", () => {
console.log("Opinator released");
this.inMove = false;
this.lastMousePos = undefined;
});
backgroundSprite.on("mousemove", (event) => {
if (this.inMove && !this.won) {
const newMousePos = event.client;
const progress = Math.sqrt(
(newMousePos.x - this.lastMousePos.x) ** 2 +
(newMousePos.y - this.lastMousePos.y) ** 2,
);
if (progress > 0) {
this.lastMousePos = newMousePos.clone();
this.currentScore += progress;
console.log(
"mouse move, current score",
this.currentScore,
"goal",
this.pixelGoalScore,
);
if (this.pixelGoalScore < this.currentScore) {
console.log("game won");
this.won = true;
}
} //else ignore, not enough move to get some distance
}
});
backgroundSprite.layout = {
width: "100%",
height: "100%",
objectFit: "contain",
};
gameContainer.addChild(backgroundSprite);
}
}
class Fridge {
constructor(gameContainer) {
const fridgeContainer = new Container();
fridgeContainer.layout = { width: "50%" };
const fridge = Sprite.from("frigo");
fridge.layout = backgroundLayout;
fridgeContainer.addChild(fridge);
const bottleContainer = new Container();
bottleContainer.layout = {
width: "50%",
flexDirection: "row",
flexWrap: "wrap",
};
const crate = Sprite.from("mate_caisse");
crate.layout = backgroundLayout;
bottleContainer.addChild(crate);
gameContainer.addChild(fridgeContainer);
gameContainer.addChild(bottleContainer);
const bottleLayout = {
width: "12%",
height: "16%",
position: "absolute",
};
for (let i = 0; i < 12; i++) {
const newBottle = Sprite.from("clubmate");
newBottle.layout = bottleLayout;
makeDragable(newBottle);
gameContainer.addChild(newBottle);
newBottle.x = 0;
newBottle.y = 0;
}
}
}
function switchToGame(gameContainer, newGame) { function switchToGame(gameContainer, newGame) {
//empty previous container //empty previous container
//create new game and add //create new game and add
@ -182,7 +78,9 @@ function switchToGame(gameContainer, newGame) {
maxHeight: "100%", maxHeight: "100%",
margin: 0, margin: 0,
}; };
//const opinator = new Opinator(gameContainter); const opinator = new Opinator();
const fridge = new Fridge(gameContainter); const fridge = new Fridge();
gameContainter.addChild(fridge.gameContainer);
//gameContainter.addChild(opinator.gameContainer);
root.addChild(gameContainter); root.addChild(gameContainter);
})(); })();

7
src/package-lock.json generated
View file

@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@pixi/layout": "^3.2.0", "@pixi/layout": "^3.2.0",
"@pixi/math": "^7.4.3",
"pixi.js": "^8.0.0" "pixi.js": "^8.0.0"
}, },
"devDependencies": { "devDependencies": {
@ -101,6 +102,12 @@
"yoga-layout": "^3" "yoga-layout": "^3"
} }
}, },
"node_modules/@pixi/math": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.4.3.tgz",
"integrity": "sha512-/uJOVhR2DOZ+zgdI6Bs/CwcXT4bNRKsS+TqX3ekRIxPCwaLra+Qdm7aDxT5cTToDzdxbKL5+rwiLu3Y1egILDw==",
"license": "MIT"
},
"node_modules/@pixi/react": { "node_modules/@pixi/react": {
"version": "8.0.5", "version": "8.0.5",
"resolved": "https://registry.npmjs.org/@pixi/react/-/react-8.0.5.tgz", "resolved": "https://registry.npmjs.org/@pixi/react/-/react-8.0.5.tgz",

View file

@ -3,6 +3,7 @@
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": true,
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite --host",
"build": "vite build", "build": "vite build",