commit b8e4dc2a5d28c2f8d983572f8c8b13d9037dff88 Author: Leo VIALLON-GALINIER Date: Sat Apr 25 12:08:19 2020 +0200 Firs commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ecb1e52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# Translations +*.mo +*.pot + +# pyenv +.python-version + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +## Editors: +*.bak +*.sav +*.backup +.*.swp +*~ + +# Secret configuration +settings.cfg + +# Personal conf +static/jquery.js +static/socket.io.js +templates/head-perso.html +templates/scripts-perso.html diff --git a/belote.py b/belote.py new file mode 100644 index 0000000..0f9268d --- /dev/null +++ b/belote.py @@ -0,0 +1,373 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import flask +from flask_socketio import SocketIO +import flask_login as fll +import datetime +from db import db, User, Game, Player, levels_users +from belote_jeu import N_TURN, N_PLAYERS +import belote_ws + +login_manager = fll.LoginManager() + +app = flask.Flask(__name__) +app.config.from_pyfile('settings.cfg') + +####################################################################### +# Plugin management # +####################################################################### + +# SocketIO +socketio = SocketIO(app) +login_manager.init_app(app) + +# Database management +db.app = app +db.init_app(app) +db.create_all() +db.session.commit() + +# Insert admin if not exist +if User.query.limit(1).first() is None: + admin = User(login='admin', name='Admin', level=levels_users['Admin']) + admin.set_password('admin') + db.session.add(admin) + db.session.commit() + +for i in range(4): + if User.query.get('leo'+str(i)) is None: + leo = User(login='leo'+str(i), name='Leo '+str(i), level=levels_users['Can manage simple users']) + leo.set_password('leo') + db.session.add(leo) +db.session.commit() + +# Login management +@login_manager.user_loader +def load_user(user_id): + if user_id is None : + return None + return User.query.get(user_id) + +@login_manager.unauthorized_handler +def unauthorized_callback(): + return flask.redirect('/login?next=' + flask.request.path) + + +####################################################################### +# Global variables or fuctions # +####################################################################### +URL_DEFAULT = '/games' +namespaces_join = {} +namespaces = {} + +def create_namespace(gid, join=False): + if(join): + namespace = belote_ws.CustomNamespaceJoin(gid) + namespaces_join[gid] = True + socketio.on_namespace(namespace) + else: + namespace = belote_ws.CustomNamespacePlay(gid) + namespaces[gid] = True + socketio.on_namespace(namespace) + + +@app.route('/') +def home(): + user = fll.current_user + if not hasattr(user, 'level'): + user = None + return flask.render_template("index.html", user=user) +####################################################################### +# Login, logout # +####################################################################### + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if fll.current_user: + flask.redirect(URL_DEFAULT) + if flask.request.method=="POST": + username = flask.request.form['username'] + password = flask.request.form['password'] + user = User.query.get(username) + if user and user.level > levels_users['Inactive'] and user.check_password(password): + fll.login_user(user) + user.last_login = datetime.datetime.utcnow() + next = flask.request.args.get('next') + if next is not None and next[0]=='/': + return flask.redirect(next) + else: + return flask.redirect(URL_DEFAULT) + else: + app.logger.warning('Login fail for user {} from {}'.format(user, flask.request.remote_addr)) + flask.flash('Invalid username/password combination') + return flask.render_template('login.html', user=None) + +@app.route('/logout') +def logout(): + fll.logout_user() + return flask.redirect('/') + +####################################################################### +# Games : list # +####################################################################### + +@app.route('/games') +@fll.login_required +def games(): + games = Game.query.all() + mesjeux = [] + joinable = [] + for g in games: + if g.admin==fll.current_user.login: + mesjeux.append(g) + elif g.can_join(fll.current_user): + joinable.append(g) + cancreategames = fll.current_user.level >= levels_users['Can create games'] + candeletegames = fll.current_user.level >= levels_users['Admin'] + return flask.render_template('games.html', mesjeux=mesjeux, joinable=joinable, user=fll.current_user, \ + cancreategames=cancreategames, candeletegames=candeletegames) + +####################################################################### +# Create, join and play to a game # +####################################################################### + +@app.route('/add_game', methods=['GET', 'POST']) +@fll.login_required +def game_add(): + player = fll.current_user + if fll.current_user.level >= levels_users['Can create games']: + if flask.request.method=="POST": + name = flask.request.form['name'] + if name is None or name == '': + return flask.redirect('/add_game') + game = Game(name=name, admin=player.login) + db.session.add(game) + db.session.commit() + game.join(fll.current_user) + db.session.commit() + return flask.redirect('/game/{}'.format(game.id)) + return flask.render_template('game_add.html', user=fll.current_user) + return flask.redirect(URL_DEFAULT) + +@app.route('/game//join') +@fll.login_required +def game_page_join(roomid): + game = Game.query.get(roomid) + if game: + player = fll.current_user + if game.start: + return flask.redirect('/game/{}'.format(roomid)) + if game.can_join(player): + if game.id not in namespaces_join: + create_namespace(game.id, join=True) + return flask.render_template('game_join.html', game=game, user=player, admin=game.admin) + return flask.redirect(URL_DEFAULT) + +@app.route('/game//start') +@fll.login_required +def game_page_start(roomid): + game = Game.query.get(roomid) + if game: + player = fll.current_user + if game.isadmin(player) and game.fixplayers and not game.start: + return flask.render_template('game_start.html', game=game, user=player) + return flask.redirect('/game/{}'.format(roomid)) + return flask.redirect(URL_DEFAULT) + +@app.route('/game/') +@fll.login_required +def game_page(roomid): + game = Game.query.get(roomid) + if game: + player = fll.current_user + if not game.start: + return flask.redirect('/game/{}/join'.format(roomid)) + if game.authorized(player) : + if game.id not in namespaces: + create_namespace(game.id) + return flask.render_template('game.html', game=game, user=player) + return flask.redirect(URL_DEFAULT) + +@app.route('/game//del') +@fll.login_required +def delgame(roomid): + game = Game.query.get(roomid) + if game is not None and fll.current_user.login == game.admin: + confirm = flask.request.args.get('confirm') + if confirm is not None: + db.session.delete(game) + db.session.commit() + else: + return flask.render_template('game_del.html', game=game, user=fll.current_user) + return flask.redirect(URL_DEFAULT) + +####################################################################### +# Users management # +####################################################################### +@app.route('/admin/users') +@fll.login_required +def admin_users(): + if fll.current_user.level < levels_users['Can manage simple users']: + return flask.redirect(URL_DEFAULT) + users = User.query.all() + return flask.render_template('admin_users.html', user=fll.current_user, users=users) + +@app.route('/admin/users/', methods=['GET', 'POST']) +@fll.login_required +def edituser(user): + new = user=='new' + user = User.query.get(user) + if not new and user is None: + return flask.redirect('/admin/users') + if fll.current_user.level >= levels_users['Admin'] or \ + fll.current_user.level >= levels_users['Can manage simple users'] and \ + fll.current_user.level > user.level: + if flask.request.method=="POST": + username = flask.request.form['username'] + name = flask.request.form['name'] + password = flask.request.form['password'] + level = int(flask.request.form['level']) + if level not in levels_users.values(): + flask.flash('Level unknown') + return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users) + if fll.current_user.level < levels_users['Admin']: + if not level < fll.current_user.level: + flask.flash('Invalid level') + return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users) + if len(password)<2 and new: + flask.flash('Mot de passe trop court') + return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users) + if new and User.query.get(username) is not None: + flask.flash('Login déjà utilisé ! Choisissez un autre !') + return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users) + if new: + usertoadd = User(login=username, name=name, level=level) + usertoadd.set_password(password) + db.session.add(usertoadd) + else: + user.name = name + if len(password)>=2: + user.password = password + user.level = level + db.session.commit() + return flask.redirect('/admin/users') + else: + return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users) + return flask.redirect(URL_DEFAULT) + +@app.route('/admin/users//del') +@fll.login_required +def deluser(user): + user = User.query.get(user) + if user is None: + return flask.redirect('/admin/users') + if fll.current_user.level >= levels_users['Admin'] or \ + fll.current_user.level >= levels_users['Can manage simple users'] and \ + fll.current_user.level > user.level: + confirm = flask.request.args.get('confirm') + if confirm is not None: + delete_game(user=user) + db.session.delete(user) + db.session.commit() + return flask.redirect('/admin/users') + return flask.render_template("admin_user_del.html", user=fll.current_user, useredit=user) + +@app.route('/password', methods=['GET', 'POST']) +@fll.login_required +def change_password(): + if flask.request.method=="POST": + password = flask.request.form['password'] + if len(password) < 5: + flask.flash('Mot de passe trop court !') + else: + fll.current_user.set_password(password) + db.session.commit() + return flask.redirect(URL_DEFAULT) + return flask.render_template("password.html", user=fll.current_user) + +####################################################################### +# Game managament # +####################################################################### +@app.route('/admin/games') +@fll.login_required +def admin_games(): + if fll.current_user.level < levels_users['Can manage games']: + return flask.redirect(URL_DEFAULT) + games = Game.query.all() + return flask.render_template('admin_games.html', user=fll.current_user, games=games) + +@app.route('/admin/games//del') +@fll.login_required +def admin_game_del(roomid): + game = Game.query.get(roomid) + if game is None: + return flask.redirect('/admin/games') + if fll.current_user.level >= levels_users['Can manage games']: + confirm = flask.request.args.get('confirm') + if confirm is not None: + delete_game(game=game) + return flask.redirect('/admin/games') + else: + return flask.render_template('game_del.html', user=fll.current_user, game=game) + return flask.redirect(URL_DEFAULT) + +@app.route('/game//see') +@fll.login_required +def see_game(roomid): + game = Game.query.get(roomid) + if game is None: + return flask.redirect('/games') + if fll.current_user.level >= levels_users['Can manage games'] or game.authorized(fll.current_user) and game.turn==N_TURN*N_PLAYERS: + return game.serialize_state_anonymous() + return flask.redirect(URL_DEFAULT) + +@app.route('/admin/games/new', methods=['GET', 'POST']) +@fll.login_required +def admin_games_new(): + if fll.current_user.level < levels_users['Can manage games']: + return flask.redirect(URL_DEFAULT) + if flask.request.method=="POST": + name = flask.request.form['name'] + admin = flask.request.form['admin'] + admin = User.query.get(admin) + if admin is None: + flask.flash('Login inconnu') + elif len(name)<2: + flask.flash('Nom trop court !') + else: + game = Game(name=name, admin=admin.login) + db.session.add(game) + db.session.commit() + game.join(admin) + db.session.commit() + return flask.redirect('/admin/games') + return flask.render_template('admin_game_add.html', user=fll.current_user) + + + +def delete_game(user=None, game=None): + if user is not None: + games = Game.query.filter_by(admin=user.login).all() + for g in games: + for p in g.players: + db.session.delete(p) + db.session.delete(g) + db.session.commit() + if isinstance(game, int): + g = Game.query.get(game) + if g is not None: + for p in g.players: + db.session.delete(p) + db.session.delete(g) + db.session.commit() + elif game is not None: + for p in game.players: + db.session.delete(p) + db.session.delete(game) + db.session.commit() + + +if __name__ == '__main__': + socketio.run(app, debug=True, host='0.0.0.0') + diff --git a/belote.wsgi b/belote.wsgi new file mode 100644 index 0000000..8ec0d02 --- /dev/null +++ b/belote.wsgi @@ -0,0 +1,8 @@ +import os +import sys +os.chdir(os.path.dirname(__file__)) +sys.path.insert(0,os.path.dirname(__file__)+'/venv/lib/python{}.{}/site-packages'.format(sys.version_info.major, sys.version_info.minor)) +sys.path.insert(0,os.path.dirname(__file__)) + +from belote import app as application + diff --git a/belote_jeu.py b/belote_jeu.py new file mode 100644 index 0000000..455812f --- /dev/null +++ b/belote_jeu.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- + +N_PLAYERS = 4 +N_TURN = int(32/4) + +# P : pique, C : Coeur, F : Carreau, T : Trèfle +# 7..9, 0 puis VDRA +cards =['AP', 'RP', 'DP', 'VP', '0P', '9P', '8P', '7P', + 'AC', 'RC', 'DC', 'VC', '0C', '9C', '8C', '7C', + 'AF', 'RF', 'DF', 'VF', '0F', '9F', '8F', '7F', + 'AT', 'RT', 'DT', 'VT', '0T', '9T', '8T', '7T' + ] + +points_atout = {'V' : 20, '9' : 14, 'A': 11, '0': 10, 'R': 4, 'D': 3, '8':0, '7':0} +points_atout_order = {'V' : 20, '9' : 14, 'A': 11, '0': 10, 'R': 4, 'D': 3, '8':1, '7':0} +points_order = {'A': 11, '0': 10, 'R': 4, 'D': 3, 'V': 2, '9': 1, '8':0.5, '7':0} +points = {'A': 11, '0': 10, 'R': 4, 'D': 3, 'V': 2, '9': 0, '8':0, '7':0} + +translate = {'A': 'As', 'R' : 'Roi', 'D': 'Dame', 'V': 'Valet', '0': '10', 'P': 'Pique', 'C': 'Coeur', 'T':'Trèfle', 'F':'Carreau'} + +colors = ['P', 'C', 'F', 'T'] + +def translator(card): + s = [] + for c in card: + if c in translate: + s.append(translate[c]) + else: + s.append(c) + return ' '.join(s) + +def winner(cards, atout): + """ + Returns id winner (0-3), number of points + """ + if isinstance(cards, str): + cardsl = cards.split(',') + cardss = cards + else: + cardss = ''.join(cards) + cardsl = cards + + if len(cardss)==0: + return 0, 0 + winner = None + if atout in cardss: + for i in range(len(cardsl)): + if cardsl[i][1] == atout: + if winner is None or points_atout_order[cardsl[i][0]] > points_atout_order[cardsl[winner][0]]: + winner = i + if winner is None: + couldemandee = cardss[1] + for i in range(len(cardsl)): + if cardsl[i][1] == couldemandee: + if winner is None or points_order[cardsl[i][0]] > points_order[cardsl[winner][0]]: + winner = i + + pointsr = 0 + for c in cardsl: + if c[1] == atout: + pointsr += points_atout[c[0]] + else: + pointsr += points[c[0]] + + return winner, pointsr + +def legal_play(played, atout, card, main): + if card not in cards: + return False + if card not in main: + return False + + if played is None : + playedl = [] + playeds = '' + elif isinstance(played, str): + playedl = played.split(',') + playeds = played + else: + playeds = ''.join(played) + playedl = played + + if main is None or main=='': + main = [] + if isinstance(main, str): + mains = main + main = main.split(',') + else: + mains = ','.join(main) + + # Le premier a jouer fait ce qu'il veut + if len(playeds)==0: + return True + couldemandee =playeds[1] + + # On regarde les atouts pour plus tard: + maxpose = -1 + for c in playedl: + if c[1]==atout and points_atout_order[c[0]]>maxpose: + maxpose = points_atout_order[c[0]] + + peutmonter = False + has_atout = False + for m in main: #Peut-on monter ? + if m[1] == atout: + has_atout = True + if points_atout_order[m[0]]>maxpose: + peutmonter = True + break + + # Il faut jouer de la couleur demandée + if couldemandee != atout: + if couldemandee in mains: + if card[1]== couldemandee: + return True + else: + return False + if couldemandee == atout: + # Il faut jouer atout (si possible) + if has_atout: + if card[1] != atout: + return False + # Et monter ! (si possible) + if peutmonter: + if points_atout_order[card[0]]<=maxpose: + return False + # Mais si on a pas, tant pis ! + return True + + # Sinon, mon partenaire est-il maitre ? + if len(playedl)>=2: + maitre, xxx = winner(playedl, atout) + if maitre == len(playedl)-2 : + # Si oui, jeu libre + return True + + # Sinon, ai-je de l'atout ? + if has_atout: + if card[1] != atout: + return False + # Si oui, je dois monter si possible + if peutmonter: + if points_atout_order[card[0]] < maxpose: + return False + return True + + # Sinon, jeu libre ! + return True diff --git a/belote_ws.py b/belote_ws.py new file mode 100644 index 0000000..63c3fd8 --- /dev/null +++ b/belote_ws.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- + +import flask_socketio as fio +from flask_login import current_user +from db import Game, Player, User +from belote_jeu import translator, legal_play, colors, N_PLAYERS, N_TURN + +NAME_ARBITRE = 'Arbitre' +NAME_BONJOUR = 'Portier' + +class CustomNamespacePlay(fio.Namespace): + def __init__(self, id): + self._id = id + self.users = [] + fio.Namespace.__init__(self, '/game/{}'.format(id)) + def on_connect(self): + if current_user is None or not self.game().authorized(current_user): + fio.disconnect() + if current_user.name not in self.users: + if len(self.users)==1: + fio.emit('text', {'text': '{} est là !'.format(self.users[0]), 'username':None, 'name':NAME_BONJOUR}) + elif len(self.users)>1: + fio.emit('text', {'text': '{} est là !'.format(', '.join(self.users)), 'username':None, 'name':NAME_BONJOUR}) + self.users.append(current_user.name) + fio.emit('text', {'text': '{} vient d\'arriver !'.format(current_user.name), 'username':None, 'name':NAME_BONJOUR}, broadcast=True) + pass + + def on_disconnect(self): + if current_user.name in self.users: + self.users.remove(current_user.name) + fio.emit('text', {'text': '{} est parti.'.format(current_user.name), 'username':None, 'name':NAME_BONJOUR}, broadcast=True) + pass + + def on_join(self, data): + pass + + def on_choose_color(self, data): + if 'atout' not in data: + return + atout = data['atout'] + + game = self.game() + if not game.turn<0: + fio.emit('text', {'text': "Choix d'atout indisponible", 'username':None, 'name':NAME_ARBITRE}) + return + # C'est mon tour ? + if game.get_nr(current_user) != (game.turn + game.first_player)%4: + fio.emit('text', {'text': "Ce n'est pas votre tour...", 'username':None, 'name':NAME_ARBITRE}) + return + # Choix légal pour ce tour ? + if atout is not None and game.turn < -4 and game.played[1] != atout: + fio.emit('text', {'text': "Pour l'instant on prend ou pas, mais on ne peut pas choisir l'atout.", 'username':None, 'name':NAME_ARBITRE}) + return + if atout is not None and not atout in colors: + fio.emit('text', {'text': "Couleur d'atout inconnue ({})".format(atout), 'username':None, 'name':NAME_ARBITRE}) + return + # OK ! + print('atout choix') + print(atout) + if(game.tour_choix(atout, current_user)): + print('Emit atout choix') + fio.emit('choose_color', {'turn':game.turn, 'first_player':game.first_player, 'atout':game.atout, 'preneur':game.preneur if game.preneur is not None else current_user.login}, broadcast=True) + else: + fio.emit('text', {'text': "Erreur E0101", 'username':None, 'name':NAME_ARBITRE}) + + def on_play(self, data): + if 'card' not in data: + return + card = data['card'] + + game = self.game() + if game.turn<0: + fio.emit('text', {'text': "Jeu pas encore disponible. Il faut choisir l'atout !", 'username':None, 'name':NAME_ARBITRE}) + return + # C'est mon tour ? + if game.get_nr(current_user) != (game.turn + game.first_player)%4: + fio.emit('text', {'text': "Ce n'est pas votre tour...", 'username':None, 'name':NAME_ARBITRE}) + return + # Choix légal pour ce tour ? + p = game.get_player(current_user) + if legal_play(game.played, game.atout, card, p.cards): + ok, winnr = game.tour_jeu(card, current_user) + if ok: + fio.emit('play', {'turn':game.turn, 'played':game.played.split(',') if game.played is not None and game.played !='' else [], \ + 'last_played':game.last_played.split(',') if game.last_played is not None else [] , 'first_player': game.first_player, 'points':[game.points_0, game.points_1]}, broadcast=True) + else: + fio.emit('text', {'text': "Vous ne pouvez pas jouer cette carte ({})".format(translator(card)), 'username':None, 'name':NAME_ARBITRE}) + return + + def on_text(self, data): + if 'text' in data and len(data['text'])>0: + fio.emit('text', {'text':data['text'], 'username':current_user.login, 'name':current_user.name}, broadcast=True); + + def on_monjeu(self, data): + cards = self.game().get_player(current_user).cards + cards = cards.split(',') if cards is not None and cards != '' else [] + fio.emit('monjeu', {'cards':cards}); + + def on_restart(self, data): + game = self.game() + if game.isadmin(current_user): + if game.turn==N_TURN*N_PLAYERS: + game.restart_jeu() + fio.emit('restart', game.serialize_state_anonymous(), broadcast=True) + else: + fio.emit('text', {'text': "Finissez la partie avant d'en recommencer une.", 'username':None, 'name':NAME_ARBITRE}) + else: + fio.emit('text', {'text': "Demandez au maitre du jeu pour reprendre une partie.", 'username':None, 'name':NAME_ARBITRE}) + + def game(self): + return Game.query.get(self._id) + + +class CustomNamespaceJoin(fio.Namespace): + def __init__(self, id): + self._id = id + fio.Namespace.__init__(self, '/game/{}/join'.format(id)) + def on_connect(self): + pass + + def on_disconnect(self): + pass + + def on_join(self, data): + if self.game().can_join(current_user): + if not self.game().authorized(current_user): + self.game().join(current_user) + fio.emit('join', {'username':current_user.login, 'name': current_user.name}, broadcast=True) + + def on_add_player(self, data): + if self.game().isadmin(current_user): + if 'username' in data: + user = User.query.get(data['username']) + if user is not None and self.game().can_join(user): + if not self.game().authorized(user): + self.game().join(user) + fio.emit('join', {'username':user.login, 'name': user.name}, broadcast=True) + + def on_leave(self, data): + if self.game().leave(current_user): + fio.emit('leave', {'username':current_user.login}, broadcast=True) + + def on_ban(self, data): + if 'username' in data: + username = data['username'] + if self.game().isadmin(current_user): + if 'username' is not None: + if self.game().leave(username): + fio.emit('leave', {'username':username}) + + def on_fixplayers(self, data): + if self.game().isadmin(current_user): + if self.game().can_start(): + self.game().fix_players() + fio.emit('fixplayers', {}) + + def on_start(self, data): + if 'order' in data: + order = data['order'] + else: + order = None + if self.game().isadmin(current_user): + if self.game().start_game(order): + fio.emit('start', {}, broadcast=True) + + def game(self): + return Game.query.get(self._id) + diff --git a/db.py b/db.py new file mode 100644 index 0000000..03012bf --- /dev/null +++ b/db.py @@ -0,0 +1,339 @@ +from flask_login import UserMixin +from werkzeug.security import generate_password_hash, check_password_hash +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy.orm import relationship +import random +from belote_jeu import cards, N_PLAYERS, colors, winner, N_TURN + +db = SQLAlchemy() + +levels_users = {'Inactive':0, 'Simple user':1, 'Can create games':2, 'Can manage simple users': 8, 'Can manage games':9, 'Admin':10} + +####################################################################### +# User class # +####################################################################### + +class User(UserMixin, db.Model): + """Model for user accounts.""" + + __tablename__ = 'users' + + login = db.Column(db.String(200), + primary_key=True) + name = db.Column(db.String(200), + nullable=False, + unique=False) + password = db.Column(db.String(200), + primary_key=False, + unique=False, + nullable=False) + level = db.Column(db.Integer, default=1) + last_login = db.Column(db.DateTime) + + def set_password(self, password): + """Create hashed password.""" + self.password = generate_password_hash(password, method='sha256') + + def check_password(self, password): + """Check hashed password.""" + return check_password_hash(self.password, password) + + def get_id(self): + return self.login + + def __repr__(self): + return ''.format(self.login) + +####################################################################### +# Game class # +####################################################################### + +class Game(db.Model): + __tablename__ = 'games' + + id = db.Column(db.Integer, + primary_key=True) + name = db.Column(db.String(200)) + players = relationship("Player") + turn = db.Column(db.Integer, default=-8) + atout = db.Column(db.String(1)) + preneur = db.Column(db.Integer) + admin = db.Column(db.String(200), db.ForeignKey('users.login')) + cards_to_distribute = db.Column(db.String(200)) + first_player = db.Column(db.Integer) + start = db.Column(db.Boolean, default=False) + last_played = db.Column(db.String(20)) + played = db.Column(db.String(20)) + fixplayers = db.Column(db.Boolean, default=False) + points_0 = db.Column(db.Integer, default=0) + points_1 = db.Column(db.Integer, default=0) + cumul_0 = db.Column(db.Integer, default=0) + cumul_1 = db.Column(db.Integer, default=0) + partie = db.Column(db.Integer, default=1) + + def start_game(self, ordered_players): + # Ordering players + if self.fixplayers and not self.start: + i=0 + if ordered_players is not None: + for op in ordered_players: + player = Player.query.filter_by(game=self.id, user=op).first() + if player is not None: + player.nr = i + i+=1 + for p in self.players: + if p.nr is None or p.nr<0: + p.nr = i + i+=1 + db.session.commit() + + self.start = True + self.first_player = 0 + self.distribute() + db.session.commit() + return True + return False + + def fix_players(self): + if self.can_start(): + self.fixplayers = True + db.session.commit() + + def distribute(self): + ___cartes = random.sample(cards, k=len(cards)) + for i in range(len(self.players)): + self.players[i].cards = ','.join(___cartes[5*i:5*(i+1)]) + self.played = ___cartes[5*(i+1)] + self.cards_to_distribute = ','.join(___cartes[5*(i+1)+1:]) + + def authorized(self, user): + for p in self.players: + if user.login == p.user: + return True + return False + + def isadmin(self, user): + return user.login == self.admin + + def serialize_state_anonymous(self): + r = {} + r['atout'] = self.atout + r['preneur'] = self.preneur + r['last_played'] = self.last_played.split(',') if self.last_played is not None else [] + r['played'] = self.played.split(',') if self.played is not None else [] + r['turn'] = self.turn + r['first_player'] = self.first_player + r['points'] = [self.points_0, self.points_1] + r['cumul'] = [self.cumul_0, self.cumul_1] + r['partie'] = self.partie + return r + + def serialize_state(self, user): + r = self.serialize_state_anonymous() + r['players'] = self.get_ordered_players() + r['playersinfo'] = {} + for p in self.players: + r['playersinfo'][p.user] = User.query.get(p.user).name + r['nr'] = self.get_nr(user) + r['admin'] = self.admin + cards = self.get_player(user).cards + r['cards'] = cards.split(',') if cards is not None else [] + return r + + def get_ordered_players(self): + r = {} + for p in self.players: + r[p.nr] = p.user + r2 = [] + for i in range(N_PLAYERS): + r2.append(r[i] if i in r else None) + return r2 + + def get_nr(self, user): + p = self.get_player(user) + if p is not None : + return p.nr + else: + return -1 + + def get_player(self, user): + if not isinstance(user, str): + user = user.login + return Player.query.filter_by(game=self.id, user=user).first() + + def can_join(self, user): + if self.authorized(user): + return True + if not self.fixplayers and len(self.players) < N_PLAYERS: + return True + return False + + def can_start(self): + if len(self.players)==N_PLAYERS: + return True + return False + + def join(self, user): + if not self.fixplayers and not self.authorized(user): + p = Player(game=self.id, user=user.login) + db.session.add(p) + db.session.commit() + + def leave(self, user): + if not self.fixplayers: + for p in self.players: + if isinstance(user, str) and user != self.admin or hasattr(user, 'login') and user.login != self.admin: + if isinstance(user, str) and user == p.user or hasattr(user, 'login') and user.login == p.user: + db.session.delete(p) + db.session.commit() + return True + return False + + def get_players(self): + r = [] + for p in self.players: + user = User.query.get(p.user) + r.append({'username':user.login, 'name':user.name}) + return r + + def tour_choix(self, atout, user): + if (self.turn + self.first_player) % N_PLAYERS == self.get_nr(user): + self.turn += 1 + if atout is not None and atout in colors: + self.atout = atout + self.preneur = user.login + self.turn = 0 + self.distribute_restant() + db.session.commit() + return True + return False + + def distribute_restant(self): + todistribute = self.cards_to_distribute.split(',') + for pn in self.get_ordered_players(): + p = self.get_player(pn) + if pn == self.preneur: + p.cards += ',' + ','.join(todistribute[:2]) + p.cards += ',' + self.played + todistribute = todistribute[2:] + else: + p.cards += ',' + ','.join(todistribute[:3]) + todistribute = todistribute[3:] + self.cards_to_distribute = '' + self.played = None + + def tour_jeu(self, carte, user): + if (self.turn + self.first_player) % N_PLAYERS == self.get_nr(user): + winnr = -1 + self.turn += 1 + if self.played is None or self.played == '': + self.played = carte + else: + self.played += ',' + carte + if self.turn % N_PLAYERS == 0 : + winnr, points = winner(self.played, self.atout) + if (self.first_player + winnr)%2 == 0: + self.points_0 += points + else: + self.points_1 += points + self.last_played = self.played + if len(self.cards_to_distribute)>1: + self.cards_to_distribute += ',' + self.cards_to_distribute += self.played + self.played = None + self.first_player = (self.first_player + winnr)%4 + if self.turn == N_TURN*N_PLAYERS: # Dix de der, chute, belote et capote ! + if (self.first_player + winnr)%2 == 0: + self.points_0 += 10 + else: + self.points_1 += 10 + if self.get_player(self.preneur).nr %2 ==0 and self.points_0 < self.points_1: + self.points_0 = 0 + self.points_1 = 162 + if self.get_player(self.preneur).nr %2 ==1 and self.points_1 < self.points_0: + self.points_0 = 162 + self.points_1 = 0 + if self.points_0 == 162 or self.points_1 == 162: + winequipe = 0 if self.points_0 == 162 else 1 + # Capote possible. + winnr = 0 + ccc = self.cards_to_distribute.split(',') + for i in range(N_TURN): + win, xxx = winner(ccc[i*N_PLAYERS:(i+1)*N_PLAYERS], self.atout) + winnr = win+winnr + if winnr % 2 != winequipe: + break + else: + if winequipe == 0: + self.points_0 += 90 + else: + self.points_1 += 90 + # belote possible + winnr = 0 + belote =-1 + ccc = self.cards_to_distribute.split(',') + for i in range(N_TURN): + cccc = ccc[i*N_PLAYERS:(i+1)*N_PLAYERS] + if 'R'+self.atout in cccc: + if belote>=0: + if (cccc.index('R'+self.atout) + winnr)%4 == belote %4: + if belote %2 ==0: + self.points_0 += 20 + else: + self.points_1 += 20 + else: + belote = winnr + cccc.index('R'+self.atout) + if 'D'+self.atout in cccc: + if belote>=0: + if (cccc.index('D'+self.atout) + winnr)%4 == belote %4: + if belote %2 ==0: + self.points_0 += 20 + else: + self.points_1 += 20 + else: + belote = winnr + cccc.index('D'+self.atout) + win, xxx = winner(cccc, self.atout) + winnr = win+winnr + # Retirer la carte du jeu ! + p = self.get_player(user) + ccc = p.cards + ccc = ccc.split(',') + ccc.remove(carte) + p.cards = ','.join(ccc) + db.session.commit() + return True, winnr + return False, -1 + + def restart_jeu(self): + if self.turn==N_TURN*N_PLAYERS: + self.turn = -8 + self.atout = None + self.preneur = None + self.last_played = None + self.played = None + self.cumul_0 += self.points_0 + self.cumul_1 += self.points_1 + self.points_0 = 0 + self.points_1 = 0 + self.partie +=1 + self.first_player = 0 + self.distribute() + db.session.commit() + return True + return False + +####################################################################### +# Player class # +####################################################################### + +class Player(db.Model): + __tablename__ = 'players' + + id = db.Column(db.Integer, + primary_key=True) + game = db.Column(db.Integer, db.ForeignKey('games.id')) + user = db.Column(db.Integer, db.ForeignKey('users.login')) + cards = db.Column(db.String(40)) + nr = db.Column(db.Integer, default=-1) + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7d18817 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +Flask==1.1.2 +Flask-Login==0.5.0 +Flask-SocketIO==4.2.1 +Flask-SQLAlchemy==2.4.1 +SQLAlchemy==1.3.16 +Werkzeug==1.0.1 +eventlet==0.25.2 diff --git a/settings.exemple.cfg b/settings.exemple.cfg new file mode 100644 index 0000000..0f8f842 --- /dev/null +++ b/settings.exemple.cfg @@ -0,0 +1,9 @@ +# App configuration file +# copy this file to settings.cfg + +# Generate a secure key with +# python -c "import os, base64; print(base64.b64encode(os.urandom(24)))" +SECRET_KEY = "to-be-changed" + +SQLALCHEMY_DATABASE_URI = 'sqlite:///db.sqlite' +SQLALCHEMY_TRACK_MODIFICATIONS = False diff --git a/static/belote-script.js b/static/belote-script.js new file mode 100644 index 0000000..c36ebbf --- /dev/null +++ b/static/belote-script.js @@ -0,0 +1,73 @@ +/************************* + la Belote +**************************/ +var go = function(url) { + document.location.href=url; +} + +$(document).ready(function() { +/*============================== +listeners à activer à ready +===============================*/ + +document.getElementById("username").addEventListener("click",function() {apparaitDisparaitMenu('username-items','username-fleche')}); + +var options = document.getElementById("options"); +if(options){ + options.addEventListener("click",function() {apparaitDisparaitAccordeon('options-valeur','options-fleche')}); +} +var derpli = document.getElementById("dernier-pli") +if(derpli){ + derpli.addEventListener("click",function() {apparaitDisparaitAccordeon('dernier-pli-valeur','dernier-pli-fleche')}); +} + + +/*============================== +fonctions +===============================*/ +/* +fonction apparaitDisparaitUserAttr +apparition/disparition d'un menu +*/ +function apparaitDisparaitMenu (idMenu,idFleche) { + var element = document.getElementById(idMenu); + if(element.style.visibility == "hidden" || element.style.visibility == "") { + document.getElementById(idMenu).style.visibility = "visible"; + } + else { + document.getElementById(idMenu).style.visibility = "hidden"; + }; + var elementFleche = document.getElementById(idFleche); + var splitElement = elementFleche.src.split('/'); + var lastElement = splitElement[splitElement.length-1]; + if(lastElement == "fleche-bas.gif") { + document.getElementById(idFleche).src = "/static/fleche-haut.gif"; + } + else { + document.getElementById(idFleche).src = "/static/fleche-bas.gif"; + } + return(0); +} + +function apparaitDisparaitAccordeon (idZone,idFleche) { + var element = document.getElementById(idZone); + if(element.style.display == "none" || element.style.display == "") { + document.getElementById(idZone).style.display = "block"; + } + else { + document.getElementById(idZone).style.display = "none"; + }; + var elementFleche = document.getElementById(idFleche); + var splitElement = elementFleche.src.split('/'); + var lastElement = splitElement[splitElement.length-1]; + if(lastElement == "fleche-bas.gif") { + document.getElementById(idFleche).src = "/static/fleche-haut.gif"; + } + else { + document.getElementById(idFleche).src = "/static/fleche-bas.gif"; + } + return(0); +} + +/* fin de $(document).ready() */ +}); diff --git a/static/belote.css b/static/belote.css new file mode 100644 index 0000000..4cc6aa1 --- /dev/null +++ b/static/belote.css @@ -0,0 +1,391 @@ +/* ========================= + la Belote +========================= */ +/****************/ +/* Reset */ +/****************/ +html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;vertical-align:baseline;background:transparent} +body{line-height:1} +ol,ul{list-style:none} +blockquote,q{quotes:none} +blockquote:before,blockquote:after,q:before,q:after{content:'';content:none} +:focus{outline:0} +ins{text-decoration:none} +del{text-decoration:line-through} +table{border-collapse:collapse;border-spacing:0} +/****************/ +/* généralités */ +/****************/ +body { + margin: 0px; + background-color: #13995E; +} +div.clear { + clear: both; +} +div.clearfix-head::after { + content:""; + clear: both; + display: table; + border: 1px solid black; + width: 100%; +} +div.clearfix::after{ + content:""; + clear: both; + display: table; +} +div, p, li, a, th, td { + font-family: 'Segoe UI', Arial, Helvetica, sans-serif; + font-size: 15px; + line-height: 17px; + color: #092E1E; +} +/****************/ +/* conteneur */ +/****************/ +#conteneur { + margin-top: 8px; + margin-right: auto; + margin-bottom: 0px; + margin-left: auto; + height: 600px; + width: 1200px; + /* border: 1px solid #092E1E; */ +} +/****************/ +/* entete */ +/****************/ +#entete { + position: relative; + height: 66px; + margin: 2px; +/* + border: 1px solid #092E1E; +*/ +} +#logo { + margin: 2px 10px 2px 2px; + float: left; +} +#logo img { + height: 60px; + width: 60px; + display: block; + margin-left: auto; + margin-right: auto; +} +#titre { + width: 55%; + line-height: 60px; + margin: 2px 15px 2px 2px; + text-align: center; + font-size: 140%; + font-weight: bold; + float: left; +} +#username { + width: 20%; + height: 60px; + line-height: 60px; + margin: 2px 15px 2px 30px; + float: right; + + font-size: 140%; + font-weight: bold; + text-align: right; +} +#username-fleche { + height: 14px; + width: 14px; + cursor: pointer; + padding-left: 10px; + vertical-align: baseline; +} +#username-items { + position: absolute; + top: 68px; + right: 0px; + width: 180px; + visibility: hidden; + margin-top: 2px ; + padding: 5px; + border: 1px solid #092E1E; + overflow: hidden; + z-index: 100; + background-color: #b7a86b; +} +.menu-item:hover { + cursor: pointer; + background-color: #15A160; +} + +/****************/ +/* zone-de-contenu */ +/****************/ +#zone-de-contenu { + width:50%; + margin:auto; +} +#zone-de-contenu>div { + margin:auto; +} +.tablelist { + width:100%; +} +/****************/ +/* zone-de-jeu */ +/****************/ +#zone-de-jeu { + height: 438px; + margin: 2px; +/* + border: 1px solid #092E1E; +*/ +} +#plateau { + height: 320px; + width: 740px; + position: relative; + margin: 3px; +} +#joueur-0 { + position: absolute; + top: 55px; + left: 190px; + text-align: right; +} +#joueur-1 { + position: absolute; + top: 145px; + left: 80px; + text-align: right; +} +#joueur-2 { + position: absolute; + top: 245px; + left: 190px; + text-align: right; +} +#joueur-3 { + position: absolute; + top: 145px; + left: 500px; +} +#jeu-0 { + position: absolute; + top: 30px; + left: 300px; +} +#jeu-1 { + position: absolute; + top: 120px; + left: 190px; +} +#jeu-2 { + position: absolute; + top: 220px; + left: 300px; +} +#jeu-3 { + position: absolute; + top: 120px; + left: 420px; +} +#suivant { +position: absolute; + bottom: 15px; + right: 90px; +} +#suivant input { + width: 80px; +} + +#ov { + height: 310px; + width: 390px; + padding: 5px 25px 5px 25px; + position: relative; + top: -322px; + left: 746px; + margin: 2px; + border: 1px solid #092E1E; + +} +#table-atout { + width: 98%; +} +#table-atout td { + padding: 4px; + text-align: center; + vertical-align: middle; +} +#atout { + margin: 0px auto 0px auto; +} +#choix { + height: 160px; + margin-top: 25px; + padding: 6px; + border: 1px solid #092E1E; + overflow-x: hidden; + overflow-y: auto; +} +#options-fleche { + width: 11px; + height: 11px; + float: right; + cursor: pointer; + vertical-align: baseline; +} +#options-valeur { + display: none; +} +#dernier-pli-fleche { + width: 11px; + height: 11px; + float: right; + cursor: pointer; + vertical-align: baseline; +} +#dernier-pli-valeur { + display: none; +} +#dernier-pli { + margin-top: 10px; +} +#dernier-pli-valeur { + padding-left: 50px; +} +#jeu-en-main { + height: 76px; + width: 642px; + position: relative; + top: -320px; + left: 0px; + margin: 2px; + padding: 15px 58px 15px 40px; + border: 1px solid #092E1E; +} +#a-vous { + float: left; + width: 90px; + height: 79px; + padding-top: 20px; + text-align: center; + margin-right: 20px; + visibility: hidden; +} +#points { + height: 98px; + width: 390px; + position: relative; + top: -430px; + left: 746px; + margin: 2px; + border: 1px solid #092E1E; + padding: 4px 25px 4px 25px; +} +#points p { + text-align: center; + margin: 5px 0px 5px 0px; +} +#points table { + width: 98%; + border-collapse: collapse; + border: 1px solid #092E1E; +} +#points table td { + padding: 0px 2px 0px 2px; + text-align: center; + border: 1px solid #092E1E; +} +.joueur { + width: 90px; + height: 18px; + overflow: hidden; + text-decoration: underscore; +} +.carte { + height: 77px; + width: 50px; + /* carte : hauteur = 1,54 * largueur; */ + border: 1px solid #092E1E; + background-color: #117F4A; +} +.carte-en-main, .carte-dernier-pli { + float: left; + margin-right: 12px; +} +.item-choix { + padding: 4px; + border: 1px solid #092E1E; +} +.item-choix-valeur { + padding: 4px; + border-right: 1px solid #092E1E; + border-bottom: 1px solid #092E1E; + border-left: 1px solid #092E1E; +} +/************************/ +/* zone-de-conversation */ +/************************/ +#zone-de-conversation { + height: 84px; + margin: 2px; +/* + border: 1px solid #092E1E; +*/ + position: relative; +} +#chat { + width: 61%; + height: 87%; + float: left; + background-color: #C4E8D7; + margin: 2px 15px 2px 2px; + border-top: 2px solid #888; + border-right: 2px solid #DDD; + border-bottom: 2px solid #DDD; + border-left: 2px solid #888; + position: absolute; + overflow-x: hidden; + overflow-y: auto; +} +#message { + width: 34%; + float: right; + margin: 1px 15px 2px 2px; + position: absolute; + right: 2px; + top: 3px; +} +#message p { + padding-bottom: 4px; +} +#texte-msg { + width:80%; + background-color: #C4E8D7; + margin-right: 5px; +} +button#add{ + min-width:180px; + margin:auto; + padding:10px; + background: #b7a86b; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + border-radius: 10px; + display:block; +} + +thead{ + font-weight: bold; +} + +fieldset.fieldset { + border:1px solid green; + padding:30px; + margin:20px; +} diff --git a/static/cards/0C.png b/static/cards/0C.png new file mode 100644 index 0000000..5efbcf1 Binary files /dev/null and b/static/cards/0C.png differ diff --git a/static/cards/0F.png b/static/cards/0F.png new file mode 100644 index 0000000..617a9b6 Binary files /dev/null and b/static/cards/0F.png differ diff --git a/static/cards/0P.png b/static/cards/0P.png new file mode 100644 index 0000000..b230ad2 Binary files /dev/null and b/static/cards/0P.png differ diff --git a/static/cards/0T.png b/static/cards/0T.png new file mode 100644 index 0000000..9c4e1ac Binary files /dev/null and b/static/cards/0T.png differ diff --git a/static/cards/7C.png b/static/cards/7C.png new file mode 100644 index 0000000..852f92c Binary files /dev/null and b/static/cards/7C.png differ diff --git a/static/cards/7F.png b/static/cards/7F.png new file mode 100644 index 0000000..5f8ae79 Binary files /dev/null and b/static/cards/7F.png differ diff --git a/static/cards/7P.png b/static/cards/7P.png new file mode 100644 index 0000000..da7d099 Binary files /dev/null and b/static/cards/7P.png differ diff --git a/static/cards/7T.png b/static/cards/7T.png new file mode 100644 index 0000000..317c38d Binary files /dev/null and b/static/cards/7T.png differ diff --git a/static/cards/8C.png b/static/cards/8C.png new file mode 100644 index 0000000..d584d6e Binary files /dev/null and b/static/cards/8C.png differ diff --git a/static/cards/8F.png b/static/cards/8F.png new file mode 100644 index 0000000..1fe3158 Binary files /dev/null and b/static/cards/8F.png differ diff --git a/static/cards/8P.png b/static/cards/8P.png new file mode 100644 index 0000000..ac76c90 Binary files /dev/null and b/static/cards/8P.png differ diff --git a/static/cards/8T.png b/static/cards/8T.png new file mode 100644 index 0000000..08f5c0e Binary files /dev/null and b/static/cards/8T.png differ diff --git a/static/cards/9C.png b/static/cards/9C.png new file mode 100644 index 0000000..5513ca8 Binary files /dev/null and b/static/cards/9C.png differ diff --git a/static/cards/9F.png b/static/cards/9F.png new file mode 100644 index 0000000..a24e1ea Binary files /dev/null and b/static/cards/9F.png differ diff --git a/static/cards/9P.png b/static/cards/9P.png new file mode 100644 index 0000000..de862e6 Binary files /dev/null and b/static/cards/9P.png differ diff --git a/static/cards/9T.png b/static/cards/9T.png new file mode 100644 index 0000000..f56a4f7 Binary files /dev/null and b/static/cards/9T.png differ diff --git a/static/cards/AC.png b/static/cards/AC.png new file mode 100644 index 0000000..85f37e0 Binary files /dev/null and b/static/cards/AC.png differ diff --git a/static/cards/AF.png b/static/cards/AF.png new file mode 100644 index 0000000..616062c Binary files /dev/null and b/static/cards/AF.png differ diff --git a/static/cards/AP.png b/static/cards/AP.png new file mode 100644 index 0000000..27d16eb Binary files /dev/null and b/static/cards/AP.png differ diff --git a/static/cards/AT.png b/static/cards/AT.png new file mode 100644 index 0000000..58d38da Binary files /dev/null and b/static/cards/AT.png differ diff --git a/static/cards/C.png b/static/cards/C.png new file mode 100644 index 0000000..125d94e Binary files /dev/null and b/static/cards/C.png differ diff --git a/static/cards/DC.png b/static/cards/DC.png new file mode 100644 index 0000000..10abed9 Binary files /dev/null and b/static/cards/DC.png differ diff --git a/static/cards/DF.png b/static/cards/DF.png new file mode 100644 index 0000000..b403c64 Binary files /dev/null and b/static/cards/DF.png differ diff --git a/static/cards/DP.png b/static/cards/DP.png new file mode 100644 index 0000000..12ee2a6 Binary files /dev/null and b/static/cards/DP.png differ diff --git a/static/cards/DT.png b/static/cards/DT.png new file mode 100644 index 0000000..b5f03ef Binary files /dev/null and b/static/cards/DT.png differ diff --git a/static/cards/F.png b/static/cards/F.png new file mode 100644 index 0000000..494dc8a Binary files /dev/null and b/static/cards/F.png differ diff --git a/static/cards/P.png b/static/cards/P.png new file mode 100644 index 0000000..db9d3c3 Binary files /dev/null and b/static/cards/P.png differ diff --git a/static/cards/RC.png b/static/cards/RC.png new file mode 100644 index 0000000..3a41c5e Binary files /dev/null and b/static/cards/RC.png differ diff --git a/static/cards/RF.png b/static/cards/RF.png new file mode 100644 index 0000000..394134c Binary files /dev/null and b/static/cards/RF.png differ diff --git a/static/cards/RP.png b/static/cards/RP.png new file mode 100644 index 0000000..363c236 Binary files /dev/null and b/static/cards/RP.png differ diff --git a/static/cards/RT.png b/static/cards/RT.png new file mode 100644 index 0000000..451dd4b Binary files /dev/null and b/static/cards/RT.png differ diff --git a/static/cards/T.png b/static/cards/T.png new file mode 100644 index 0000000..6d5ac65 Binary files /dev/null and b/static/cards/T.png differ diff --git a/static/cards/VC.png b/static/cards/VC.png new file mode 100644 index 0000000..fdb8dfc Binary files /dev/null and b/static/cards/VC.png differ diff --git a/static/cards/VF.png b/static/cards/VF.png new file mode 100644 index 0000000..3b2bab2 Binary files /dev/null and b/static/cards/VF.png differ diff --git a/static/cards/VP.png b/static/cards/VP.png new file mode 100644 index 0000000..1560ec8 Binary files /dev/null and b/static/cards/VP.png differ diff --git a/static/cards/VT.png b/static/cards/VT.png new file mode 100644 index 0000000..ef09cd2 Binary files /dev/null and b/static/cards/VT.png differ diff --git a/static/croix.png b/static/croix.png new file mode 100644 index 0000000..00f7d5e Binary files /dev/null and b/static/croix.png differ diff --git a/static/edit.png b/static/edit.png new file mode 100644 index 0000000..2d7cca2 Binary files /dev/null and b/static/edit.png differ diff --git a/static/fleche-bas.gif b/static/fleche-bas.gif new file mode 100644 index 0000000..8de7b46 Binary files /dev/null and b/static/fleche-bas.gif differ diff --git a/static/fleche-haut.gif b/static/fleche-haut.gif new file mode 100644 index 0000000..5b6ad65 Binary files /dev/null and b/static/fleche-haut.gif differ diff --git a/static/game_script.js b/static/game_script.js new file mode 100644 index 0000000..b73a436 --- /dev/null +++ b/static/game_script.js @@ -0,0 +1,262 @@ +var socket = null; +var sendtext = function(){ + var text = $('#texte-msg').val(); + if (text.length > 1){ + $('#texte-msg').val(''); + socket.emit('text', {'text':text}); + } +}; + +var choixcouleur = function(coul){ + socket.emit('choose_color', {'atout':coul}); +} + +var jouer = function(carte, no){ + socket.emit('play', {'card':carte}); +} +var restart_jeu = function(){ + socket.emit('restart', {}); +} + + +$.fn.pressEnter = function (fnc) { + return this.each(function () { + $(this).keypress(function (ev) { + var keycode = (ev.keyCode ? ev.keyCode : ev.which); + if (keycode == '13') { + fnc.call(this, ev); + } + }) + }) +} + +var set_points = function(){ + $('#nous-tot').html( jeu['cumul'][jeu['nr']%2] ); + $('#nous-part').html(jeu['points'][jeu['nr']%2] ); + $('#eux-tot').html( jeu['cumul'][(jeu['nr']+1)%2] ); + $('#eux-part').html( jeu['points'][(jeu['nr']+1)%2]); + $('#nopart').html('Partie '+jeu['partie']); +} + +var set_atout = function(){ + if(jeu['atout'] == null || jeu['atout']==''){ + var coulprop = jeu['played'][0]; + $('#atout').html(``); + if (jeu['turn']<-4 && (jeu['turn']+jeu['first_player']+8)%4 == jeu['nr']){ + $('#prispar').html(``); + $('#preneur').html(``); + }else if((jeu['turn']+jeu['first_player']+8)%4 == jeu['nr']){ + $('#prispar').html(` + +
+ + + `); + $('#preneur').html(``); + }else{ + $('#prispar').html(`Pris par`); + $('#preneur').html(`----`); + } + }else{ + $('#atout').html(``); + $('#prispar').html(`Pris par`); + $('#preneur').html(`${jeu['playersinfo'][jeu['preneur']]}`); + } +} + +jeu['monjeu'] = Array.from(jeu['cards']); +var order = ['A', '0', 'R', 'D', 'V', '9', '8', '7']; +var order_atout = ['V', '9', 'A', '0', 'R', 'D', '8', '7']; +var set_jeu = function(){ + var order_colors = ['P', 'C', 'T', 'F']; + if (jeu['atout'] == 'C'){ + order_colors = ['C', 'T', 'F', 'P']; + }else if (jeu['atout'] == 'T'){ + order_colors = ['T', 'C', 'P', 'F']; + }else if (jeu['atout'] == 'F'){ + order_colors = ['F', 'T', 'C', 'P']; + } + jeu['monjeu'].sort(function(a,b){ + if (a[1]==jeu['atout']){ + x = order_atout.indexOf(a[0]); + }else{ + x = order.indexOf(a[0]); + } + if (b[1]==jeu['atout']){ + y = order_atout.indexOf(b[0]); + }else{ + y = order.indexOf(b[0]); + } + return x+15*order_colors.indexOf(a[1]) > y+15*order_colors.indexOf(b[1]) ? 1 : -1; + }); + console.log(jeu['monjeu']); + + for(i=0;i`); + } + } +} + +var set_cartes = function(){ + first_player = jeu['first_player'] + if (jeu['turn']%4==0 && 'lastfirstplayer' in jeu){ + first_player = jeu['lastfirstplayer'] + } + if (jeu['preneur'] == null){ + $('#jeu-1').html(''); + $('#jeu-2').html(''); + $('#jeu-3').html(''); + $('#jeu-4').html(''); + }else{ + for(i=first_player;i j && jeu['played'][j]!= ''){ + $('#jeu-'+ k).html(``); + }else{ + $('#jeu-'+ k).html(''); + } + } + } +} + +var set_derpli = function(){ + if(jeu['last_played'].length >0){ + for(i=0;i<4;i++){ + $('#der-pli-'+i).html(``); + } + } +} + +var set_players = function(){ + for(i=0;i<4;i++){ + $('#joueur-'+i).html(`${jeu['playersinfo'][jeu['players'][i]]}`); + } +} + +var set_current_player = function(){ + for(i=0;i<4;i++){ + $('#joueur-'+i).css('font-weight', 'normal'); + } + i = (jeu['turn']+jeu['first_player']+8)%4; + $('#joueur-'+i).css('font-weight', 'bold'); + if((jeu['turn']+jeu['first_player']+8)%4 == jeu['nr']){ + $('#a-vous').css('visibility', 'visible'); + }else{ + $('#a-vous').css('visibility', 'hidden'); + } + + if(jeu['turn']==32 && jeu['admin']==jeu['players'][jeu['nr']]){ + $('#suivant').show(); + } +} + + +$(document).ready(function() { +socket = io(window.location.pathname); +$('#suivant').hide() +set_points(); +set_atout(); +set_jeu(); +set_cartes(); +set_derpli(); +set_players(); +set_current_player(); + +for (e in jeu){ + $('#jeu').append(`${e} : ${jeu[e]}
`) +} +if((jeu['turn']+jeu['first_player'] + 8)%4 == jeu['nr']){ + $('#a-vous').css('visibility', 'visible'); +} + + +socket.on('connect', function() { + socket.emit('join', {data: 'I\'m connected!'}); +}); + +socket.on('text', function(data) { + textr = data['text']; + name = data['name']; + $('#chat').prepend(`${name} : ${textr}
`); +}); + +socket.on('restart', function(data) { + for(d in data){ + jeu[d] = data[d] + } + set_points(); + set_atout(); + set_cartes(); + set_derpli(); + set_players(); + set_current_player(); + socket.emit('monjeu', {}); +}); + +socket.on('choose_color', function(data){ + console.log(data) + jeu['turn'] = data['turn'] + jeu['preneur'] = data['preneur'] + jeu['atout'] = data['atout'] + set_atout(); + set_current_player(); + if(jeu['turn']>=0){ + socket.emit('monjeu', {}); + } +}); + +socket.on('monjeu', function(data){ + jeu['cards'] = data['cards']; + jeu['monjeu'] = Array.from(data['cards']); + set_jeu(); +}); + +socket.on('play', function(data){ + console.log(data) + jeu['turn'] = data['turn']; + jeu['lastfirstplayer'] = jeu['first_player'] + jeu['first_player'] = data['first_player']; + jeu['last_played'] = data['last_played']; + jeu['points'] = data['points']; + if(data['played'] == null || data['played'].length == 0){ + jeu['played'] = data['last_played']; + console.log(jeu['played']) + set_cartes(); + set_derpli(); + set_points(); + } + else{ + jeu['played'] = data['played']; + console.log(jeu['played']) + set_cartes(); + set_points(); + } + var first_player = jeu['first_player']; + if (jeu['turn']%4==0 && 'lastfirstplayer' in jeu){ + first_player = jeu['lastfirstplayer'] + } + if((jeu['turn']+first_player+7)%4 == jeu['nr']){ + console.log("Je viens de jouer") + i = jeu['monjeu'].indexOf(jeu['played'][jeu['played'].length-1]); + if (jeu['played'].length>0 && i >=0){ + console.log(i) + jeu['monjeu'][i] = null; + $('#carte-'+i).html('') + } + + } + set_current_player(); +}); + + +$('#texte-msg').pressEnter(function(e){ + sendtext(); +}); + + + +}); diff --git a/static/logo.gif b/static/logo.gif new file mode 100644 index 0000000..b8f3ab9 Binary files /dev/null and b/static/logo.gif differ diff --git a/static/ok.png b/static/ok.png new file mode 100644 index 0000000..476318f Binary files /dev/null and b/static/ok.png differ diff --git a/static/plus.png b/static/plus.png new file mode 100644 index 0000000..6d2cba7 Binary files /dev/null and b/static/plus.png differ diff --git a/templates/admin_game_add.html b/templates/admin_game_add.html new file mode 100644 index 0000000..5d22130 --- /dev/null +++ b/templates/admin_game_add.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} + +{% block title %} Créer jeu {% endblock %} + +{% block titre %} Créer un jeu {% endblock %} + +{% block contenu %} +
+
+ +
Nom du jeu + {% for message in get_flashed_messages() %} +
+ + {{ message }} +
+ {% endfor %} +
+ Nom du jeu :
+ Maitre du jeu :
+ +
+
+
+ +{% endblock %} + + diff --git a/templates/admin_games.html b/templates/admin_games.html new file mode 100644 index 0000000..c62bc6e --- /dev/null +++ b/templates/admin_games.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block title %} Administration {% endblock %} + +{% block titre %}Administration : jeux {% endblock %} + +{% block contenu %} +
+
Ajouter un utilisateur + +
+
Liste des jeux + + + + + {% for g in games %} + + + + {% endfor %} +
IdNamePartieJoueursStatusActions
{{ g.id }}{{ g.name }}{{ g.partie }}{% for p in g.players %}{{ p.user }} {% endfor %}{% if g.start %} Started {% elif g.fixplayers %}Players set{% else %}Join{% endif %}Voir Del
+
+
+{% endblock %} diff --git a/templates/admin_user_del.html b/templates/admin_user_del.html new file mode 100644 index 0000000..9212b80 --- /dev/null +++ b/templates/admin_user_del.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block title %} Jeux {% endblock %} + +{% block titre %} Suppression utilisateur {{ useredit.name }} {% endblock %} + +{% block contenu %} +
+
Confirmation +

