41 Commits

Author SHA1 Message Date
kalips003 b2bca72cd4 ^^._, work in progress, small changes 2026-03-26 13:55:49 +01:00
Thomas Fauve-Piot cc4b7c9efc Last add : the Cat 2026-03-24 21:33:02 +01:00
Thomas Fauve-Piot aae651aa8b Front done 2026-03-24 21:02:48 +01:00
Kali Gallon ec560f3447 a faire 2026-03-24 19:38:31 +01:00
Kali Gallon 6df0f24ef6 ^^._, work in progress, small changes 2026-03-24 19:35:13 +01:00
Kali Gallon d8b97ebe17 Merge branch 'LosGringos' into kali 2026-03-24 19:00:32 +01:00
Kali Gallon 98d30c85b2 Merge branch 'LosGringos' of github.com:OlaketalAmigo/Transcendence into LosGringos 2026-03-24 19:00:04 +01:00
Kali Gallon ec36271886 ^^._, work in progress, small changes 2026-03-24 18:58:14 +01:00
Kali Gallon 029c8a6650 ^^._, work in progress, small changes 2026-03-24 18:56:09 +01:00
Thomas Fauve-Piot 0a6e9a25ed Added doodles.png 2026-03-24 18:52:30 +01:00
Kali Gallon e764d565c1 fix merge confict? 2026-03-24 17:02:11 +01:00
Kali Gallon b0fc705d26 ^^._, work in progress, small changes 2026-03-24 15:36:43 +01:00
Thomas Fauve-Piot cb1fc01ad6 Animated but not smooth enough 2026-03-23 23:01:25 +01:00
Kali Gallon 5299f3d1af ^^._, work in progress, small changes 2026-03-20 19:44:08 +01:00
Thomas Fauve-Piot 27704b97f8 Home page's front good 2026-03-20 17:57:00 +01:00
Kali Gallon 2eaae81f28 first impression, i did nothing 2026-03-20 15:16:10 +01:00
Kali Gallon 6f5d27f6a2 ^^._, work in progress, small changes 2026-03-20 15:13:10 +01:00
Thomas Fauve-Piot 938d4cf3b5 45env45 2026-03-20 15:01:39 +01:00
Thomas Fauve-Piot 167896aedd Front started 2026-03-19 23:08:45 +01:00
H3XploR 30e4f04c52 changement de couleur du bouton home 2026-03-17 21:44:47 +01:00
H3XploR a202889f79 Merge pull request #19 from OlaketalAmigo/better_tetris
Merge better_tetris into TETRIS
2026-03-17 21:29:03 +01:00
H3XploR 37ab3e83f6 responsive tetris 2026-03-17 21:10:28 +01:00
H3XploR e4eb9b0c95 better theme 2026-03-16 16:11:48 +01:00
H3XploR ad4becc38f ajout d'historique 2026-03-09 00:15:01 +01:00
H3XploR 0c8b6a663a Merge pull request #18 from OlaketalAmigo/add_score_tetris
Add score tetris
2026-03-08 23:39:35 +01:00
H3XploR 29c0863470 no bckp 2026-03-08 23:37:22 +01:00
H3XploR 8feb894a39 END? 2026-03-08 23:32:58 +01:00
H3XploR c8203cfc49 Merge pull request #17 from OlaketalAmigo/add_home_button
ajout du bouton home sans style
2026-03-07 15:23:24 +01:00
Master c2585774cc ajout du bouton home sans style 2026-03-07 14:57:10 +01:00
Master 5ca2a485f8 retrait du zip 2026-03-07 14:24:00 +01:00
gprunet b3141387b1 merge done 2026-03-05 19:03:34 +01:00
Master 3769ee27a8 Merge manuel bientot finis 2026-03-03 21:01:49 +01:00
Master 7fda24a6cc retrait du bouton restart 2026-03-01 16:25:19 +01:00
Master eeb9e7bf4d tetris fonctionnel sans bug 2026-03-01 15:07:53 +01:00
Master a4210af235 duel tetris fonctionnel mais galere a tester en solo 2026-02-19 16:45:54 +01:00
Master 0f69f4fb6f tetris duel bugged 2026-02-19 16:28:22 +01:00
Master 1879203ac8 meileure README 2026-02-19 15:50:33 +01:00
Master fd955be677 WEW:cleaning 2026-02-19 14:50:15 +01:00
Master f9d3a537c0 bug fixed 2026-02-19 14:46:25 +01:00
Master 4e7a9fdee7 modulaire tetris 2026-02-19 14:18:27 +01:00
Master 276e6867a9 ajout du jeu tetris 2026-02-18 17:15:35 +01:00
172 changed files with 5755 additions and 326 deletions
+4 -3
View File
@@ -3,7 +3,8 @@ JWT_SECRET=superlongsecretkeyatleast32characterspleasenevercommitthis
POSTGRES_DB=database
POSTGRES_HOST=database
POSTGRES_USER=user
GITHUB_CLIENT_ID=Ov23liYIX8bJcdamjQJm
GITHUB_CLIENT_SECRET=9db75e695a8c028a80bb2e9b5604b2e44f76fb26
GITHUB_CLIENT_ID=Ov23li6ovg3fzec5IO5D
GITHUB_CLIENT_SECRET=0345e959e8f0e9f784061c5c90ee227ddb2ef9ab
GITHUB_CALLBACK_URL=http://localhost:8080/api/auth/github/callback
pogpog
+37
View File
@@ -0,0 +1,37 @@
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Environment / secrets
.env
.env.*
.env.local
.env.production
# Node
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Logs
*.log
logs/
# Build output
dist/
build/
# IDE
.vscode/
.idea/
*.swp
*.swo
# Docker volumes / data
postgres-data/
data/
# OS
Thumbs.db
+33 -1
View File
@@ -1,3 +1,35 @@
# macOS
.DS_Store
srcs/.DS_Store
*.DS_Store
srcs/backend/avatar/*
# Environment / secrets
.env
.env.*
# Node
node_modules/
npm-debug.log*
yarn-debug.log*
# Logs
*.log
logs/
# Build output
dist/
build/
# Uploads utilisateurs (garder uniquement default.png)
srcs/backend/avatar/*
!srcs/backend/avatar/default.png
# IDE
.vscode/
.idea/
*.swp
*.swo
# Docker volumes / data
postgres-data/
data/
+454 -3
View File
@@ -1,14 +1,465 @@
all : up
all :
@$(call random_shmol_cat, "hELLO", "nice human corrector", $(CLS), )
@docker compose -f ./docker-compose.yml up -d
up :
no_cache :
@docker compose -f ./docker-compose.yml build --no-cache
@docker compose -f ./docker-compose.yml up -d
clean :
@$(call print_cat, $(CLEAR), $(C_225), $(C_320), $(C_450), $(call pad_word, 10, "Objects"), $(call pad_word, 12, "Exterminated"));
@docker compose -f ./docker-compose.yml down -t 1
fclean :
@$(call print_cat, $(CLEAR), $(C_120), $(C_300), $(C_210), $(call pad_word, 10, "Allclean"), $(call pad_word, 12, "Miaster"));
@docker compose -f ./docker-compose.yml down -v -t 1
@docker system prune -af --volumes
re : fclean up
re : fclean no_cache
@$(call print_cat, $(CLEAR), $(C_120), $(C_300), $(C_210), $(call pad_word, 10, "Re-Doing"), $(call pad_word, 12, "Miaster"));
.PHONY : all no_cache clean fclean re
# ╭────────────────────────────────────────────────────────────────────────────╮
# │─██████████████─██████████████─██████████████─██████─────────██████████████─│
# │─██░░░░░░░░░░██─██░░░░░░░░░░██─██░░░░░░░░░░██─██░░██─────────██░░░░░░░░░░██─│
# │─██████░░██████─██░░██████░░██─██░░██████░░██─██░░██─────────██░░██████████─│
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────██░░██─────────│
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────██░░██████████─│
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────██░░░░░░░░░░██─│
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────██████████░░██─│
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────────────██░░██─│
# │─────██░░██─────██░░██████░░██─██░░██████░░██─██░░██████████─██████████░░██─│
# │─────██░░██─────██░░░░░░░░░░██─██░░░░░░░░░░██─██░░░░░░░░░░██─██░░░░░░░░░░██─│
# │─────██████─────██████████████─██████████████─██████████████─██████████████─│
# ╰────────────────────────────────────────────────────────────────────────────╯
# --------------------------------------------------------------------------------- >
VALGRIND = valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes -s --track-fds=yes --trace-children=yes $(V_FLAG)
# ↑さ↓ぎょう を ↓ほ↑ぞん
# Default git push
git: fclean
@$(call random_shmol_cat_blink, 作業を保存してるかな.., いいね、いいねえー , $(CLS), );
@current_date=$$(date); \
git add .; \
git commit -m "^^._, work in progress, small changes"; \
git push
# Git Push that asks for commit msg
git2: fclean
@$(call random_shmol_cat_blink, 作業を保存してるかな.., いいね、いいねえー , $(CLS), );
@read -p "Enter commit message: " msg; \
[ -z "$$msg" ] && msg=$$(date); \
git add .; \
git commit -m "$$msg"; \
git push
# Git Push use the content of .gitmsg to push
# if .gitmsg empty, return error
# clear .gitmsg on succesfull push.
GIT_MSG_FILE = ../.gitmsg
git3: fclean
@$(call random_shmol_cat_blink, 作業を保存してるかな.., いいね、いいねえー , $(CLS), );
@{ \
msg="$$(cat $(GIT_MSG_FILE) 2>/dev/null)"; \
[ -z "$$msg" ] && { $(call random_shmol_cat_blink, error, file is empty, , ); exit 1; }; \
git add . && \
git commit -m "$$msg" && \
git push && \
: > $(GIT_MSG_FILE) && \
$(call random_shmol_cat_blink, success!, $(GIT_MSG_FILE) cleared., , ); \
}
.SILENT: $(NAME)
# ╭────────────────────────────────────────────────────────────────────────────────────╮
# │─██████████████─████████████████───██████████─██████──────────██████─██████████████─│
# │─██░░░░░░░░░░██─██░░░░░░░░░░░░██───██░░░░░░██─██░░██████████──██░░██─██░░░░░░░░░░██─│
# │─██░░██████░░██─██░░████████░░██───████░░████─██░░░░░░░░░░██──██░░██─██████░░██████─│
# │─██░░██──██░░██─██░░██────██░░██─────██░░██───██░░██████░░██──██░░██─────██░░██─────│
# │─██░░██████░░██─██░░████████░░██─────██░░██───██░░██──██░░██──██░░██─────██░░██─────│
# │─██░░░░░░░░░░██─██░░░░░░░░░░░░██─────██░░██───██░░██──██░░██──██░░██─────██░░██─────│
# │─██░░██████████─██░░██████░░████─────██░░██───██░░██──██░░██──██░░██─────██░░██─────│
# │─██░░██─────────██░░██──██░░██───────██░░██───██░░██──██░░██████░░██─────██░░██─────│
# │─██░░██─────────██░░██──██░░██████─████░░████─██░░██──██░░░░░░░░░░██─────██░░██─────│
# │─██░░██─────────██░░██──██░░░░░░██─██░░░░░░██─██░░██──██████████░░██─────██░░██─────│
# │─██████─────────██████──██████████─██████████─██████──────────██████─────██████─────│
# ╰────────────────────────────────────────────────────────────────────────────────────╯
# C_213
PURPLE = \033[38;5;97m
# C_430
GOLD = \033[38;5;178m
# C_040
GREEN1 = \033[38;5;40m
# C_045
BLUE1 = \033[38;5;45m
# $(C_105), $(C_510), $(C_025)
# $(RED), $(GOLD), $(BLUE1)
test_color666:
@$(call random_cat, $(call pad_word, 12, TheCake), $(call pad_word, 14, IsALie...), $(CLS), $(RESET));
@$(call random_cat, $(call pad_word, 13, TheCake), $(call pad_word, 15, IsALie...), , $(RESET));
# $(call pad_word, 12, TheCake)
pad_word = $(BLINK)$(shell printf "%$(1)s" "$(2)")$(RESET)
# improve with: STRING1=$$(printf "\033[38;5;%dm" $$(shuf -i 0-255 -n 1));
# --------------------------------------------------------------------------------- >
# @$(call print_cat, $(CLEAR), $(body), $(eye), $(txt), $(call pad_word, 12, "TheCake"), $(call pad_word, 12, "IsALie..."));
# print_cat (resest?)(C_c)_sCtt$padded_txt_top))($(padded_txt_bot))
define print_cat
echo -e "$(1)$(2)\
\t\t\t\t\t ⠀⢠⠒⡄⠀\n\
\t\t\t\t\t ⠀⢀⠇⠀⠘⡄⠀⠀⠀⠀⠀⠀⣀⠀⠀\n\
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀⠀⠁⠉⠉⠉⠒⠊⠉⠀⡇⠀\n\
\t\t\t\t\t ⠀⡜$(3)$(BLINK)⣀⡀$(RESET)$(2)⠀⢰⠁⠀\n\
\t\t\t\t\t ⠀⠲⢴⠁$(3)$(BLINK)⠛⠁$(RESET)$(2)$(3)$(BLINK)⢀⣄$(RESET)$(2)⠀⢸⠀\n\
\t\t\t\t\t ⠀⠉⠑⠺⡀⠀⠀⢶⠤$(3)$(BLINK)⠈⠋$(RESET)$(2)⠀⡘⠀\n\
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠑⢄⡀⠀⠀⠀⠠⣉⠑⠂⠀⢠⠃⠀⠀\n\
\t\t\t\t\t ⠀⢠⠊⠀⠀⠀⠀⠀⠀⠁⠀⠀⠈⢆⠀⠀\n\
\t\t\t\t\t ⠀⢰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀\n\
\t\t\t\t\t ⠀⢀⠤⠒⠒⠃⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠀\n\
\t\t\t\t\t ⠀⠔⠑⠄⠀⠀⡎⠀⠀⠀⠀⠀⠀⡇\n\
\t\t\t\t\t ⠸⡀⠀⢣⠀⠀⡇$(4)$(5)$(2)⠀⡇\n\
\t\t\t\t\t ⠀⠱⡀⠀⠳⡀⠀⠀⢃$(4)$(6)$(2)⠀⡸⠀\n\
\t\t\t\t\t ⠀⠑⢄⠀⠈⠒⢄⡀⠀⠀⠸⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠁⠀\n\
\t\t\t\t\t ⠀⠑⠦⣀⠀⠈⠉⠐⠒⠒⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⢢⠀\n\
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠉⠐⠒⠠⠤⠤⠤⠤⠔⠂⠠⠤⠤⠤⠄⠐⠒⠂⠉⠉⠉⠉⠉⠁\n$(RESET)"
endef
# --------------------------------------------------------------------------------- >
# @$(call random_cat, $(call pad_word, 12, txt1), $(call pad_word, 12, txt2), $(CLS), $(RESET));
# print_cat (resest?)(C_c)_sCtt$padded_txt_top))($(padded_txt_bot))
define random_cat
COLOR=$$(printf "\033[38;5;%dm" $$(shuf -i 0-255 -n 1)); \
COLOR2=$$(printf "\033[38;5;%dm" $$(shuf -i 0-255 -n 1)); \
COLOR3=$$(printf "\033[38;5;%dm" $$(shuf -i 0-255 -n 1)); \
echo -e "$(3)$${COLOR}\
\t\t\t\t\t ⠀⢠⠒⡄⠀\n\
\t\t\t\t\t ⠀⢀⠇⠀⠘⡄⠀⠀⠀⠀⠀⠀⣀⠀⠀\n\
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀⠀⠁⠉⠉⠉⠒⠊⠉⠀⡇⠀\n\
\t\t\t\t\t ⠀⡜⠀$${COLOR2}$(BLINK)⣀⡀$(RESET)$${COLOR}⠀⢰⠁⠀\n\
\t\t\t\t\t ⠀⠲⢴⠁⠀$${COLOR2}$(BLINK)⠛⠁$(RESET)$${COLOR}$${COLOR2}$(BLINK)⠀⢀⣄$(RESET)$${COLOR}⠀⢸⠀\n\
\t\t\t\t\t ⠀⠉⠑⠺⡀⠀⠀⢶⠤$${COLOR2}$(BLINK)⠀⠈⠋$(RESET)$${COLOR}⠀⡘⠀\n\
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠑⢄⡀⠀⠀⠀⠠⣉⠑⠂⠀⢠⠃⠀⠀\n\
\t\t\t\t\t ⠀⢠⠊⠀⠀⠀⠀⠀⠀⠁⠀⠀⠈⢆⠀⠀\n\
\t\t\t\t\t ⠀⢰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀\n\
\t\t\t\t\t ⠀⢀⠤⠒⠒⠃⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠀\n\
\t\t\t\t\t ⠀⠔⠑⠄⠀⠀⡎⠀⠀⠀⠀⠀⠀⡇\n\
\t\t\t\t\t ⠸⡀⠀⢣⠀⠀⡇$${COLOR3}$(1)$${COLOR}⠀⡇\n\
\t\t\t\t\t ⠀⠱⡀⠀⠳⡀⠀⠀⢃$${COLOR3}$(2)$${COLOR}⠀⡸⠀\n\
\t\t\t\t\t ⠀⠑⢄⠀⠈⠒⢄⡀⠀⠀⠸⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠁⠀\n\
\t\t\t\t\t ⠀⠑⠦⣀⠀⠈⠉⠐⠒⠒⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⢢⠀\n\
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠉⠐⠒⠠⠤⠤⠤⠤⠔⠂⠠⠤⠤⠤⠄⠐⠒⠂⠉⠉⠉⠉⠉⠁\n$(4)"
endef
# --------------------------------------------------------------------------------- >
# @$(call shmol_cat_color, $(C_c), $(C_t), txt1, txt2, $(CLS), $(RESET));
define shmol_cat_color
echo -e "$(5)$(2)\
\tにゃ~$(1)\t⠀|、\n\
\t\t(˚ˎ。7$(2)~ $(3) ~$(1)\n\
\t\t|、˜\\\\\t\t$(2)$(4)$(1)\n\
\t\t⠀じしˍ)\n$(6)"
endef
# --------------------------------------------------------------------------------- >
# @$(call random_shmol_cat, text 1, text 2, $(CLS), $(RESET));
# $(1)= $(CLEAR); $(2)= text1; $(3)= text2; $(4)= $(RESET)
define random_shmol_cat
COLOR=$$(printf "\033[38;5;%dm" $$(shuf -i 1-255 -n 1)); \
COLOR2=$$(printf "\033[38;5;%dm" $$(shuf -i 0-255 -n 1)); \
echo -e "$(3)$${COLOR2}\
\tにゃ~$${COLOR}\t⠀|、\n\
\t\t(˚ˎ。7$${COLOR2}~ $(1) ~$${COLOR}\n\
\t\t|、˜\\\\\t\t$${COLOR2}~ $(2)$${COLOR}\n\
\t\t⠀じしˍ)\n$(4)"
endef
# // <!> - - - - - - - - - - - </!>
# --------------------------------------------------------------------------------- >
rscs:
@$(call random_shmol_cat_surligne, text 1, text 2, $(CLS), $(RESET));
define random_shmol_cat_surligne
COLOR=$$(printf "\033[0m\033[38;5;%dm" $$(shuf -i 0-255 -n 1)); \
COLOR2=$$(printf "\033[48;5;%dm" $$(shuf -i 0-255 -n 1)); \
echo -e "$(3)$${COLOR2}\
\tにゃ~$${COLOR}\t⠀|、\n\
\t\t(˚ˎ。7$${COLOR2}~ $(1) ~$${COLOR}\n\
\t\t|、˜\\\\\t\t$${COLOR2}~ $(2)$${COLOR}\n\
\t\t⠀じしˍ)\n$(4)"
endef
rscb:
@$(call random_shmol_cat_blink, text 1, text 2, $(CLS), $(RESET));
define random_shmol_cat_blink
COLOR=$$(printf "\033[0m\033[38;5;%dm" $$(shuf -i 0-255 -n 1)); \
COLOR2=$$(printf "\e[5m\033[38;5;%dm" $$(shuf -i 0-255 -n 1)); \
echo -e "$(3)\n$${COLOR2}\
\tにゃ~$${COLOR}\t⠀|、\n\
\t\t(˚ˎ。7$${COLOR2}~ $(1) ~$${COLOR}\n\
\t\t|、˜\\\\\t\t$${COLOR2}~ $(2)$${COLOR}\n\
\t\t⠀じしˍ)\n$(4)"
endef
# // <!> - - - - - - - - - - - </!>
# --------------------------------------------------------------------------------- >
# @$(call shmol_cat_error, $(RED), $(RED_L));
# $(1) = $(C_c)$2) = $(C_ttN CLS
define shmol_cat_error
echo -e "$(2)\
\tにゃ~$(1)\t⠀|、\n\
\t\t(˚ˎ。7$(2)~ somshin wen wong ~$(1)\n\
\t\t|、˜\\\\\n\
\t\t⠀じしˍ)\n$(RESET)"
endef
# Define all 256 colors
CLEAR = \033[2J\033[H
CLS = \033[2J\033[H
RESET = \033[0m
BLINK = \033[5m
# U+2800 to U+28FF Braile
# <Esc>[38;5;ColorNumberm
BLACK = \033[38;5;0m
RED = \033[38;5;1m
GREEN = \033[38;5;2m
YELLOW = \033[38;5;3m
BLUE = \033[38;5;4m
MAGENTA = \033[38;5;5m
CYAN = \033[38;5;6m
GRAY = \033[38;5;7m
BLACK_L = \033[38;5;8m
RED_L = \033[38;5;9m
GREEN_L = \033[38;5;10m
YELLOW_L = \033[38;5;11m
BLUE_L = \033[38;5;12m
MAGENTA_L = \033[38;5;13m
CYAN_L = \033[38;5;14m
WHITE = \033[38;5;15m
C_000 = \033[38;5;16m
C_001 = \033[38;5;17m
C_002 = \033[38;5;18m
C_003 = \033[38;5;19m
C_004 = \033[38;5;20m
C_005 = \033[38;5;21m
C_010 = \033[38;5;22m
C_011 = \033[38;5;23m
C_012 = \033[38;5;24m
C_013 = \033[38;5;25m
C_014 = \033[38;5;26m
C_015 = \033[38;5;27m
C_020 = \033[38;5;28m
C_021 = \033[38;5;29m
C_022 = \033[38;5;30m
C_023 = \033[38;5;31m
C_024 = \033[38;5;32m
C_025 = \033[38;5;33m
C_030 = \033[38;5;34m
C_031 = \033[38;5;35m
C_032 = \033[38;5;36m
C_033 = \033[38;5;37m
C_034 = \033[38;5;38m
C_035 = \033[38;5;39m
C_040 = \033[38;5;40m
C_041 = \033[38;5;41m
C_042 = \033[38;5;42m
C_043 = \033[38;5;43m
C_044 = \033[38;5;44m
C_045 = \033[38;5;45m
C_050 = \033[38;5;46m
C_051 = \033[38;5;47m
C_052 = \033[38;5;48m
C_053 = \033[38;5;49m
C_054 = \033[38;5;50m
C_055 = \033[38;5;51m
C_100 = \033[38;5;52m
C_101 = \033[38;5;53m
C_102 = \033[38;5;54m
C_103 = \033[38;5;55m
C_104 = \033[38;5;56m
C_105 = \033[38;5;57m
C_110 = \033[38;5;58m
C_111 = \033[38;5;59m
C_112 = \033[38;5;60m
C_113 = \033[38;5;61m
C_114 = \033[38;5;62m
C_115 = \033[38;5;63m
C_120 = \033[38;5;64m
C_121 = \033[38;5;65m
C_122 = \033[38;5;66m
C_123 = \033[38;5;67m
C_124 = \033[38;5;68m
C_125 = \033[38;5;69m
C_130 = \033[38;5;70m
C_131 = \033[38;5;71m
C_132 = \033[38;5;72m
C_133 = \033[38;5;73m
C_134 = \033[38;5;74m
C_135 = \033[38;5;75m
C_140 = \033[38;5;76m
C_141 = \033[38;5;77m
C_142 = \033[38;5;78m
C_143 = \033[38;5;79m
C_144 = \033[38;5;80m
C_145 = \033[38;5;81m
C_150 = \033[38;5;82m
C_151 = \033[38;5;83m
C_152 = \033[38;5;84m
C_153 = \033[38;5;85m
C_154 = \033[38;5;86m
C_155 = \033[38;5;87m
C_200 = \033[38;5;88m
C_201 = \033[38;5;89m
C_202 = \033[38;5;90m
C_203 = \033[38;5;91m
C_204 = \033[38;5;92m
C_205 = \033[38;5;93m
C_210 = \033[38;5;94m
C_211 = \033[38;5;95m
C_212 = \033[38;5;96m
C_213 = \033[38;5;97m
C_214 = \033[38;5;98m
C_215 = \033[38;5;99m
C_220 = \033[38;5;100m
C_221 = \033[38;5;101m
C_222 = \033[38;5;102m
C_223 = \033[38;5;103m
C_224 = \033[38;5;104m
C_225 = \033[38;5;105m
C_230 = \033[38;5;106m
C_231 = \033[38;5;107m
C_232 = \033[38;5;108m
C_233 = \033[38;5;109m
C_234 = \033[38;5;110m
C_235 = \033[38;5;111m
C_240 = \033[38;5;112m
C_241 = \033[38;5;113m
C_242 = \033[38;5;114m
C_243 = \033[38;5;115m
C_244 = \033[38;5;116m
C_245 = \033[38;5;117m
C_250 = \033[38;5;118m
C_251 = \033[38;5;119m
C_252 = \033[38;5;120m
C_253 = \033[38;5;121m
C_254 = \033[38;5;122m
C_255 = \033[38;5;123m
C_300 = \033[38;5;124m
C_301 = \033[38;5;125m
C_302 = \033[38;5;126m
C_303 = \033[38;5;127m
C_304 = \033[38;5;128m
C_305 = \033[38;5;129m
C_310 = \033[38;5;130m
C_311 = \033[38;5;131m
C_312 = \033[38;5;132m
C_313 = \033[38;5;133m
C_314 = \033[38;5;134m
C_315 = \033[38;5;135m
C_320 = \033[38;5;136m
C_321 = \033[38;5;137m
C_322 = \033[38;5;138m
C_323 = \033[38;5;139m
C_324 = \033[38;5;140m
C_325 = \033[38;5;141m
C_330 = \033[38;5;142m
C_331 = \033[38;5;143m
C_332 = \033[38;5;144m
C_333 = \033[38;5;145m
C_334 = \033[38;5;146m
C_335 = \033[38;5;147m
C_340 = \033[38;5;148m
C_341 = \033[38;5;149m
C_342 = \033[38;5;150m
C_343 = \033[38;5;151m
C_344 = \033[38;5;152m
C_345 = \033[38;5;153m
C_350 = \033[38;5;154m
C_351 = \033[38;5;155m
C_352 = \033[38;5;156m
C_353 = \033[38;5;157m
C_354 = \033[38;5;158m
C_355 = \033[38;5;159m
C_400 = \033[38;5;160m
C_401 = \033[38;5;161m
C_402 = \033[38;5;162m
C_403 = \033[38;5;163m
C_404 = \033[38;5;164m
C_405 = \033[38;5;165m
C_410 = \033[38;5;166m
C_411 = \033[38;5;167m
C_412 = \033[38;5;168m
C_413 = \033[38;5;169m
C_414 = \033[38;5;170m
C_415 = \033[38;5;171m
C_420 = \033[38;5;172m
C_421 = \033[38;5;173m
C_422 = \033[38;5;174m
C_423 = \033[38;5;175m
C_424 = \033[38;5;176m
C_425 = \033[38;5;177m
C_430 = \033[38;5;178m
C_431 = \033[38;5;179m
C_432 = \033[38;5;180m
C_433 = \033[38;5;181m
C_434 = \033[38;5;182m
C_435 = \033[38;5;183m
C_440 = \033[38;5;184m
C_441 = \033[38;5;185m
C_442 = \033[38;5;186m
C_443 = \033[38;5;187m
C_444 = \033[38;5;188m
C_445 = \033[38;5;189m
C_450 = \033[38;5;190m
C_451 = \033[38;5;191m
C_452 = \033[38;5;192m
C_453 = \033[38;5;193m
C_454 = \033[38;5;194m
C_455 = \033[38;5;195m
C_500 = \033[38;5;196m
C_501 = \033[38;5;197m
C_502 = \033[38;5;198m
C_503 = \033[38;5;199m
C_504 = \033[38;5;200m
C_505 = \033[38;5;201m
C_510 = \033[38;5;202m
C_511 = \033[38;5;203m
C_512 = \033[38;5;204m
C_513 = \033[38;5;205m
C_514 = \033[38;5;206m
C_515 = \033[38;5;207m
C_520 = \033[38;5;208m
C_521 = \033[38;5;209m
C_522 = \033[38;5;210m
C_523 = \033[38;5;211m
C_524 = \033[38;5;212m
C_525 = \033[38;5;213m
C_530 = \033[38;5;214m
C_531 = \033[38;5;215m
C_532 = \033[38;5;216m
C_533 = \033[38;5;217m
C_534 = \033[38;5;218m
C_535 = \033[38;5;219m
C_540 = \033[38;5;220m
C_541 = \033[38;5;221m
C_542 = \033[38;5;222m
C_543 = \033[38;5;223m
C_544 = \033[38;5;224m
C_545 = \033[38;5;225m
C_550 = \033[38;5;226m
C_551 = \033[38;5;227m
C_552 = \033[38;5;228m
C_553 = \033[38;5;229m
C_554 = \033[38;5;230m
C_555 = \033[38;5;231m
-54
View File
@@ -1,54 +0,0 @@
# Transcendence
Exemple d'../.env fonctionnel:
POSTGRES_PASSWORD=coucou
JWT_SECRET=superlongsecretkeyatleast32characterspleasenevercommitthis
POSTGRES_DB=database
POSTGRES_HOST=database
POSTGRES_USER=user
GITHUB_CLIENT_ID=Iv1.xxxxxxxxxxxxxxxxxxxxxxxxxxx
GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
GITHUB_CALLBACK_URL=http://localhost:8080/api/auth/github/callback
Les Variables d'env GITHUB_* sont a generer sur ce lien 'https://github.com/settings/applications/new'
Gestion de friendship dans POSTGRESQL:
'pending' → demande envoyée
'accepted' → amis
'blocked' → bloqué
'rejected' → refusé
Ressource:
https://www.postgresql.org/docs/
https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
https://docs.github.com/fr/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app
/////////////////////////////////////////////////////////////////////////////////////////
BACKEND
17/01 - Ajout du service/route pour le systeme de game_room
permet aux joueurs de creer et rejoindre des rooms
une room vide est automatiquement detruite.
- Presence d'une fonction affichant toutes les rooms joignables
ainsi qu'une autre fonction pour afficher tous les joueurs de la room avec
leur scores et leur etat actuel.
- Aucun moyen de changer l'etat de la room de waiting a en cours ou finished
ca attendra le systeme du jeu
21/01 - Ajout du service/route pour le systeme d'avatar
permet aux utilisateurs de changer ou supprimer leur avatar actuel
- Ajout egalement d'une simple fonction pour recuperer l'avatar d'un utilisateur (pour le frontend)
DATABASE
17/01 Ajout des tables game_rooms, game_players, game_rounds, words
- nom, status et parametres de la game
- joueurs dans la game, leur scores et leur role actuel (dessinateur, devineur)
- historique de la game, qui a dessine quoi precedemment ainsi que les timers des rounds, sera aussi utile si on veut faire les stats de compte a l'avenir.
- contient la liste des mots utilisable par les joueurs
21/01 Ajout de avatar_url dans la table users
Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

+29
View File
@@ -45,8 +45,28 @@ async function runMigrations()
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='users' AND column_name='games_won') THEN
ALTER TABLE users ADD COLUMN games_won INT DEFAULT 0;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='users' AND column_name='tetris_best_score') THEN
ALTER TABLE users ADD COLUMN tetris_best_score INT DEFAULT 0;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='users' AND column_name='tetris_wins') THEN
ALTER TABLE users ADD COLUMN tetris_wins INT DEFAULT 0;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='users' AND column_name='tetris_games_played') THEN
ALTER TABLE users ADD COLUMN tetris_games_played INT DEFAULT 0;
END IF;
END $$;
`);
// Create tetris_game_history table if not exists
await pool.query(`
CREATE TABLE IF NOT EXISTS tetris_game_history (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id) ON DELETE CASCADE,
score INT NOT NULL DEFAULT 0,
game_type VARCHAR(10) NOT NULL DEFAULT 'solo',
result VARCHAR(10) DEFAULT NULL,
played_at TIMESTAMP DEFAULT NOW()
);
`);
console.log('Migrations completed!');
}
catch (err)
@@ -138,6 +158,15 @@ async function createTables()
started_at TIMESTAMP DEFAULT NOW(),
ended_at TIMESTAMP
);
CREATE TABLE IF NOT EXISTS tetris_game_history (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id) ON DELETE CASCADE,
score INT NOT NULL DEFAULT 0,
game_type VARCHAR(10) NOT NULL DEFAULT 'solo',
result VARCHAR(10) DEFAULT NULL,
played_at TIMESTAMP DEFAULT NOW()
);
`);
console.log('Tables created!');
}
@@ -18,6 +18,21 @@ router.get('/', authenticateToken, async(req, res) =>
}
});
// Get list of rooms currently being played (for spectators)
router.get('/playing', authenticateToken, async(req, res) =>
{
try
{
const rooms = await gameRoomService.listPlayingRooms();
res.json(rooms);
}
catch (err)
{
console.error(err);
res.status(500).json({error: 'Server error'});
}
});
// IMPORTANT: This route must be before /:roomId to avoid "current" being interpreted as a roomId
router.get('/current', authenticateToken, async(req, res) =>
{
@@ -134,4 +149,37 @@ router.post('/:roomId/leave', authenticateToken, async(req, res) =>
}
});
// Join a room as spectator
router.post('/:roomId/spectate', authenticateToken, async(req, res) =>
{
try
{
const room = await gameRoomService.spectateRoom(req.params.roomId, req.user.userId);
res.json(room);
}
catch(err)
{
console.error(err);
if (err.message.includes('not found') || err.message.includes('not in playing') || err.message.includes('already in'))
res.status(400).json({error: err.message});
else
res.status(500).json({error: err.message});
}
});
// Leave spectator mode
router.post('/:roomId/leave-spectate', authenticateToken, async(req, res) =>
{
try
{
await gameRoomService.leaveSpectateRoom(req.params.roomId, req.user.userId);
res.json({message: 'Left spectator mode successfully'});
}
catch(err)
{
console.error(err);
res.status(500).json({error: 'Server error'});
}
});
export default router;
@@ -31,7 +31,7 @@ router.get('/user/:username', authenticateToken, async (req, res) => {
}
});
// Get leaderboard
// Get general leaderboard
router.get('/leaderboard', authenticateToken, async (req, res) => {
try {
const limit = Math.min(parseInt(req.query.limit) || 10, 50);
@@ -43,4 +43,78 @@ router.get('/leaderboard', authenticateToken, async (req, res) => {
}
});
// Save tetris score (solo) — updates best score if higher + saves to history
router.post('/tetris/score', authenticateToken, async (req, res) => {
try {
const { score } = req.body;
if (typeof score !== 'number' || score < 0) {
return res.status(400).json({ error: 'Invalid score' });
}
const bestScore = await playerStatsService.updateTetrisBestScore(req.user.userId, score);
await playerStatsService.incrementTetrisGamesPlayed(req.user.userId);
await playerStatsService.addTetrisGameHistory(req.user.userId, score, 'solo', null);
res.json({ bestScore });
} catch (err) {
console.error('Error saving tetris score:', err);
res.status(500).json({ error: 'Server error' });
}
});
// Tetris best score leaderboard
router.get('/tetris/leaderboard/score', authenticateToken, async (req, res) => {
try {
const limit = Math.min(parseInt(req.query.limit) || 10, 50);
const leaderboard = await playerStatsService.getTetrisBestScoreLeaderboard(limit);
res.json(leaderboard);
} catch (err) {
console.error('Error getting tetris score leaderboard:', err);
res.status(500).json({ error: 'Server error' });
}
});
// Tetris duel wins leaderboard
router.get('/tetris/leaderboard/wins', authenticateToken, async (req, res) => {
try {
const limit = Math.min(parseInt(req.query.limit) || 10, 50);
const leaderboard = await playerStatsService.getTetrisDuelWinsLeaderboard(limit);
res.json(leaderboard);
} catch (err) {
console.error('Error getting tetris wins leaderboard:', err);
res.status(500).json({ error: 'Server error' });
}
});
// Current user's rank by tetris best score
router.get('/tetris/rank/score', authenticateToken, async (req, res) => {
try {
const rank = await playerStatsService.getTetrisScoreRank(req.user.userId);
res.json({ rank });
} catch (err) {
console.error('Error getting tetris score rank:', err);
res.status(500).json({ error: 'Server error' });
}
});
// Get current user's tetris game history (last 15)
router.get('/tetris/history', authenticateToken, async (req, res) => {
try {
const history = await playerStatsService.getTetrisGameHistory(req.user.userId);
res.json(history);
} catch (err) {
console.error('Error getting tetris history:', err);
res.status(500).json({ error: 'Server error' });
}
});
// Current user's rank by tetris duel wins
router.get('/tetris/rank/wins', authenticateToken, async (req, res) => {
try {
const rank = await playerStatsService.getTetrisDuelWinsRank(req.user.userId);
res.json({ rank });
} catch (err) {
console.error('Error getting tetris wins rank:', err);
res.status(500).json({ error: 'Server error' });
}
});
export default router;
@@ -44,6 +44,70 @@ async function listActiveRooms()
return (result.rows);
}
async function listPlayingRooms()
{
const result = await query
(
`SELECT r.*, COUNT(p.id) as player_count
FROM game_rooms r
LEFT JOIN game_players p ON r.id = p.room_id
WHERE r.status = 'playing'
GROUP BY r.id
ORDER BY player_count DESC, r.created_at DESC`
);
return (result.rows);
}
async function spectateRoom(roomId, userId)
{
const room = await getRoomById(roomId);
if (!room)
throw new Error('Room not found');
if (room.status !== 'playing')
throw new Error('Room is not in playing status');
// Check if user is already a player in any active game
const playerInGame = await query
(
`SELECT r.id, r.name, r.status
FROM game_rooms r
JOIN game_players gp ON r.id = gp.room_id
WHERE gp.user_id = $1 AND r.status IN ('waiting', 'playing')
LIMIT 1`,
[userId]
);
if (playerInGame.rows.length > 0)
{
const gameRoom = playerInGame.rows[0];
if (gameRoom.id === parseInt(roomId))
throw new Error('You cannot spectate a game you are playing in');
else
throw new Error('You are already in an active game');
}
return (room);
}
async function leaveSpectateRoom(roomId, userId)
{
const playerCount = await query
(
'SELECT COUNT(*) FROM game_players WHERE room_id = $1',
[roomId]
);
if (parseInt(playerCount.rows[0].count) === 0)
{
await query
(
'DELETE FROM game_rooms WHERE id = $1',
[roomId]
);
}
}
async function joinRoom(roomId, userId)
{
const room = await getRoomById(roomId);
@@ -116,20 +180,75 @@ async function getCurrentRoom(userId)
`SELECT r.*
FROM game_rooms r
JOIN game_players gp ON r.id = gp.room_id
WHERE gp.user_id = $1 AND r.status = 'waiting'
WHERE gp.user_id = $1 AND r.status IN ('waiting', 'playing')
LIMIT 1`,
[userId]
);
return (result.rows[0] || null);
}
// Update room status (waiting, playing, ended)
async function updateRoomStatus(roomId, status)
{
const validStatuses = ['waiting', 'playing', 'ended'];
if (!validStatuses.includes(status))
throw new Error('Invalid status');
let updateQuery = 'UPDATE game_rooms SET status = $1';
const params = [status, roomId];
if (status === 'playing')
{
updateQuery += ', started_at = NOW()';
}
else if (status === 'ended')
{
updateQuery += ', ended_at = NOW()';
}
updateQuery += ' WHERE id = $2 RETURNING *';
const result = await query(updateQuery, params);
return (result.rows[0]);
}
async function resetRoomScores(roomId)
{
await query
(
'UPDATE game_players SET score = 0 WHERE room_id = $1',
[roomId]
);
}
async function cleanupEndedRooms()
{
await query
(
'DELETE FROM game_players WHERE room_id IN (SELECT id FROM game_rooms WHERE status = $1)',
['ended']
);
await query
(
'DELETE FROM game_rooms WHERE status = $1',
['ended']
);
}
export default
{
createRoom,
getRoomById,
listActiveRooms,
listPlayingRooms,
spectateRoom,
leaveSpectateRoom,
joinRoom,
leaveRoom,
getRoomPlayers,
getCurrentRoom
getCurrentRoom,
updateRoomStatus,
resetRoomScores,
cleanupEndedRooms
};
@@ -3,7 +3,8 @@ import { query } from '../db.js';
// Get player stats by user ID
async function getStatsByUserId(userId) {
const result = await query(
`SELECT id, username, avatar_url, total_points, games_played, games_won, created_at
`SELECT id, username, avatar_url, total_points, games_played, games_won,
tetris_best_score, tetris_wins, tetris_games_played, created_at
FROM users WHERE id = $1`,
[userId]
);
@@ -13,7 +14,8 @@ async function getStatsByUserId(userId) {
// Get player stats by username
async function getStatsByUsername(username) {
const result = await query(
`SELECT id, username, avatar_url, total_points, games_played, games_won, created_at
`SELECT id, username, avatar_url, total_points, games_played, games_won,
tetris_best_score, tetris_wins, tetris_games_played, created_at
FROM users WHERE username = $1`,
[username]
);
@@ -76,6 +78,111 @@ async function getUserIdByUsername(username) {
return result.rows[0]?.id || null;
}
// Update tetris best score (only if new score is higher)
async function updateTetrisBestScore(userId, score) {
const result = await query(
`UPDATE users SET tetris_best_score = GREATEST(COALESCE(tetris_best_score, 0), $1) WHERE id = $2 RETURNING tetris_best_score`,
[score, userId]
);
return result.rows[0]?.tetris_best_score || 0;
}
// Increment tetris duel wins
async function incrementTetrisWins(userId) {
await query(
`UPDATE users SET tetris_wins = COALESCE(tetris_wins, 0) + 1 WHERE id = $1`,
[userId]
);
}
// Increment tetris games played
async function incrementTetrisGamesPlayed(userId) {
await query(
`UPDATE users SET tetris_games_played = COALESCE(tetris_games_played, 0) + 1 WHERE id = $1`,
[userId]
);
}
// Leaderboard: best tetris scores
async function getTetrisBestScoreLeaderboard(limit = 10) {
const result = await query(
`SELECT id, username, avatar_url, tetris_best_score, tetris_wins, tetris_games_played
FROM users
WHERE tetris_best_score > 0
ORDER BY tetris_best_score DESC
LIMIT $1`,
[limit]
);
return result.rows;
}
// Leaderboard: most tetris duel wins
async function getTetrisDuelWinsLeaderboard(limit = 10) {
const result = await query(
`SELECT id, username, avatar_url, tetris_wins, tetris_games_played, tetris_best_score
FROM users
WHERE tetris_wins > 0
ORDER BY tetris_wins DESC
LIMIT $1`,
[limit]
);
return result.rows;
}
// Add a game to tetris history (keep max 15 per user)
async function addTetrisGameHistory(userId, score, gameType = 'solo', result = null) {
await query(
`INSERT INTO tetris_game_history (user_id, score, game_type, result) VALUES ($1, $2, $3, $4)`,
[userId, score, gameType, result]
);
// Keep only the 15 most recent entries
await query(
`DELETE FROM tetris_game_history
WHERE id IN (
SELECT id FROM tetris_game_history
WHERE user_id = $1
ORDER BY played_at DESC
OFFSET 15
)`,
[userId]
);
}
// Get the last 15 games for a user
async function getTetrisGameHistory(userId) {
const result = await query(
`SELECT id, score, game_type, result, played_at
FROM tetris_game_history
WHERE user_id = $1
ORDER BY played_at DESC
LIMIT 15`,
[userId]
);
return result.rows;
}
// Rank of a user by tetris best score (1 = best)
async function getTetrisScoreRank(userId) {
const result = await query(
`SELECT COUNT(*) + 1 AS rank
FROM users
WHERE tetris_best_score > COALESCE((SELECT tetris_best_score FROM users WHERE id = $1), 0)`,
[userId]
);
return parseInt(result.rows[0]?.rank || 1);
}
// Rank of a user by tetris duel wins (1 = best)
async function getTetrisDuelWinsRank(userId) {
const result = await query(
`SELECT COUNT(*) + 1 AS rank
FROM users
WHERE tetris_wins > COALESCE((SELECT tetris_wins FROM users WHERE id = $1), 0)`,
[userId]
);
return parseInt(result.rows[0]?.rank || 1);
}
export default {
getStatsByUserId,
getStatsByUsername,
@@ -84,5 +191,14 @@ export default {
incrementGamesPlayed,
incrementGamesWon,
getLeaderboard,
getUserIdByUsername
getUserIdByUsername,
updateTetrisBestScore,
incrementTetrisWins,
incrementTetrisGamesPlayed,
getTetrisBestScoreLeaderboard,
getTetrisDuelWinsLeaderboard,
getTetrisScoreRank,
getTetrisDuelWinsRank,
addTetrisGameHistory,
getTetrisGameHistory
};
+478 -18
View File
@@ -7,6 +7,12 @@ import playerStatsService from './player_stats.js';
// Store game state per room
const gameRooms = new Map();
// Store tetris duel rooms { roomCode → Map<socketId, socket> }
const tetrisRooms = new Map();
// Matchmaking queue for tetris
const tetrisMatchmakingQueue = [];
// Store io instance globally for use in routes
let ioInstance = null;
@@ -24,6 +30,42 @@ async function broadcastRoomsList(io) {
}
}
// Check if a playing game has only 1 player left and auto-stop it
async function checkAndStopSinglePlayerGame(io, roomId, dbRoomId) {
if (!dbRoomId) return;
try {
// Check if room is in 'playing' status
const room = await gameRoomService.getRoomById(dbRoomId);
if (!room || room.status !== 'playing') return;
// Count remaining players
const players = await gameRoomService.getRoomPlayers(dbRoomId);
if (players.length <= 1) {
console.log(`Room ${dbRoomId} has only ${players.length} player(s) left, ending game`);
// Update room status to 'ended'
await gameRoomService.updateRoomStatus(dbRoomId, 'waiting');
await gameRoomService.resetRoomScores(dbRoomId);
// Remove from game state
gameRooms.delete(roomId);
// Notify remaining player(s)
io.to(roomId).emit('game-ended');
io.to(roomId).emit('game-message', {
message: 'La partie s\'est terminée car il ne reste qu\'un seul joueur',
type: 'info'
});
// Broadcast updated rooms list
broadcastRoomsList(io);
}
} catch (err) {
console.error('Error checking single player game:', err);
}
}
// Save round points to database (only the difference from round start)
async function saveRoundPoints(currentScores, roundStartScores) {
for (const [username, currentPoints] of Object.entries(currentScores)) {
@@ -182,16 +224,94 @@ function setupSocketIO(io)
socket.gameRoomId = null;
socket.gameRoomDbId = null;
// Check if game should auto-stop due to single player
await checkAndStopSinglePlayerGame(io, roomId, dbRoomId);
// Broadcast updated rooms list
broadcastRoomsList(io);
}
});
// Join a game room as spectator
socket.on('game-spectate-room', async (data) => {
console.log('Received game-spectate-room from', socket.user.username, 'data:', data);
const roomId = `game-room-${data.roomId}`;
// Verify room exists and is in playing status, and user is not already in a game
try {
const room = await gameRoomService.spectateRoom(data.roomId, socket.user.userId);
socket.join(roomId);
socket.gameRoomId = roomId;
socket.gameRoomDbId = data.roomId;
socket.isSpectator = true;
console.log(`${socket.user.username} joined ${roomId} as spectator`);
// Send confirmation
socket.emit('game-spectate-joined', {
roomId: data.roomId,
success: true
});
// Notify others that a spectator joined
socket.to(roomId).emit('game-spectator-joined', {
username: socket.user.username
});
// Send current game state
const gameState = gameRooms.get(roomId);
if (gameState && gameState.isPlaying) {
socket.emit('game-state-sync', {
isPlaying: gameState.isPlaying,
drawer: gameState.drawer,
wordLength: gameState.currentWord ? gameState.currentWord.length : 0,
revealedLetters: gameState.revealedLetters,
revealedWord: gameState.revealedWord || [],
guessedLetters: gameState.guessedLetters,
players: gameState.players,
scores: gameState.scores || {}
});
}
} catch (err) {
console.error('Error joining as spectator:', err);
socket.emit('game-spectate-error', {
error: err.message || 'Cannot spectate this room'
});
}
});
// Leave spectator mode
socket.on('game-leave-spectate', () => {
if (socket.gameRoomId && socket.isSpectator) {
const roomId = socket.gameRoomId;
socket.to(roomId).emit('game-spectator-left', {
username: socket.user.username
});
socket.leave(roomId);
console.log(`${socket.user.username} left spectator mode in ${roomId}`);
socket.gameRoomId = null;
socket.gameRoomDbId = null;
socket.isSpectator = false;
}
});
// Start the game
socket.on('game-start', (data) => {
socket.on('game-start', async (data) => {
console.log('Received game-start event from', socket.user.username);
console.log('socket.gameRoomId:', socket.gameRoomId);
// Security check: need at least 2 players
if (!data.players || data.players.length < 2) {
console.log('Game start rejected: not enough players');
socket.emit('game-start-error', {
error: 'Il faut au moins 2 joueurs pour commencer'
});
return;
}
const gameStartedData = {
drawer: data.drawer,
players: data.players
@@ -206,6 +326,33 @@ function setupSocketIO(io)
return;
}
// Verify player count from database
const dbRoomId = socket.gameRoomDbId;
if (dbRoomId) {
try {
const players = await gameRoomService.getRoomPlayers(dbRoomId);
if (players.length < 2) {
console.log(`Game start rejected: only ${players.length} player(s) in room`);
socket.emit('game-start-error', {
error: 'Il faut au moins 2 joueurs pour commencer'
});
return;
}
} catch (err) {
console.error('Error checking player count:', err);
}
}
// Update room status to 'playing' in database
if (dbRoomId) {
try {
await gameRoomService.updateRoomStatus(dbRoomId, 'playing');
console.log(`Room ${dbRoomId} status updated to 'playing'`);
} catch (err) {
console.error('Error updating room status to playing:', err);
}
}
// Initialize scores for all players
const scores = {};
data.players.forEach(p => scores[p] = 0);
@@ -230,6 +377,9 @@ function setupSocketIO(io)
socket.emit('game-started', gameStartedData);
console.log(`Game started in ${roomId} by ${socket.user.username}`);
// Broadcast updated rooms list (this room should no longer appear)
broadcastRoomsList(io);
});
// Drawer sets the word
@@ -266,6 +416,12 @@ function setupSocketIO(io)
const roomId = socket.gameRoomId;
if (!roomId) return;
// Spectators cannot draw
if (socket.isSpectator) {
console.log(`Spectator ${socket.user.username} tried to draw - blocked`);
return;
}
// Broadcast drawing to all other players in the room
socket.to(roomId).emit('game-draw', {
x1: data.x1,
@@ -282,6 +438,9 @@ function setupSocketIO(io)
const roomId = socket.gameRoomId;
if (!roomId) return;
// Spectators cannot clear canvas
if (socket.isSpectator) return;
socket.to(roomId).emit('game-clear-canvas');
});
@@ -290,6 +449,13 @@ function setupSocketIO(io)
const roomId = socket.gameRoomId;
if (!roomId) return;
// Spectators cannot make guesses
if (socket.isSpectator) {
console.log(`Spectator ${socket.user.username} tried to guess - blocked`);
return;
}
const gameState = gameRooms.get(roomId);
if (!gameState || !gameState.currentWord) return;
@@ -413,45 +579,339 @@ function setupSocketIO(io)
});
});
socket.on('leave-room-during-game', async () => {
const roomId = socket.gameRoomId;
const dbRoomId = socket.gameRoomDbId;
const userId = socket.user.userId;
const username = socket.user.username;
if (!roomId || !dbRoomId || !userId) return;
console.log(`Player ${username} leaving room ${roomId} during game`);
try
{
socket.leave(roomId);
await gameRoomService.leaveRoom(dbRoomId, userId);
io.to(roomId).emit('game-player-left', {
username: username,
message: `${username} a quitté la partie`
});
const gameState = gameRooms.get(roomId);
if (gameState)
{
const wasDrawer = gameState.drawer === username;
gameState.players = gameState.players.filter(p => p !== username);
delete gameState.scores[username];
io.to(roomId).emit('scores-updated', gameState.scores);
// If the drawer left and there are still enough players, choose a new drawer
if (wasDrawer && gameState.players.length >= 1)
{
// Pick the next player as the new drawer
gameState.currentPlayerIndex = gameState.currentPlayerIndex % gameState.players.length;
const newDrawer = gameState.players[gameState.currentPlayerIndex];
gameState.drawer = newDrawer;
// Reset the word state for the new round
gameState.currentWord = '';
gameState.revealedLetters = [];
gameState.revealedWord = [];
gameState.guessedLetters = [];
gameState.wrongGuesses = 0;
console.log(`Drawer ${username} left, new drawer is ${newDrawer}`);
io.to(roomId).emit('game-drawer-changed', {
newDrawer: newDrawer,
reason: 'drawer_left',
message: `${username} (dessinateur) a quitté, ${newDrawer} devient le nouveau dessinateur`
});
}
}
await checkAndStopSinglePlayerGame(io, roomId, dbRoomId);
socket.gameRoomId = null;
socket.gameRoomDbId = null;
broadcastRoomsList(io);
}
catch (err)
{
console.error('Error leaving room during game:', err);
}
});
// End game
socket.on('game-end', () => {
socket.on('game-end', async () => {
const roomId = socket.gameRoomId;
if (!roomId) return;
// Update room status to 'waiting' in database
const dbRoomId = socket.gameRoomDbId;
if (dbRoomId) {
try {
await gameRoomService.updateRoomStatus(dbRoomId, 'waiting');
await gameRoomService.resetRoomScores(dbRoomId);
console.log(`Room ${dbRoomId} status updated to 'waiting'`);
} catch (err) {
console.error('Error updating room status to waiting:', err);
}
}
gameRooms.delete(roomId);
io.to(roomId).emit('game-ended');
// Broadcast updated rooms list
broadcastRoomsList(io);
});
// ============================================
// TETRIS DUEL EVENTS
// ============================================
socket.on('tetris:join', ({ roomCode }) => {
const code = String(roomCode).toUpperCase().slice(0, 8);
// Quitter l'ancienne room tetris si besoin
if (socket.tetrisRoomCode) {
_tetrisLeave(socket);
}
if (!tetrisRooms.has(code)) {
tetrisRooms.set(code, new Map());
}
const room = tetrisRooms.get(code);
if (room.size >= 2) {
socket.emit('tetris:room-status', { status: 'full', players: [] });
return;
}
room.set(socket.id, socket);
socket.tetrisRoomCode = code;
const players = [...room.values()].map(s => s.user.username);
if (room.size === 1) {
socket.emit('tetris:room-status', { status: 'waiting', players });
} else {
// Notifier les deux joueurs
for (const s of room.values()) {
s.emit('tetris:room-status', { status: 'ready', players });
}
// Notifier l'adversaire qu'un nouveau joueur a rejoint
for (const [id, s] of room) {
if (id !== socket.id) {
s.emit('tetris:opponent-joined', { username: socket.user.username });
}
}
}
});
socket.on('tetris:leave', () => {
_tetrisLeave(socket);
});
// Relay pur : grid-update → adversaire uniquement
socket.on('tetris:grid-update', (data) => {
if (data.score !== undefined) socket.tetrisLastScore = data.score;
_tetrisRelayToOpponent(socket, 'tetris:grid-update', data);
});
// Relay pur : lines-cleared → adversaire uniquement
socket.on('tetris:lines-cleared', (data) => {
_tetrisRelayToOpponent(socket, 'tetris:lines-cleared', data);
});
// start-duel → relayé aux DEUX joueurs de la room (inclut l'émetteur)
socket.on('tetris:start-duel', () => {
const code = socket.tetrisRoomCode;
if (!code) return;
const room = tetrisRooms.get(code);
if (!room || room.size < 2) return;
for (const s of room.values()) {
s.emit('tetris:start-duel');
}
});
// pause → relayé aux DEUX joueurs de la room
socket.on('tetris:pause', () => {
const code = socket.tetrisRoomCode;
if (!code) return;
const room = tetrisRooms.get(code);
if (!room) return;
for (const s of room.values()) {
s.emit('tetris:pause');
}
});
// stop → relayé aux DEUX joueurs de la room
socket.on('tetris:stop', () => {
const code = socket.tetrisRoomCode;
if (!code) return;
const room = tetrisRooms.get(code);
if (!room) return;
for (const s of room.values()) {
s.emit('tetris:stop');
}
});
// settings → relayé aux DEUX joueurs de la room
socket.on('tetris:settings', (data) => {
const code = socket.tetrisRoomCode;
if (!code) return;
const room = tetrisRooms.get(code);
if (!room) return;
for (const s of room.values()) {
s.emit('tetris:settings', data);
}
});
// game-over → save stats + relay opponent-game-over
socket.on('tetris:game-over', async (data) => {
const loserId = socket.user.userId;
try {
await playerStatsService.updateTetrisBestScore(loserId, data.score || 0);
await playerStatsService.incrementTetrisGamesPlayed(loserId);
await playerStatsService.addTetrisGameHistory(loserId, data.score || 0, 'duel', 'loss');
} catch (err) {
console.error('Error saving tetris loser stats:', err);
}
const code = socket.tetrisRoomCode;
if (code) {
const room = tetrisRooms.get(code);
if (room) {
for (const [id, s] of room) {
if (id !== socket.id) {
s.emit('tetris:opponent-game-over', data);
try {
await playerStatsService.incrementTetrisWins(s.user.userId);
await playerStatsService.incrementTetrisGamesPlayed(s.user.userId);
const winnerScore = s.tetrisLastScore || 0;
await playerStatsService.addTetrisGameHistory(s.user.userId, winnerScore, 'duel', 'win');
} catch (err) {
console.error('Error saving tetris winner stats:', err);
}
}
}
}
}
});
// Matchmaking
socket.on('tetris:matchmaking-join', () => {
// Remove from queue if already there
const idx = tetrisMatchmakingQueue.findIndex(s => s.id === socket.id);
if (idx !== -1) tetrisMatchmakingQueue.splice(idx, 1);
tetrisMatchmakingQueue.push(socket);
socket.emit('tetris:matchmaking-status', { status: 'searching', position: tetrisMatchmakingQueue.length });
if (tetrisMatchmakingQueue.length >= 2) {
const player1 = tetrisMatchmakingQueue.shift();
const player2 = tetrisMatchmakingQueue.shift();
const roomCode = Math.random().toString(36).substring(2, 8).toUpperCase();
player1.emit('tetris:matched', { roomCode, opponent: player2.user.username });
player2.emit('tetris:matched', { roomCode, opponent: player1.user.username });
}
});
socket.on('tetris:matchmaking-leave', () => {
const idx = tetrisMatchmakingQueue.findIndex(s => s.id === socket.id);
if (idx !== -1) tetrisMatchmakingQueue.splice(idx, 1);
socket.emit('tetris:matchmaking-status', { status: 'idle' });
});
socket.on('disconnect', async () =>
{
// Nettoyage matchmaking tetris
const mqIdx = tetrisMatchmakingQueue.findIndex(s => s.id === socket.id);
if (mqIdx !== -1) tetrisMatchmakingQueue.splice(mqIdx, 1);
// Nettoyage room tetris
if (socket.tetrisRoomCode) {
_tetrisLeave(socket);
}
console.log(`User disconnected: ${socket.user.username}`);
// Notify game room if player was in one
// Notify game room if player/spectator was in one
if (socket.gameRoomId) {
const roomId = socket.gameRoomId;
const dbRoomId = socket.gameRoomDbId;
socket.to(roomId).emit('game-player-left', {
username: socket.user.username,
userId: socket.user.userId
});
// Get updated player list and broadcast
if (dbRoomId) {
try {
const players = await gameRoomService.getRoomPlayers(dbRoomId);
io.to(roomId).emit('game-players-updated', { players });
} catch (err) {
console.log('Room may have been deleted on disconnect:', err.message);
}
// If spectator, just notify and leave
if (socket.isSpectator) {
socket.to(roomId).emit('game-spectator-left', {
username: socket.user.username
});
console.log(`Spectator ${socket.user.username} disconnected from ${roomId}`);
}
else
{
// Regular player disconnect
socket.to(roomId).emit('game-player-left', {
username: socket.user.username,
userId: socket.user.userId
});
// Broadcast updated rooms list
broadcastRoomsList(io);
// Get updated player list and broadcast
if (dbRoomId) {
try {
const players = await gameRoomService.getRoomPlayers(dbRoomId);
io.to(roomId).emit('game-players-updated', { players });
} catch (err) {
console.log('Room may have been deleted on disconnect:', err.message);
}
}
// Check if game should auto-stop due to single player
await checkAndStopSinglePlayerGame(io, roomId, dbRoomId);
// Broadcast updated rooms list
broadcastRoomsList(io);
}
}
});
});
}
// ── Helpers tetris duel ──────────────────────────────────────────────────
function _tetrisLeave(socket)
{
const code = socket.tetrisRoomCode;
if (!code) return;
const room = tetrisRooms.get(code);
if (room) {
room.delete(socket.id);
// Notifier l'adversaire restant
for (const s of room.values()) {
s.emit('tetris:opponent-left');
s.emit('tetris:room-status', { status: 'waiting', players: [s.user.username] });
}
if (room.size === 0) tetrisRooms.delete(code);
}
socket.tetrisRoomCode = null;
}
function _tetrisRelayToOpponent(socket, event, data) {
const code = socket.tetrisRoomCode;
if (!code) return;
const room = tetrisRooms.get(code);
if (!room) return;
for (const [id, s] of room) {
if (id !== socket.id) s.emit(event, data);
}
}
export { broadcastRoomsList };
export default setupSocketIO;
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1018 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 955 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Some files were not shown because too many files have changed in this diff Show More