Sur de vouloir supprimer l'utilisateur {{ useredit.name }} ({{useredit.login}}) ?

+
+ + +
Oui Non
+
+
+{% endblock %} diff --git a/templates/admin_user_edit.html b/templates/admin_user_edit.html new file mode 100644 index 0000000..ef2d28d --- /dev/null +++ b/templates/admin_user_edit.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} + +{% block title %} Administration {% endblock %} + +{% block titre %} Administration : Éditer un utilisateur {% endblock %} + +{% block contenu %} +
+
+
+ +
Utilisateur + {% for message in get_flashed_messages() %} +
+ + {{ message }} +
+ {% endfor %} + + + + + + +
Nom d'utilisateur :
Nom complet :
Mot de passe :
Droits : +
 
+
+
+
+ +{% endblock %} + diff --git a/templates/admin_users.html b/templates/admin_users.html new file mode 100644 index 0000000..213cc8d --- /dev/null +++ b/templates/admin_users.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} + +{% block title %} Administration {% endblock %} + +{% block titre %}Administration : utilisateurs {% endblock %} + +{% block contenu %} +
+
Ajouter un utilisateur + +
+
Liste des utilisateurs + + + + + {% for u in users %} + + {% endfor %} +
LoginNameLevelLast loginActions
{{ u.login }}{{ u.name }}{{ u.level }}{{ u.last_login }}Edit Del
+
+
+{% endblock %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..4f532df --- /dev/null +++ b/templates/base.html @@ -0,0 +1,52 @@ + + + + {% include ['head-perso.html', 'head.html'] %} + {% block head %} + {% block title %}{% endblock %} - Belote + {% endblock %} + + +
+ +
+ +
+ {% block titre %} {% endblock %} +
+
+ {% if not user == None %} + {{ user.name }} + {% else %} + S'identifier + {% endif %} + +
+
+ {% if not user == None %} + + + + {% if user.level > 9 %} + + + {% endif %} + {% else %} + + {% endif %} +
+
+ + + {% block contenu %} + {% endblock %} + + +
+ {% block script %} {% endblock %} + + + + diff --git a/templates/belote.html b/templates/belote.html new file mode 100644 index 0000000..0ec14f1 --- /dev/null +++ b/templates/belote.html @@ -0,0 +1,44 @@ + + + + + + +Belote + + + + + + + +
+ +
+ +
+TITRE +
+
+username + +
+
+ + +
+
+ +/div> + + diff --git a/templates/game.html b/templates/game.html new file mode 100644 index 0000000..977bd9d --- /dev/null +++ b/templates/game.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} + +{% block title %} Jeu {% endblock %} + +{% block titre %} Jeu {{ game.name }} {% endblock %} + +{% block head %} +{% include ['scripts-perso.html', 'scripts.html'] %} +{% endblock %} + +{% block contenu %} + {% include "game_zone_jeu.html" %} +{% endblock %} + +{% block script %} + + +{% endblock %} diff --git a/templates/game_add.html b/templates/game_add.html new file mode 100644 index 0000000..0547079 --- /dev/null +++ b/templates/game_add.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} + +{% block title %} Créer jeu {% endblock %} + +{% block titre %} Créer un jeu {% endblock %} + +{% block contenu %} +
+
+ +
Nom du jeu +
+ Nom du jeu : + +
+
+
+ +{% endblock %} + + diff --git a/templates/game_del.html b/templates/game_del.html new file mode 100644 index 0000000..4d6fc99 --- /dev/null +++ b/templates/game_del.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block title %} Jeux {% endblock %} + +{% block titre %} Suppression jeu {{game.name }} {% endblock %} + +{% block contenu %} +
+
Confirmation +

Sur de vouloir supprimer le jeu {{ game.name }} ?

+
+ + +
Oui Non
+
+
+{% endblock %} diff --git a/templates/game_join.html b/templates/game_join.html new file mode 100644 index 0000000..29db2a7 --- /dev/null +++ b/templates/game_join.html @@ -0,0 +1,166 @@ +{% extends "base.html" %} + +{% block title %} Jeu {% endblock %} + +{% block titre %} Jeu {{game.name}} {% endblock %} + +{% block head %} +{% include ['scripts-perso.html', 'scripts.html'] %} +{% endblock %} + +{% block contenu %} +
+
+ +
Liste des joueurs +
    + {% for p in game.get_players() %} + {% if p['username'] == admin %} +
  • {{ p['name'] }} ({{ p['username'] }}) (admin)
  • + {% else %} + {% if p['username'] == user.login %} +
  • {{ p['name'] }} ({{ p['username'] }}) (quitter le jeu)
  • + {% elif user.login == admin %} +
  • {{ p['name'] }} ({{ p['username'] }}) (bannir)
  • + {% else %} +
  • {{ p['name'] }} ({{ p['username'] }})
  • + {% endif %} + {% endif %} + {% endfor %} +
+
+ {% if user.login == admin %} +
Gestion des participants +
+ Ajouter par nom d'utilisateur : + + +
+ +
+ Vous pouvez aussi donner l'adresse du jeu (lire dans la barre d'adresse). +
+
Démarrer le jeu + Commencer +
+ {% else %} +
Info +

Veuillez attendre que le maitre du jeu lance la partie...

+

Rechargez la page si cela traine trop.

+
+ {% endif %} + +{% endblock %} + +{% block script %} + + +{% endblock %} diff --git a/templates/game_start.html b/templates/game_start.html new file mode 100644 index 0000000..65aa259 --- /dev/null +++ b/templates/game_start.html @@ -0,0 +1,88 @@ +{% extends "base.html" %} + +{% block title %} Jeu {% endblock %} + +{% block titre %} Jeu {{ game.name }} {% endblock %} + +{% block head %} +{% include ['scripts-perso.html', 'scripts.html'] %} +{% endblock %} + +{% block contenu %} +
+
Liste des joueurs +

Faites les équipes :

+
    +
+
+
Commencer + Commencer +
+
+ +{% endblock %} + +{% block script %} + +{% endblock %} diff --git a/templates/game_zone_jeu.html b/templates/game_zone_jeu.html new file mode 100644 index 0000000..a00cbae --- /dev/null +++ b/templates/game_zone_jeu.html @@ -0,0 +1,95 @@ +
+
+
joueur 0
+
joueur 1
+
joueur 2
+
joueur 3
+
+
+
+
+
+ +
+
+
+ + + + + + + + + +
Atout
Pris par ----
+
+
+

Dernier pli

+
+
+
+
+
+
+
+
+

Options

+ +
+
+
+
+
A vous de jouer ! +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Points +

+ + + + + + + + + + + + + + + + +
TotalPartie
Nous00
Eux00
+
+
+ +
+
+
+
+

Message

+ + +
+
diff --git a/templates/games.html b/templates/games.html new file mode 100644 index 0000000..04f48b9 --- /dev/null +++ b/templates/games.html @@ -0,0 +1,43 @@ +{% extends "base.html" %} + +{% block title %} Jeux {% endblock %} + +{% block titre %} Liste des jeux {% endblock %} + +{% block contenu %} +
+ {% if cancreategames %} +
Ajouter un jeu + +
+ {% endif %} + {% if mesjeux|length > 0 %} +
Mes jeux + + {% for g in mesjeux %} + + + + + {% endfor %} +
{{ g.name }} SupprimerSupprimer
+
+ {% endif %} + {% if joinable|length > 0 %} +
Autres jeux + + {% for g in joinable %} + + + + + {% endfor %} +
{{ g.name }} Supprimer
+
+ {% endif %} +
+ +{% endblock %} + diff --git a/templates/head.html b/templates/head.html new file mode 100644 index 0000000..83b87a5 --- /dev/null +++ b/templates/head.html @@ -0,0 +1,5 @@ + + + + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..c668c5e --- /dev/null +++ b/templates/index.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} + +{% block title %} Bienvenue {% endblock %} + +{% block titre %} Bienvenue {% endblock %} + +{% block contenu %} +
+

Bienvenue sur le jeu de belote !

+

Vous devez vous identifier pour jouer, rendez vous sur la page de login

+
+{% endblock %} diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..b60ef3a --- /dev/null +++ b/templates/login.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} + +{% block title %} Login {% endblock %} + +{% block titre %} Login {% endblock %} + +{% block contenu %} +
+
+
+
Identifiez-vous + {% for message in get_flashed_messages() %} +
+ + {{ message }} +
+ {% endfor %} + + + + + +
Nom d'utilisateur :
Mot de passe :
 
+
+
+
+ +{% endblock %} + diff --git a/templates/password.html b/templates/password.html new file mode 100644 index 0000000..ae995a6 --- /dev/null +++ b/templates/password.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block title %} Mot de passe {% endblock %} + +{% block titre %} Changement de mot de passe {% endblock %} + +{% block contenu %} +
+
+ +
Nom du jeu + {% for message in get_flashed_messages() %} +
+ + {{ message }} +
+ {% endfor %} +
+ Nouveau mot de passe : + +
+
+
+ +{% endblock %} + + diff --git a/templates/scripts.html b/templates/scripts.html new file mode 100644 index 0000000..c108492 --- /dev/null +++ b/templates/scripts.html @@ -0,0 +1 @@ +