├── bug.txt ├── src ├── go.mod ├── readme.md ├── dice │ └── throw_dice.go └── main │ └── run_monopoly.go ├── favicon.ico ├── img ├── bijou.png ├── bus.png ├── dice.png ├── dices.png ├── hotel.png ├── house.png ├── info2.png ├── jail.jpg ├── list.png ├── money.png ├── save.png ├── swap.png ├── tap.png ├── tour.png ├── tour2.png ├── train.png ├── banque.png ├── banque2.png ├── house2.png ├── light2.png ├── maison.png ├── money2.png ├── prison.png ├── behaviour.png ├── big_train.png ├── info-user.png ├── info-user2.png ├── strategy.png ├── little_train.png ├── mr_monopoly.jpg ├── mr_monopoly.png ├── reload_dice.png ├── interrogation.png ├── jamesbond │ ├── woman.png │ ├── gadget.png │ ├── spectre.png │ ├── aston_martin.png │ └── universal_export.png └── starwars │ ├── r2_d2.png │ ├── xwing.png │ ├── clone_1.png │ ├── clone_2.png │ ├── clone_3.png │ ├── clone_4.png │ ├── sw_c3po.png │ ├── sw_clone.png │ ├── sw_r2d2.png │ ├── boba_fett.png │ ├── darth_vader.png │ ├── jango_fett.png │ ├── stormtrooper.png │ ├── sw_boba_fett.png │ ├── sw_chewbacca.png │ ├── jedi_light_saber.png │ ├── sith_light_saber.png │ └── sw_darth_vader.png ├── old ├── plateau.jpg └── monopoly_old.html ├── poc ├── img_test │ ├── icon_ok_1.png │ ├── icon_ok_10.png │ ├── icon_ok_2.png │ ├── icon_ok_3.png │ ├── icon_ok_4.png │ ├── icon_ok_5.png │ ├── icon_ok_6.png │ ├── icon_ok_7.png │ ├── icon_ok_8.png │ └── icon_ok_9.png ├── pi.js ├── test-eyes.html ├── jquery.mousewheel.min.js ├── slider.html └── bingo.html ├── data ├── data-monopoly-circle-bond.json ├── data-monopoly-couleur.json ├── data-monopoly-circle-starwars2.json ├── data-monopoly-starwars2.json ├── data-monopoly-circle.json ├── plateaux.json ├── data-monopoly-junior.json ├── data-monopoly-jamesbond.json ├── data-monopoly-starwars.json ├── data-monopoly-original.json └── data-monopoly.json ├── js ├── request_service.js ├── bus_message.js ├── utils.js ├── entity │ ├── network │ │ └── local_joueur.js │ ├── pion.js │ ├── cartes_action.js │ ├── comportement.js │ └── strategie.js ├── sauvegarde.js ├── core │ ├── gestion_constructions.js │ └── gestion_joueurs.js ├── ui │ ├── graphics.js │ └── circle_graphics.js └── display │ └── displayers.js ├── package.json ├── webpack-conf.js ├── .github └── workflows │ └── main.yml ├── README.textile ├── webpack-conf-public.js ├── css └── qunit-1.12.0.css ├── test └── test-maisons-js.html └── lib └── circletype.min.js /bug.txt: -------------------------------------------------------------------------------- 1 | 7) Ajout de messages sympas lors de l'echange 2 | -------------------------------------------------------------------------------- /src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jotitan/monopoly 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /src/readme.md: -------------------------------------------------------------------------------- 1 | Server implementation in golang for remote player. -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/favicon.ico -------------------------------------------------------------------------------- /img/bijou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/bijou.png -------------------------------------------------------------------------------- /img/bus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/bus.png -------------------------------------------------------------------------------- /img/dice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/dice.png -------------------------------------------------------------------------------- /img/dices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/dices.png -------------------------------------------------------------------------------- /img/hotel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/hotel.png -------------------------------------------------------------------------------- /img/house.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/house.png -------------------------------------------------------------------------------- /img/info2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/info2.png -------------------------------------------------------------------------------- /img/jail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/jail.jpg -------------------------------------------------------------------------------- /img/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/list.png -------------------------------------------------------------------------------- /img/money.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/money.png -------------------------------------------------------------------------------- /img/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/save.png -------------------------------------------------------------------------------- /img/swap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/swap.png -------------------------------------------------------------------------------- /img/tap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/tap.png -------------------------------------------------------------------------------- /img/tour.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/tour.png -------------------------------------------------------------------------------- /img/tour2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/tour2.png -------------------------------------------------------------------------------- /img/train.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/train.png -------------------------------------------------------------------------------- /img/banque.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/banque.png -------------------------------------------------------------------------------- /img/banque2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/banque2.png -------------------------------------------------------------------------------- /img/house2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/house2.png -------------------------------------------------------------------------------- /img/light2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/light2.png -------------------------------------------------------------------------------- /img/maison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/maison.png -------------------------------------------------------------------------------- /img/money2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/money2.png -------------------------------------------------------------------------------- /img/prison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/prison.png -------------------------------------------------------------------------------- /old/plateau.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/old/plateau.jpg -------------------------------------------------------------------------------- /img/behaviour.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/behaviour.png -------------------------------------------------------------------------------- /img/big_train.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/big_train.png -------------------------------------------------------------------------------- /img/info-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/info-user.png -------------------------------------------------------------------------------- /img/info-user2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/info-user2.png -------------------------------------------------------------------------------- /img/strategy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/strategy.png -------------------------------------------------------------------------------- /img/little_train.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/little_train.png -------------------------------------------------------------------------------- /img/mr_monopoly.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/mr_monopoly.jpg -------------------------------------------------------------------------------- /img/mr_monopoly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/mr_monopoly.png -------------------------------------------------------------------------------- /img/reload_dice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/reload_dice.png -------------------------------------------------------------------------------- /img/interrogation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/interrogation.png -------------------------------------------------------------------------------- /img/jamesbond/woman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/jamesbond/woman.png -------------------------------------------------------------------------------- /img/starwars/r2_d2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/r2_d2.png -------------------------------------------------------------------------------- /img/starwars/xwing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/xwing.png -------------------------------------------------------------------------------- /old/monopoly_old.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/old/monopoly_old.html -------------------------------------------------------------------------------- /img/jamesbond/gadget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/jamesbond/gadget.png -------------------------------------------------------------------------------- /img/jamesbond/spectre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/jamesbond/spectre.png -------------------------------------------------------------------------------- /img/starwars/clone_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/clone_1.png -------------------------------------------------------------------------------- /img/starwars/clone_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/clone_2.png -------------------------------------------------------------------------------- /img/starwars/clone_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/clone_3.png -------------------------------------------------------------------------------- /img/starwars/clone_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/clone_4.png -------------------------------------------------------------------------------- /img/starwars/sw_c3po.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/sw_c3po.png -------------------------------------------------------------------------------- /img/starwars/sw_clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/sw_clone.png -------------------------------------------------------------------------------- /img/starwars/sw_r2d2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/sw_r2d2.png -------------------------------------------------------------------------------- /img/starwars/boba_fett.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/boba_fett.png -------------------------------------------------------------------------------- /img/starwars/darth_vader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/darth_vader.png -------------------------------------------------------------------------------- /img/starwars/jango_fett.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/jango_fett.png -------------------------------------------------------------------------------- /poc/img_test/icon_ok_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/poc/img_test/icon_ok_1.png -------------------------------------------------------------------------------- /poc/img_test/icon_ok_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/poc/img_test/icon_ok_10.png -------------------------------------------------------------------------------- /poc/img_test/icon_ok_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/poc/img_test/icon_ok_2.png -------------------------------------------------------------------------------- /poc/img_test/icon_ok_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/poc/img_test/icon_ok_3.png -------------------------------------------------------------------------------- /poc/img_test/icon_ok_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/poc/img_test/icon_ok_4.png -------------------------------------------------------------------------------- /poc/img_test/icon_ok_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/poc/img_test/icon_ok_5.png -------------------------------------------------------------------------------- /poc/img_test/icon_ok_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/poc/img_test/icon_ok_6.png -------------------------------------------------------------------------------- /poc/img_test/icon_ok_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/poc/img_test/icon_ok_7.png -------------------------------------------------------------------------------- /poc/img_test/icon_ok_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/poc/img_test/icon_ok_8.png -------------------------------------------------------------------------------- /poc/img_test/icon_ok_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/poc/img_test/icon_ok_9.png -------------------------------------------------------------------------------- /img/jamesbond/aston_martin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/jamesbond/aston_martin.png -------------------------------------------------------------------------------- /img/starwars/stormtrooper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/stormtrooper.png -------------------------------------------------------------------------------- /img/starwars/sw_boba_fett.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/sw_boba_fett.png -------------------------------------------------------------------------------- /img/starwars/sw_chewbacca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/sw_chewbacca.png -------------------------------------------------------------------------------- /img/starwars/jedi_light_saber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/jedi_light_saber.png -------------------------------------------------------------------------------- /img/starwars/sith_light_saber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/sith_light_saber.png -------------------------------------------------------------------------------- /img/starwars/sw_darth_vader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/starwars/sw_darth_vader.png -------------------------------------------------------------------------------- /img/jamesbond/universal_export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jotitan/monopoly/HEAD/img/jamesbond/universal_export.png -------------------------------------------------------------------------------- /data/data-monopoly-circle-bond.json: -------------------------------------------------------------------------------- 1 | { 2 | "extend":"data-monopoly-jamesbond.json", 3 | "plateau":{ 4 | "subtitle":"Round", 5 | "type":"circle" 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /data/data-monopoly-couleur.json: -------------------------------------------------------------------------------- 1 | { 2 | "extend":"data-monopoly.json", 3 | "plateau":{ 4 | "nomsJoueurs":["Bleu","Rouge","Orange","Vert","Gris","Marron","Violet"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /data/data-monopoly-circle-starwars2.json: -------------------------------------------------------------------------------- 1 | { 2 | "extend":"data-monopoly-starwars2.json", 3 | "plateau":{ 4 | "type":"circle" 5 | }, 6 | "images":{ 7 | "default":{ 8 | "marginTop":140, 9 | "marginLeft":0.25, 10 | "rotate":-90 11 | } 12 | } 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/dice/throw_dice.go: -------------------------------------------------------------------------------- 1 | package dice 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | var randSource = rand.New(rand.NewSource(time.Now().UnixNano())) 9 | 10 | func Throw(nbDice int)[]int{ 11 | dices := make([]int,nbDice) 12 | for i := 0 ; i < nbDice ; i++{ 13 | dices[i] = randSource.Int()%6+1 14 | } 15 | return dices 16 | } 17 | -------------------------------------------------------------------------------- /poc/pi.js: -------------------------------------------------------------------------------- 1 | // Calcul de PI 2 | 3 | var NB_LANCER = 200000; 4 | var results = {in:0,out:0}; 5 | for(var i = 0 ; i < NB_LANCER ; i++){ 6 | var x = rand(); 7 | var y = rand(); 8 | if(x*x + y*y <=1){results.in++;} 9 | else{results.out++;} 10 | } 11 | var pi = (results.in/NB_LANCER)*4; 12 | console.log(pi); 13 | 14 | function rand(){ 15 | return (Math.random()*1000000000)/1000000000; 16 | } -------------------------------------------------------------------------------- /data/data-monopoly-starwars2.json: -------------------------------------------------------------------------------- 1 | { 2 | "extend":"data-monopoly-starwars.json", 3 | "plateau":{ 4 | "subtitle":"Star Wars Personnage", 5 | "nomsJoueurs":["R2 D2","C3 PO","Dark Vador","StormTrooper","Boba Feet","Chewbacca"], 6 | "imgJoueurs":["img/starwars/sw_r2d2.png","img/starwars/sw_c3po.png","img/starwars/sw_darth_vader.png","img/starwars/sw_clone.png","img/starwars/sw_boba_fett.png","img/starwars/sw_chewbacca.png"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /data/data-monopoly-circle.json: -------------------------------------------------------------------------------- 1 | { 2 | "extend":"data-monopoly.json", 3 | "plateau":{ 4 | "subtitle":"Round", 5 | "type":"circle" 6 | }, 7 | "images":{ 8 | "default":{ 9 | "marginTop":150, 10 | "marginLeft":0.25, 11 | "rotate":-90 12 | }, 13 | "gare":{ 14 | "width":40, 15 | "height":20 16 | }, 17 | "chance":{ 18 | "width":25, 19 | "height":30 20 | }, 21 | "caisseDeCommunaute":{ 22 | "height":30, 23 | "width":25, 24 | "marginLeft":0.15 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /js/request_service.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function get(path){ 4 | return fetch(path).then(d=>d.json()) 5 | } 6 | 7 | function post(path, data=''){ 8 | return fetch(path,{method:'POST',body:data}).then(d=>d.json()) 9 | } 10 | 11 | function createGameRequest(){ 12 | return post('/createGame') 13 | } 14 | 15 | function loadAllPlateaux(){ 16 | return get('data/plateaux.json'); 17 | } 18 | 19 | function dices(nb){ 20 | return get(`dices?nb=${nb}`); 21 | } 22 | 23 | function sendEventOnNetwork(url, event){ 24 | return post(url, JSON.stringify(event)) 25 | } 26 | 27 | export {get, post, createGameRequest, loadAllPlateaux, dices, sendEventOnNetwork}; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "copy-webpack-plugin": "^11.0.0", 4 | "css-loader": "^6.8.1", 5 | "grunt": "^1.6.1", 6 | "grunt-cli": "^1.4.3", 7 | "grunt-contrib-clean": "^2.0.1", 8 | "grunt-contrib-copy": "^1.0.0", 9 | "grunt-contrib-uglify": "^5.2.2", 10 | "grunt-contrib-watch": "^1.1.0", 11 | "grunt-targethtml": "^0.2.6", 12 | "grunt-zip": "^0.20.0", 13 | "html-replace-webpack-plugin": "^2.6.0", 14 | "html-webpack-plugin": "^5.5.3", 15 | "string-replace-loader": "^3.1.0", 16 | "webpack": "^5.88.2", 17 | "webpack-cli": "^5.1.4", 18 | "zip-webpack-plugin": "^4.0.1" 19 | }, 20 | "scripts": { 21 | "pack": "webpack --config ./webpack-conf.js", 22 | "pack-github": "webpack --config ./webpack-conf-public.js" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /js/bus_message.js: -------------------------------------------------------------------------------- 1 | 2 | // Manage messages between parts of app 3 | 4 | class Bus { 5 | constructor(){ 6 | this.observers = {}; 7 | } 8 | network(data){ 9 | this.send('event.network',data); 10 | } 11 | debug(data){ 12 | this.send('monopoly.debug',data); 13 | } 14 | 15 | refresh(){ 16 | this.send('refreshPlateau'); 17 | } 18 | 19 | send(key, data = {}){ 20 | const clients = this.observers[key]; 21 | if(clients != null){ 22 | clients.forEach(callback => callback(data)) 23 | } 24 | } 25 | observe(key, callback){ 26 | let clients = this.observers[key]; 27 | if(clients == null) { 28 | clients = []; 29 | this.observers[key] = clients; 30 | } 31 | clients.push(callback); 32 | } 33 | 34 | watchRefresh(callback) { 35 | this.observe('refreshPlateau',callback); 36 | } 37 | } 38 | 39 | const bus = new Bus() 40 | 41 | export {bus} -------------------------------------------------------------------------------- /data/plateaux.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "Remplace le futur service listant dynamiquement les fichiers de configuration", 3 | "plateaux": [ 4 | { 5 | "name": "Classique", 6 | "url": "data-monopoly.json" 7 | }, 8 | { 9 | "name": "Original", 10 | "url": "data-monopoly-original.json" 11 | }, 12 | { 13 | "name": "Classique Circle", 14 | "url": "data-monopoly-circle.json" 15 | }, 16 | { 17 | "name": "Couleur", 18 | "url": "data-monopoly-couleur.json" 19 | }, 20 | { 21 | "name": "James Bond", 22 | "url": "data-monopoly-jamesbond.json" 23 | }, 24 | { 25 | "name": "James Bond Circle", 26 | "url": "data-monopoly-circle-bond.json" 27 | }, 28 | { 29 | "name": "Star Wars", 30 | "url": "data-monopoly-starwars.json" 31 | }, 32 | { 33 | "name": "Star Wars 2", 34 | "url": "data-monopoly-starwars2.json" 35 | }, 36 | { 37 | "name": "Star Wars 2 Circle", 38 | "url": "data-monopoly-circle-starwars2.json" 39 | } 40 | , 41 | { 42 | "name": "Monopoly Junior", 43 | "url": "data-monopoly-junior.json" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /webpack-conf.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const HtmlReplaceWebpackPlugin = require('html-replace-webpack-plugin'); 3 | const CopyPlugin = require('copy-webpack-plugin'); 4 | const ZipPlugin = require('zip-webpack-plugin'); 5 | 6 | const webpackConf = require('webpack'); //to access built-in plugins 7 | 8 | const path = require('path'); 9 | 10 | module.exports = { 11 | entry: './js/core/monopoly.js', 12 | mode: 'production', 13 | output: { 14 | path: path.join(__dirname, 'dist'), 15 | filename: 'my-bundle.js' 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | test: /monopoly\.js$/, 21 | loader: 'string-replace-loader', 22 | options: { 23 | search: /let debug = true;/i, 24 | replace: 'let debug = false;' 25 | } 26 | } 27 | ] 28 | }, 29 | plugins: [ 30 | new HtmlWebpackPlugin({template: './monopoly-template.html'}), 31 | new HtmlReplaceWebpackPlugin([ 32 | { 33 | pattern: '', 34 | replacement: '' 35 | }]), 36 | new CopyPlugin({ 37 | patterns: [ 38 | {from: "css", to: "css"}, 39 | {from: "img", to: "img"}, 40 | {from: "lib", to: "lib"}, 41 | {from: "data", to: "data"}, 42 | ], 43 | }), 44 | new ZipPlugin({ 45 | path: '', 46 | filename: 'monopoly_build.zip', 47 | }) 48 | ] 49 | 50 | }; -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # Workflow to build at each commit in master the application 2 | 3 | name: CI build Monopoly 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | build: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v3 24 | 25 | - name: Build go server 26 | run: | 27 | echo Run build server go 28 | cd src/main 29 | export GOOS=linux 30 | export GOARCH=arm 31 | go build -o run_monopoly run_monopoly.go 32 | cd ../../ 33 | mkdir artifacts 34 | echo Copy monopoly server to artifacts/ 35 | mv src/main/run_monopoly artifacts 36 | 37 | - uses: actions/setup-node@v3 38 | with: 39 | node-version: '16' 40 | 41 | - name: Build front 42 | run: | 43 | echo Install and build app, 44 | npm install 45 | npm run pack 46 | mv dist/monopoly_build.zip artifacts 47 | 48 | # Upload artifact 49 | - uses: actions/upload-artifact@v4 50 | with: 51 | name: Artifacts monopoly 52 | path: artifacts 53 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. Monopoly full JS !https://codebeat.co/badges/843f9fbf-e914-495f-8d9f-2f85343cc22b(codebeat badge)!:https://codebeat.co/projects/github-com-jotitan-monopoly-master !https://github.com/jotitan/monopoly/actions/workflows/main.yml/badge.svg?branch=master! 2 | 3 | Implementation Full Javascript du Monopoly. 4 | Aucune librairie Javascript et packaging avec Webpack. 5 | 6 | h2. Démo 7 | 8 | Demo du monopoly : "Monopoly":http://jotitan.github.io/monopoly/demo/ 9 | 10 | h2. Fonctionnalités 11 | 12 | * Possibilité de jouer à 6 sur le même ordinateur 13 | * IA : plusieurs robots peuvent jouer. Différents niveaux sont implémentés (gestion de l'argent, choix des terrains et agressivité). 14 | * Plusieurs variantes : case départ x 2, parc gratuit, enchere immédiate 15 | * Possibilité d'échanger les terrains avec les autres joueurs 16 | * Utilisation du dé rapide (règle récente) 17 | * Plusieurs plateaux sont disponibles : classique, en cercle 18 | * Possibilité de customiser les plateaux (images, nom des joueurs, nom des rues, cartes, couleurs) 19 | * Sauvegarde la partie (en cliquant sur "MONOPOLY") 20 | * Gestion du réseau (serveur en Go) 21 | 22 | h2. Build 23 | 24 | Il est possible de tester l'application localement en ouvrant monopoly-template.html 25 | Vous pouvez également l'application pour réduire sa taille en lançant : 26 | ```shell 27 | npm run pack 28 | ``` 29 | 30 | h2. Capture de la version avec dé rapide : 31 | 32 | !https://raw.github.com/jotitan/monopoly-js/gh-pages/screenshots/screenshot2.png! 33 | 34 | h2. Version en cercle du Monopoly 35 | 36 | !https://raw.github.com/jotitan/monopoly-js/gh-pages/screenshots/screenshot-circle.png! 37 | 38 | h2. Todo 39 | 40 | * Accepter plus facilement les propositions vers la fin du jeu, surtout si on a rien 41 | -------------------------------------------------------------------------------- /webpack-conf-public.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const HtmlReplaceWebpackPlugin = require('html-replace-webpack-plugin'); 3 | const CopyPlugin = require('copy-webpack-plugin'); 4 | const ZipPlugin = require('zip-webpack-plugin'); 5 | 6 | const webpackConf = require('webpack'); //to access built-in plugins 7 | 8 | const path = require('path'); 9 | 10 | module.exports = { 11 | entry: './js/core/monopoly.js', 12 | mode: 'production', 13 | output: { 14 | path: path.join(__dirname, 'dist'), 15 | filename: 'my-bundle.js' 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | test: /monopoly\.js$/, 21 | loader: 'string-replace-loader', 22 | options: { 23 | multiple:[{ 24 | search: /let enableNetwork = true;/i, 25 | replace: 'let enableNetwork = false;' 26 | }, { 27 | search: /let debug = true;/i, 28 | replace: 'let debug = false;' 29 | }] 30 | } 31 | } 32 | ] 33 | }, 34 | plugins: [ 35 | new HtmlWebpackPlugin({template: './monopoly-template.html'}), 36 | new HtmlReplaceWebpackPlugin([ 37 | { 38 | pattern: '', 39 | replacement: '' 40 | },{ 41 | pattern: 'let debug = true;', 42 | replacement: 'let debug = false;' 43 | },{ 44 | pattern: 'let enableNetwork = true;', 45 | replacement: 'let enableNetwork = false;' 46 | }]), 47 | 48 | new CopyPlugin({ 49 | patterns: [ 50 | {from: "css", to: "css"}, 51 | {from: "img", to: "img"}, 52 | {from: "lib", to: "lib"}, 53 | {from: "data", to: "data"}, 54 | ], 55 | }) 56 | ] 57 | 58 | }; -------------------------------------------------------------------------------- /poc/test-eyes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /js/utils.js: -------------------------------------------------------------------------------- 1 | /* Fonctions utilitaires */ 2 | 3 | // Defini la methode size. Cette methode evite d'etre enumere dans les boucles 4 | Object.defineProperty(Array.prototype, "size", { 5 | value: function () { 6 | let count = 0; 7 | for (let i in this) { 8 | count++; 9 | } 10 | return count; 11 | }, 12 | writable: false, 13 | enumerable: false, 14 | configurable: false 15 | }); 16 | 17 | Object.defineProperty(Array.prototype, "contains", { 18 | value: function (value) { 19 | for (let i in this) { 20 | if (this[i] === value) { 21 | return true; 22 | } 23 | } 24 | return false; 25 | }, 26 | writable: false, 27 | enumerable: false, 28 | configurable: false 29 | }); 30 | 31 | Object.defineProperty(Array.prototype, "filter", { 32 | value: function (callback) { 33 | const list = []; 34 | for (const i in this) { 35 | if (callback(this[i])) { 36 | list.push(this[i]); 37 | } 38 | } 39 | return list; 40 | }, 41 | writable: false, 42 | enumerable: false, 43 | configurable: false 44 | }); 45 | 46 | Object.defineProperty(Array.prototype, "some", { 47 | value: function (callback) { 48 | for (let i in this) { 49 | if (callback(this[i])) { 50 | return true; 51 | } 52 | } 53 | return false; 54 | }, 55 | writable: false, 56 | enumerable: false, 57 | configurable: false 58 | }); 59 | 60 | function deepCopy(a,b){ 61 | let copy = {...a}; 62 | Object.keys(b).forEach(k=>{ 63 | if(copy[k] == null){ 64 | copy[k] = b[k]; 65 | }else{ 66 | if(typeof b[k] === 'object' && !Array.isArray(b[k])){ 67 | copy[k] = deepCopy(copy[k],b[k]); 68 | }else{ 69 | copy[k] = b[k]; 70 | } 71 | } 72 | }) 73 | return copy; 74 | } 75 | 76 | function disableActions(){ 77 | document.querySelectorAll('.action-joueur').forEach(d=>{ 78 | d.classList.add('disabled'); 79 | d.setAttribute('disabled','disabled'); 80 | }) 81 | } 82 | 83 | function enableActions(){ 84 | document.querySelectorAll('.action-joueur').forEach(d=>{ 85 | d.classList.remove('disabled'); 86 | d.removeAttribute('disabled'); 87 | }) 88 | } 89 | 90 | export {deepCopy, disableActions, enableActions} -------------------------------------------------------------------------------- /js/entity/network/local_joueur.js: -------------------------------------------------------------------------------- 1 | import {NetworkJoueur, Joueur, Notifier} from '../joueur.js'; 2 | import {GestionFiche} from "../../display/case_jeu.js"; 3 | import {GestionDes} from "../dices.js"; 4 | import {GestionJoueur} from "../../core/gestion_joueurs.js"; 5 | 6 | /* Represent a local player in remote game */ 7 | /* Extend joueur but override many methods, no action, only receive events */ 8 | class LocalPlayer extends NetworkJoueur { 9 | constructor(numero, nom, color,argent,montantDepart){ 10 | super(numero,nom,color,argent,montantDepart); 11 | } 12 | lancerDes() { 13 | // Send event to get score for dices. If prison, ask stay or not before 14 | GestionDes.gestionDes.before(()=>Notifier.askDices(this)); 15 | } 16 | notifySelect(){} 17 | // Notify to master end of turn 18 | endTurn(){ 19 | Notifier.notifyEnd() 20 | } 21 | notifyDices(dices){} 22 | } 23 | 24 | class RemotePlayer extends Joueur{ 25 | constructor(numero,nom,color,argent,montantDepart){ 26 | super(numero,nom,color,argent,montantDepart); 27 | this.canPlay = false; 28 | this.type = "Distant"; 29 | this.free = true; 30 | } 31 | isSlotFree(){ 32 | return this.free; 33 | } 34 | setPlayer(player){ 35 | this.nom = player; 36 | this.free = false; 37 | this.div.querySelector('.joueur-name').innerHTML = this.nom; 38 | } 39 | // If remote player receive money, it's done and send to all 40 | notifyPay(montant){ 41 | Notifier.payer(montant,this); 42 | } 43 | } 44 | 45 | // Remote manage by master, must send his events 46 | class MasterRemotePlayer extends RemotePlayer{ 47 | constructor(numero,nom,color,argent,montantDepart) { 48 | super(numero, nom, color, argent,montantDepart); 49 | } 50 | // Send event when set as player 51 | notifySelect(){ 52 | Notifier.notifySelect(this); 53 | } 54 | notifyDices(dices,event){ 55 | // Send event tax if necessary, move to and change 56 | Notifier.dices(dices,event,this); 57 | if(event.prison != null) { 58 | if (!event.prison.sortie) { 59 | GestionJoueur.change() 60 | }else{ 61 | GestionJoueur.getJoueurCourant().exitPrison() 62 | // exist but must by or relaunch 63 | } 64 | } 65 | } 66 | moveTo(nb){ 67 | let nextCase = this.pion.deplaceValeursDes(nb); 68 | Notifier.moveTo(GestionFiche.buildId(nextCase),this); 69 | this.joueSurCase(nextCase); 70 | } 71 | } 72 | 73 | export {MasterRemotePlayer,RemotePlayer,LocalPlayer}; 74 | -------------------------------------------------------------------------------- /poc/jquery.mousewheel.min.js: -------------------------------------------------------------------------------- 1 | /*! Copyright (c) 2013 Brandon Aaron (http://brandon.aaron.sh) 2 | * Licensed under the MIT License (LICENSE.txt). 3 | * 4 | * Version: 3.1.12 5 | * 6 | * Requires: jQuery 1.2.2+ 7 | */ 8 | !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?module.exports=a:a(jQuery)}(function(a){function b(b){var g=b||window.event,h=i.call(arguments,1),j=0,l=0,m=0,n=0,o=0,p=0;if(b=a.event.fix(g),b.type="mousewheel","detail"in g&&(m=-1*g.detail),"wheelDelta"in g&&(m=g.wheelDelta),"wheelDeltaY"in g&&(m=g.wheelDeltaY),"wheelDeltaX"in g&&(l=-1*g.wheelDeltaX),"axis"in g&&g.axis===g.HORIZONTAL_AXIS&&(l=-1*m,m=0),j=0===m?l:m,"deltaY"in g&&(m=-1*g.deltaY,j=m),"deltaX"in g&&(l=g.deltaX,0===m&&(j=-1*l)),0!==m||0!==l){if(1===g.deltaMode){var q=a.data(this,"mousewheel-line-height");j*=q,m*=q,l*=q}else if(2===g.deltaMode){var r=a.data(this,"mousewheel-page-height");j*=r,m*=r,l*=r}if(n=Math.max(Math.abs(m),Math.abs(l)),(!f||f>n)&&(f=n,d(g,n)&&(f/=40)),d(g,n)&&(j/=40,l/=40,m/=40),j=Math[j>=1?"floor":"ceil"](j/f),l=Math[l>=1?"floor":"ceil"](l/f),m=Math[m>=1?"floor":"ceil"](m/f),k.settings.normalizeOffset&&this.getBoundingClientRect){var s=this.getBoundingClientRect();o=b.clientX-s.left,p=b.clientY-s.top}return b.deltaX=l,b.deltaY=m,b.deltaFactor=f,b.offsetX=o,b.offsetY=p,b.deltaMode=0,h.unshift(b,j,l,m),e&&clearTimeout(e),e=setTimeout(c,200),(a.event.dispatch||a.event.handle).apply(this,h)}}function c(){f=null}function d(a,b){return k.settings.adjustOldDeltas&&"mousewheel"===a.type&&b%120===0}var e,f,g=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],h="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],i=Array.prototype.slice;if(a.event.fixHooks)for(var j=g.length;j;)a.event.fixHooks[g[--j]]=a.event.mouseHooks;var k=a.event.special.mousewheel={version:"3.1.12",setup:function(){if(this.addEventListener)for(var c=h.length;c;)this.addEventListener(h[--c],b,!1);else this.onmousewheel=b;a.data(this,"mousewheel-line-height",k.getLineHeight(this)),a.data(this,"mousewheel-page-height",k.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var c=h.length;c;)this.removeEventListener(h[--c],b,!1);else this.onmousewheel=null;a.removeData(this,"mousewheel-line-height"),a.removeData(this,"mousewheel-page-height")},getLineHeight:function(b){var c=a(b),d=c["offsetParent"in a.fn?"offsetParent":"parent"]();return d.length||(d=a("body")),parseInt(d.css("fontSize"),10)||parseInt(c.css("fontSize"),10)||16},getPageHeight:function(b){return a(b).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};a.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})}); -------------------------------------------------------------------------------- /js/entity/pion.js: -------------------------------------------------------------------------------- 1 | /* Objet PION */ 2 | import {DrawerFactory,Drawer} from "../ui/graphics.js"; 3 | import {GestionJoueur} from "../core/gestion_joueurs.js"; 4 | import {VARIANTES} from "../core/monopoly.js"; 5 | import {bus} from "../bus_message.js"; 6 | 7 | function Pion(color, joueur,img, montantDepart = 20000) { 8 | this.axe = 2; 9 | this.position = 0; 10 | this.joueur = joueur; 11 | this.montantDepart = montantDepart; 12 | this.stats = { 13 | tour: 0, 14 | prison: 0, 15 | positions:[] 16 | }; // stat du joueur 17 | this.pion = DrawerFactory.getPionJoueur(color,DrawerFactory.dimensions.largeurPion,img,this.joueur); 18 | Drawer.addRealTime(this.pion); 19 | 20 | /* Supprime le pion en cas de defaite */ 21 | this.remove = function () { 22 | Drawer.removeComponent(this.pion); 23 | } 24 | 25 | // Ca directement en prison, sans passer par la case depart, en coupant 26 | this.goPrison = function (callback) { 27 | this.stats.prison++; 28 | // Manque le callback 29 | this.goDirectToCell(3, 0, callback); 30 | } 31 | 32 | this.deplaceValeursDes = function (des) { 33 | let pos = this.position + des; 34 | let axe = this.axe; 35 | while(pos<0){ 36 | pos+=DrawerFactory.dimensions.nbCases; 37 | axe= (axe + 3)%4; 38 | } 39 | while (pos >= DrawerFactory.dimensions.nbCases) { 40 | pos -= DrawerFactory.dimensions.nbCases; 41 | axe = (axe + 1) % 4; 42 | } 43 | return { 44 | pos: pos, 45 | axe: axe 46 | } 47 | }; 48 | 49 | this.goto = function (axe, pos, call,direct=VARIANTES.quickMove,primeDepart=true) { 50 | let id = axe+"-"+pos; 51 | 52 | if(this.stats.positions[id] == null){ 53 | this.stats.positions[id] = 1; 54 | } 55 | else{ 56 | this.stats.positions[id]++; 57 | } 58 | if(GestionJoueur.getJoueurCourant() != null) { 59 | bus.debug( { 60 | message: `${GestionJoueur.getJoueurCourant().nom} est en ${this.axe} - ${this.position} et va en ${id}` 61 | }); 62 | } 63 | // On gere le cas de la case depart (si elle est sur le trajet) 64 | let depart = this.axe*DrawerFactory.dimensions.nbCases + this.position; 65 | let cible = axe*DrawerFactory.dimensions.nbCases + pos; 66 | let caseDepart = 2*DrawerFactory.dimensions.nbCases; 67 | if(primeDepart && ((depart < caseDepart && cible > caseDepart) || (depart > cible && (depart < caseDepart || cible > caseDepart)))){ 68 | this.treatCaseDepart(); 69 | } 70 | this.axe = axe; 71 | this.position = pos; 72 | this.pion.goto(axe, pos, call,false,direct); 73 | }; 74 | 75 | // Si on passe par la case depart, on prend 20000 Francs 76 | this.treatCaseDepart = function () { 77 | this.stats.tour++; 78 | this.joueur.gagner(this.montantDepart); 79 | } 80 | 81 | this.goDirectToCell = function (axe, pos, callback) { 82 | this.axe = axe; 83 | this.position = pos; 84 | this.pion.gotoDirect(axe,pos,callback); 85 | } 86 | } 87 | 88 | export {Pion}; 89 | -------------------------------------------------------------------------------- /js/sauvegarde.js: -------------------------------------------------------------------------------- 1 | /* Gestion de la sauvegarde */ 2 | import {GestionJoueur} from "./core/gestion_joueurs.js"; 3 | import {GestionFiche} from "./display/case_jeu.js"; 4 | import {VARIANTES, globalStats, updateVariantes} from "./core/monopoly.js"; 5 | import {bus} from "./bus_message.js"; 6 | import {deepCopy} from "./utils.js"; 7 | 8 | let Sauvegarde = { 9 | prefix: "monopoly.", 10 | suffix: ".save", 11 | currentSauvegardeName:null, 12 | isSauvegarde:function(){ 13 | return this.currentSauvegardeName!=null; 14 | }, 15 | save: function (name, plateau) { 16 | this.currentSauvegardeName = name !=null ? this.getSauvegardeName(name) : this.currentSauvegardeName || this.getSauvegardeName(); 17 | this.saveWithName(this.currentSauvegardeName,plateau); 18 | bus.send('monopoly.save',{name: this.currentSauvegardeName}); 19 | }, 20 | saveWithName: function (saveName, plateau) { 21 | // On recupere la liste des joueurs 22 | let saveJoueurs = []; 23 | GestionJoueur.joueurs.filter(j=>j.saver).forEach(j=>saveJoueurs.push(j.saver.save())); 24 | // On recupere la liste des fiches 25 | let saveFiches = []; 26 | let it = GestionFiche.iteratorTerrains(); 27 | while (it.hasNext()) { 28 | saveFiches.push(it.next().save()); 29 | } 30 | let data = { 31 | joueurs: saveJoueurs, 32 | fiches: saveFiches, 33 | joueurCourant: GestionJoueur.getJoueurCourant() != null ? GestionJoueur.getJoueurCourant().id:'', 34 | variantes: VARIANTES, 35 | options:plateau.options, 36 | nbTours: globalStats.nbTours, 37 | plateau:plateau.name 38 | }; 39 | this._putStorage(saveName, data); 40 | }, 41 | load: function (name, monopoly) { 42 | this.currentSauvegardeName = name; 43 | let data = this._getStorage(name); 44 | // On charge le plateau 45 | updateVariantes(deepCopy(VARIANTES, data.variantes)); 46 | //VARIANTES = data.variantes || VARIANTES; 47 | monopoly.plateau.load(data.plateau || "data-monopoly.json",data.options,function(){ 48 | data.joueurs.forEach((j,i)=>GestionJoueur.createAndLoad(!j.canPlay, i,j.nom,j,monopoly.plateau.infos.montantDepart)); 49 | data.fiches.forEach(f=>GestionFiche.getById(f.id).load(f)); 50 | bus.refresh(); 51 | globalStats.nbTours = data.nbTours || 0; 52 | monopoly.afterCreateGame(); 53 | GestionJoueur.change(data.joueurCourant); 54 | }); 55 | }, 56 | delete: function (name) { 57 | localStorage.removeItem(name); 58 | }, 59 | _putStorage: function (name, data) { 60 | localStorage[name] = JSON.stringify(data); 61 | }, 62 | _getStorage: function (name) { 63 | if (localStorage[name] == null) { 64 | throw "Aucune sauvegarde"; 65 | } 66 | const data = localStorage[name]; 67 | return JSON.parse(data); 68 | }, 69 | autoSave: function () { 70 | 71 | }, 72 | findSauvegardes: function () { 73 | let exp = "^" + this.prefix + "(.*)" + this.suffix + "$"; 74 | let list = []; 75 | for (let name in localStorage) { 76 | let label = new RegExp(exp, "g").exec(name); 77 | if (label != null) { 78 | list.push({ 79 | value: name, 80 | label: label[1] 81 | }); 82 | } 83 | } 84 | return list; 85 | }, 86 | getSauvegardeName: function (name) { 87 | return this.prefix + ((name == null || name === "") ? new Date().getTime() : name) + this.suffix; 88 | } 89 | }; 90 | 91 | 92 | 93 | export {Sauvegarde}; 94 | -------------------------------------------------------------------------------- /js/entity/cartes_action.js: -------------------------------------------------------------------------------- 1 | /* Cartes actions (chance ou caisse de communaute) */ 2 | 3 | import {GestionJoueur} from "../core/gestion_joueurs.js"; 4 | 5 | class CarteAction{ 6 | constructor(type) { 7 | this.type = type; 8 | } 9 | action(joueur) { 10 | console.error("Not implemented " + joueur,this.type); 11 | } 12 | } 13 | 14 | /* Carte chance : reparations des maisons et hotels */ 15 | class ReparationsCarte extends CarteAction{ 16 | constructor(tarifHotel,tarifMaison) { 17 | super("reparation"); 18 | this.tarifHotel = tarifHotel; 19 | this.tarifMaison = tarifMaison; 20 | } 21 | action(joueur){ 22 | let stats = joueur.getStats(); 23 | let montant = stats.hotel * this.tarifHotel + stats.maison * this.tarifMaison; 24 | joueur.payer(montant,()=>GestionJoueur.change()); 25 | } 26 | } 27 | 28 | /* Carte chance : chaque joueur vous donne 1000 francs */ 29 | class BirthdayCarte extends CarteAction{ 30 | constructor(montant){ 31 | super("birthday"); 32 | this.montant = montant; 33 | } 34 | action(joueur){ 35 | let payers = GestionJoueur.joueurs.filter(j=>!joueur.equals(j) && !j.defaite); 36 | payers.forEach((j,i)=>{ 37 | j.payerTo(this.montant,joueur,()=>{ 38 | if(i === payers.length -1){ 39 | GestionJoueur.change(); 40 | } 41 | }); 42 | }); 43 | } 44 | } 45 | 46 | /* Action de déplacement vers une case */ 47 | /* @param direct : si renseigné a vrai, le pion est deplacé directement vers la case, sans passer par la case depart */ 48 | class GotoCarte extends CarteAction{ 49 | constructor(axe, pos, direct,primeDepart=true) { 50 | super("goto"); 51 | this.fiche = {axe:axe,pos:pos}; 52 | this.direct = direct; 53 | this.primeDepart = primeDepart; 54 | } 55 | action(joueur) { 56 | joueur.joueSurCase(this.fiche,this.direct,this.primeDepart, true); 57 | } 58 | } 59 | 60 | /* Carte sortie de prison */ 61 | class PrisonCarte extends CarteAction { 62 | constructor() { 63 | super("prison"); 64 | this.joueurPossede = null; 65 | } 66 | action(joueur) { 67 | joueur.cartesSortiePrison.push(this); 68 | this.joueurPossede = joueur; 69 | GestionJoueur.change(); 70 | } 71 | isLibre () { 72 | return this.joueurPossede == null; 73 | } 74 | } 75 | 76 | /* Action de déplacement d'un certain nombre de case */ 77 | class MoveNbCarte extends CarteAction { 78 | constructor(nb,direct=true,primeDepart=true) { 79 | super("move"); 80 | this.nb = nb; 81 | this.direct = direct; 82 | // If false, no prime on start case 83 | this.primeDepart = primeDepart; 84 | } 85 | action(joueur) { 86 | let pos = joueur.pion.deplaceValeursDes(this.nb); 87 | joueur.joueSurCase(pos,this.direct,this.primeDepart); 88 | } 89 | } 90 | 91 | /* Action de gain d'argent pour une carte */ 92 | class PayerCarte extends CarteAction { 93 | constructor(montant,plateauMonopoly) { 94 | super("taxe"); 95 | this.plateauMonopoly = plateauMonopoly; 96 | this.montant = montant; 97 | } 98 | action(joueur) { 99 | joueur.payerParcGratuit(this.plateauMonopoly.parcGratuit,this.montant, ()=>GestionJoueur.change()); 100 | } 101 | } 102 | 103 | /* Action de perte d'argent pour une carte */ 104 | class GagnerCarte extends CarteAction { 105 | constructor(montant) { 106 | super("prime"); 107 | this.montant = montant; 108 | } 109 | action(joueur){ 110 | joueur.gagner(this.montant); 111 | GestionJoueur.change(); 112 | } 113 | } 114 | 115 | let CarteActionFactory = { 116 | get:function(data, plateauMonopoly) { 117 | switch (data.type) { 118 | /* Amende a payer */ 119 | case "taxe": 120 | return new PayerCarte(data.montant,plateauMonopoly); 121 | /* Argent a toucher */ 122 | case "prime": 123 | return new GagnerCarte(data.montant); 124 | /* Endroit ou aller */ 125 | case "goto": 126 | return new GotoCarte(data.axe, data.pos, data.direct,data.primeDepart); 127 | /* Deplacement a effectuer */ 128 | case "move": 129 | return new MoveNbCarte(data.nb,data.direct,data.primeDepart); 130 | /* Carte prison */ 131 | case "prison": 132 | return new PrisonCarte(); 133 | /* Carte anniversaire */ 134 | case "birthday": 135 | return new BirthdayCarte(data.montant); 136 | case "repair": 137 | return new ReparationsCarte(data.hotel,data.maison) 138 | } 139 | throw "Type inconnu"; 140 | } 141 | }; 142 | 143 | export {CarteActionFactory}; 144 | 145 | -------------------------------------------------------------------------------- /data/data-monopoly-junior.json: -------------------------------------------------------------------------------- 1 | { 2 | "plateau":{ 3 | "subtitle":"Classique", 4 | "backgroundColor": "#cee6d0", 5 | "textColor": "#000000", 6 | "nomsJoueurs":["Gluglu","Yoyo","Bibou","Sorceline"], 7 | "colors":["#319c39","#3a56a4","#9f3338","#cfd4d0"], 8 | "nbCases": 6, 9 | "argent": 31, 10 | "depart": 2, 11 | "prison": 1, 12 | "hideConstructions": true 13 | }, 14 | "background-image":"", 15 | "currency": "M", 16 | "fiches": [ 17 | {"type": "depart","axe": 2,"pos": 0,"nom": "Départ"}, 18 | {"type": "propriete-junior","groupe": "Violet","axe": 2,"pos": 1,"nom": "Bus de Ninjaka","colors": ["#964526", "#cca494"],"prix": 1}, 19 | {"type": "propriete-junior","groupe": "Violet","axe": 2,"pos": 2,"nom": "Ninjaka","colors": ["#964526", "#cca494"],"prix": 1}, 20 | {"type": "chance","axe": 2,"pos": 3}, 21 | {"type": "propriete-junior","groupe": "Bleu clair","axe": 2,"pos": 4,"nom": "Labo","colors": ["#bbeefe", "#d2f1fb"],"prix": 1}, 22 | {"type": "propriete-junior","groupe": "Bleu clair","axe": 2,"pos": 5,"nom": "Roméo","colors": ["#bbeefe", "#d2f1fb"],"prix": 1}, 23 | {"type": "special","axe": 3,"pos": 0,"nom": "Simple visite"}, 24 | {"type": "propriete-junior","groupe": "Violet clair","axe": 3,"pos": 1,"nom": "Aéro-lune","colors": ["#e0338a", "#df83b1"],"prix": 2}, 25 | {"type": "propriete-junior","groupe": "Violet clair","axe": 3,"pos": 2,"nom": "Sorceline","colors": ["#e0338a", "#df83b1"],"prix": 2}, 26 | {"type": "chance","axe": 3,"pos": 3}, 27 | {"type": "propriete-junior","groupe": "Orange","axe": 3,"pos": 4,"nom": "Pyja-robot","colors": ["#ff9500", "#ffb854"],"prix": 2}, 28 | {"type": "propriete-junior","groupe": "Orange","axe": 3,"pos": 5,"nom": "Ninjazouaves","colors": ["#ff9500", "#ffb854"],"prix": 2}, 29 | {"type": "parc","axe": 0,"pos": 0,"nom": "Parc Gratuit"}, 30 | {"type": "propriete-junior","groupe": "Rouge","axe": 0,"pos": 1,"nom": "Astro-Hibou","colors": ["#d90e22", "#e05462"],"prix": 3}, 31 | {"type": "propriete-junior","groupe": "Rouge","axe": 0,"pos": 2,"nom": "Bibou","colors": ["#d90e22", "#e05462"],"prix": 3}, 32 | {"type": "chance","axe": 0,"pos": 3}, 33 | {"type": "propriete-junior","groupe": "Jaune","axe": 0,"pos": 4,"nom": "Spatio-totem","colors": ["#ffef01", "#fff77e"],"prix": 3}, 34 | {"type": "propriete-junior","groupe": "Jaune","axe": 0,"pos": 5,"nom": "QG","colors": ["#ffef01", "#fff77e"],"prix": 3}, 35 | {"type": "prison","axe": 1,"pos": 0,"nom": "Allez en prison"}, 36 | {"type": "propriete-junior","groupe": "Vert","axe": 1,"pos": 1,"nom": "Reptilo-mobile","colors": ["#08ab47", "#6aad84"],"prix": 4}, 37 | {"type": "propriete-junior","groupe": "Vert","axe": 1,"pos": 2,"nom": "Gluglu","colors": ["#08ab47", "#6aad84"],"prix": 4}, 38 | {"type": "chance","axe": 1,"pos": 3}, 39 | {"type": "propriete-junior","groupe": "Bleu","axe": 1,"pos": 4,"nom": "Chat-bolide","colors": ["#0066bb", "#5a93c3"],"prix": 5}, 40 | {"type": "propriete-junior","groupe": "Bleu","axe": 1,"pos": 5,"nom": "Yoyo","colors": ["#0066bb", "#5a93c3"],"prix": 5} 41 | ], 42 | "images":{ 43 | "chance":{ 44 | "src": "img/interrogation.png", 45 | "width": 50, 46 | "height": 60 47 | } 48 | }, 49 | "titles":{ 50 | "chance":"Chance" 51 | }, 52 | "chance":{ 53 | "cartes":[ 54 | {"nom":"C'est votre anniversaire, chaque joueur vous donne F 1.000","type":"birthday","montant":1000}, 55 | {"nom":"Faites des réparations dans toutes vos maisons : F 11.500 par hôtel et F 4.500 par maison","type":"repair","hotel":11500,"maison":4500}, 56 | {"nom":"Payer pour frais de scolarité F 15.000","type":"taxe","montant":15000}, 57 | {"nom":"Amende pour excès de vitesse : F 1.500","type":"taxe","montant":1500}, 58 | {"nom":"Vous avez gagné le prix de mots croisés. Recevez F 10.000","type":"prime","montant":10000}, 59 | {"nom":"La banque vous verse un dividende de F 5.000","type":"prime","montant":5000}, 60 | {"nom":"Votre immeuble et votre prêt rapportent. Vous devez toucher F 15.000","type":"prime","montant":15000}, 61 | {"nom":"Amende pour ivresse : F 2.000","type":"taxe","montant":2000}, 62 | {"nom":"Rendez vous à l'avenue Henri-Martin. Si vous passez par la case Départ, recevez F 20.000","type":"goto","axe":0,"pos":4}, 63 | {"nom":"Rendez vous à la Rue de la Paix","type":"goto","axe":1,"pos":9}, 64 | {"nom":"Reculez de 3 cases","type":"move","nb":-3}, 65 | {"nom":"Avancez jusqu'à la case Départ","type":"goto","axe":2,"pos":0}, 66 | {"nom":"Rendez vous à la gare de Lyon. Si vous passez par la case Départ, recevez F 20.000","type":"goto","axe":3,"pos":5}, 67 | {"nom":"Avancez au Boulevard de la Villette. Si vous passez par la case Départ, recevez F 20.000","type":"goto","axe":3,"pos":1}, 68 | {"nom":"Allez en prison. Rendez vous directement à la prison. Ne franchissez par la case Départ. Ne touchez pas F 20.000","type":"goto","axe":1,"pos":0,"direct":true} 69 | ] 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/run_monopoly.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/hex" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "github.com/jotitan/monopoly/dice" 10 | "io/ioutil" 11 | "log" 12 | "math/rand" 13 | "net/http" 14 | "os" 15 | "path/filepath" 16 | "strconv" 17 | "strings" 18 | "time" 19 | ) 20 | 21 | var resources string 22 | 23 | func main() { 24 | if len(os.Args) != 2 { 25 | log.Fatal("Impossible to start server, need 1 argument : resources folder", os.Args) 26 | return 27 | } 28 | resources = os.Args[1] 29 | runServer() 30 | } 31 | 32 | func runServer() { 33 | server := http.NewServeMux() 34 | server.HandleFunc("/dices", runDice) 35 | server.HandleFunc("/createGame", createGame) 36 | server.HandleFunc("/game/exist", isGameExists) 37 | server.HandleFunc("/event", manageEvent) 38 | server.HandleFunc("/connect", connectGame) 39 | server.HandleFunc("/", root) 40 | 41 | log.Println("Run on 8100") 42 | http.ListenAndServe(":8100", server) 43 | } 44 | 45 | // Launch some dices and return results 46 | func runDice(w http.ResponseWriter, r *http.Request) { 47 | w.Header().Set("Access-Control-Allow-Origin", "*") 48 | w.Header().Set("Content-type", "application/json") 49 | 50 | if nb, err := strconv.Atoi(r.FormValue("nb")); err == nil { 51 | data, _ := json.Marshal(dice.Throw(int(nb))) 52 | w.Write(data) 53 | } 54 | } 55 | 56 | // Serve files 57 | func root(response http.ResponseWriter, request *http.Request) { 58 | if strings.HasSuffix(request.RequestURI[1:], ".js") { 59 | response.Header().Set("Content-Type", "text/javascript") 60 | } 61 | http.ServeFile(response, request, filepath.Join(resources, request.RequestURI[1:])) 62 | } 63 | 64 | func generateGameId() string { 65 | d := []byte(time.Now().Format("20060102150405")) 66 | r := rand.Uint64() 67 | data := make([]byte, 8) 68 | binary.LittleEndian.PutUint64(data, r) 69 | d = append(d, data...) 70 | return hex.EncodeToString(data) 71 | } 72 | 73 | func isGameExists(w http.ResponseWriter, r *http.Request) { 74 | gameID := r.FormValue("id") 75 | _, exist := games[gameID] 76 | w.Write([]byte(fmt.Sprintf("%t", exist))) 77 | } 78 | 79 | func createGame(w http.ResponseWriter, r *http.Request) { 80 | gameID := generateGameId() 81 | games[gameID] = &Game{ID: gameID, Players: make(map[string]chan []byte), counterID: 0} 82 | w.Header().Set("Content-type", "application/json") 83 | w.Write([]byte(fmt.Sprintf("{\"game\":\"%s\"}", gameID))) 84 | } 85 | 86 | // Receive an event from a player, send to others, avoid launcher 87 | // Two parameters is needed : game id and player sender id 88 | func manageEvent(w http.ResponseWriter, r *http.Request) { 89 | w.Header().Set("Access-Control-Allow-Origin", "*") 90 | if data, err := ioutil.ReadAll(r.Body); err == nil { 91 | gameID := r.FormValue("game") 92 | playerID := r.FormValue("playerID") 93 | // Dispatch event to all others gamer 94 | if err := dispatchEventToPlayers(gameID, playerID, data); err != nil { 95 | http.Error(w, err.Error(), 404) 96 | } else { 97 | w.Write([]byte("{}")) 98 | } 99 | } else { 100 | http.Error(w, "Unknown event", 404) 101 | } 102 | } 103 | 104 | func dispatchEventToPlayers(gameID, playerID string, event []byte) error { 105 | if game, exist := games[gameID]; exist { 106 | for player, chanel := range game.Players { 107 | if !strings.EqualFold(playerID, player) { 108 | chanel <- event 109 | } 110 | } 111 | return nil 112 | } 113 | return errors.New("Game with id " + gameID + " not exist") 114 | } 115 | 116 | func connectGame(w http.ResponseWriter, r *http.Request) { 117 | w.Header().Set("Content-Type", "text/event-stream") 118 | w.Header().Set("Cache-Control", "no-cache") 119 | w.Header().Set("Connection", "keep-alive") 120 | w.Header().Set("Access-Control-Allow-Origin", "*") 121 | 122 | gameID := r.FormValue("game") 123 | uniquePlayer := r.FormValue("player") 124 | if game, exist := games[gameID]; exist { 125 | game.counterID++ 126 | playerID := fmt.Sprintf("player_%d", game.counterID) 127 | game.Players[playerID] = make(chan []byte, 10) 128 | if strings.EqualFold("", uniquePlayer) { 129 | writeEvent(w, "welcome", []byte("{\"message\":\"Start game\",\"playerID\":\""+playerID+"\"}")) 130 | } else { 131 | writeEvent(w, "welcome", []byte(fmt.Sprintf("{\"message\":\"Rejoin\",\"playerID\":\"%s\",\"uniquePlayerID\":\"%s\"}", playerID, uniquePlayer))) 132 | } 133 | go func() { 134 | <-r.Context().Done() 135 | close(game.Players[playerID]) 136 | game.Players[playerID] = nil 137 | delete(game.Players, playerID) 138 | // If all players are disconnected, remove game 139 | if len(game.Players) == 0 { 140 | log.Println("Remove game", gameID) 141 | delete(games, gameID) 142 | } else { 143 | // notify players for disconnect 144 | dispatchEventToPlayers(gameID, "", []byte("{\"kind\":\"exit\",\"player\":\""+playerID+"\"}")) 145 | } 146 | }() 147 | for { 148 | if event, more := <-game.Players[playerID]; more { 149 | writeEvent(w, "event", event) 150 | } else { 151 | // Close the chanel 152 | log.Println("Close the chanel") 153 | break 154 | } 155 | } 156 | } else { 157 | http.Error(w, "No game exist", 404) 158 | } 159 | } 160 | 161 | func writeEvent(w http.ResponseWriter, eventName string, event []byte) { 162 | log.Println(fmt.Sprintf("Send %s : %s", eventName, string(event))) 163 | w.Write([]byte(fmt.Sprintf("event: %s\n", eventName))) 164 | w.Write([]byte("data: " + string(event) + "\n\n")) 165 | w.(http.Flusher).Flush() 166 | 167 | } 168 | 169 | var games = make(map[string]*Game, 0) 170 | var counterGame = 0 171 | 172 | // Game represent a monopoly game 173 | type Game struct { 174 | ID string 175 | // For each player, a chanel to send information. Key a map is player id 176 | Players map[string]chan []byte 177 | counterID int 178 | } 179 | -------------------------------------------------------------------------------- /css/qunit-1.12.0.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.12.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 5px 5px 0 0; 42 | -moz-border-radius: 5px 5px 0 0; 43 | -webkit-border-top-right-radius: 5px; 44 | -webkit-border-top-left-radius: 5px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-testrunner-toolbar label { 58 | display: inline-block; 59 | padding: 0 .5em 0 .1em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | overflow: hidden; 71 | } 72 | 73 | #qunit-userAgent { 74 | padding: 0.5em 0 0.5em 2.5em; 75 | background-color: #2b81af; 76 | color: #fff; 77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 78 | } 79 | 80 | #qunit-modulefilter-container { 81 | float: right; 82 | } 83 | 84 | /** Tests: Pass/Fail */ 85 | 86 | #qunit-tests { 87 | list-style-position: inside; 88 | } 89 | 90 | #qunit-tests li { 91 | padding: 0.4em 0.5em 0.4em 2.5em; 92 | border-bottom: 1px solid #fff; 93 | list-style-position: inside; 94 | } 95 | 96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 97 | display: none; 98 | } 99 | 100 | #qunit-tests li strong { 101 | cursor: pointer; 102 | } 103 | 104 | #qunit-tests li a { 105 | padding: 0.5em; 106 | color: #c2ccd1; 107 | text-decoration: none; 108 | } 109 | #qunit-tests li a:hover, 110 | #qunit-tests li a:focus { 111 | color: #000; 112 | } 113 | 114 | #qunit-tests li .runtime { 115 | float: right; 116 | font-size: smaller; 117 | } 118 | 119 | .qunit-assert-list { 120 | margin-top: 0.5em; 121 | padding: 0.5em; 122 | 123 | background-color: #fff; 124 | 125 | border-radius: 5px; 126 | -moz-border-radius: 5px; 127 | -webkit-border-radius: 5px; 128 | } 129 | 130 | .qunit-collapsed { 131 | display: none; 132 | } 133 | 134 | #qunit-tests table { 135 | border-collapse: collapse; 136 | margin-top: .2em; 137 | } 138 | 139 | #qunit-tests th { 140 | text-align: right; 141 | vertical-align: top; 142 | padding: 0 .5em 0 0; 143 | } 144 | 145 | #qunit-tests td { 146 | vertical-align: top; 147 | } 148 | 149 | #qunit-tests pre { 150 | margin: 0; 151 | white-space: pre-wrap; 152 | word-wrap: break-word; 153 | } 154 | 155 | #qunit-tests del { 156 | background-color: #e0f2be; 157 | color: #374e0c; 158 | text-decoration: none; 159 | } 160 | 161 | #qunit-tests ins { 162 | background-color: #ffcaca; 163 | color: #500; 164 | text-decoration: none; 165 | } 166 | 167 | /*** Test Counts */ 168 | 169 | #qunit-tests b.counts { color: black; } 170 | #qunit-tests b.passed { color: #5E740B; } 171 | #qunit-tests b.failed { color: #710909; } 172 | 173 | #qunit-tests li li { 174 | padding: 5px; 175 | background-color: #fff; 176 | border-bottom: none; 177 | list-style-position: inside; 178 | } 179 | 180 | /*** Passing Styles */ 181 | 182 | #qunit-tests li li.pass { 183 | color: #3c510c; 184 | background-color: #fff; 185 | border-left: 10px solid #C6E746; 186 | } 187 | 188 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 189 | #qunit-tests .pass .test-name { color: #366097; } 190 | 191 | #qunit-tests .pass .test-actual, 192 | #qunit-tests .pass .test-expected { color: #999999; } 193 | 194 | #qunit-banner.qunit-pass { background-color: #C6E746; } 195 | 196 | /*** Failing Styles */ 197 | 198 | #qunit-tests li li.fail { 199 | color: #710909; 200 | background-color: #fff; 201 | border-left: 10px solid #EE5757; 202 | white-space: pre; 203 | } 204 | 205 | #qunit-tests > li:last-child { 206 | border-radius: 0 0 5px 5px; 207 | -moz-border-radius: 0 0 5px 5px; 208 | -webkit-border-bottom-right-radius: 5px; 209 | -webkit-border-bottom-left-radius: 5px; 210 | } 211 | 212 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 213 | #qunit-tests .fail .test-name, 214 | #qunit-tests .fail .module-name { color: #000000; } 215 | 216 | #qunit-tests .fail .test-actual { color: #EE5757; } 217 | #qunit-tests .fail .test-expected { color: green; } 218 | 219 | #qunit-banner.qunit-fail { background-color: #EE5757; } 220 | 221 | 222 | /** Result */ 223 | 224 | #qunit-testresult { 225 | padding: 0.5em 0.5em 0.5em 2.5em; 226 | 227 | color: #2b81af; 228 | background-color: #D2E0E6; 229 | 230 | border-bottom: 1px solid white; 231 | } 232 | #qunit-testresult .module-name { 233 | font-weight: bold; 234 | } 235 | 236 | /** Fixture */ 237 | 238 | #qunit-fixture { 239 | position: absolute; 240 | top: -10000px; 241 | left: -10000px; 242 | width: 1000px; 243 | height: 1000px; 244 | } 245 | -------------------------------------------------------------------------------- /poc/slider.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Bonjour

12 | 13 |
14 | 15 |
16 | 17 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /poc/bingo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | Tirage 12 | Boule 13 | Tri 14 | Table 15 |
16 |
17 | 21 |
22 |
23 | 24 | 83 | 84 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /js/core/gestion_constructions.js: -------------------------------------------------------------------------------- 1 | /* Gere les reserves de constructions (maison / hotel) */ 2 | let GestionConstructions = { 3 | nbInitHouse: 32, 4 | nbInitHotel: 12, 5 | nbSellHouse: 0, 6 | nbSellHotel: 0, 7 | reset: function () { 8 | this.nbInitHouse = 32; 9 | this.nbInitHotel = 12; 10 | this.nbSellHouse = 0; 11 | this.nbSellHotel = 0; 12 | }, 13 | isFreeHouse: function () { 14 | return this.nbInitHouse > this.nbSellHouse; 15 | }, 16 | isFreeHotel: function (nbActualHouse) { 17 | if (nbActualHouse == null) { 18 | return this.nbInitHotel > this.nbSellHotel; 19 | } 20 | const needHouse = 4 - nbActualHouse; 21 | return this.nbInitHotel > this.nbSellHotel & this.nbInitHouse >= this.nbSellHouse + needHouse; 22 | }, 23 | getRestHouse: function () { 24 | return this.nbInitHouse - this.nbSellHouse; 25 | }, 26 | getRestHotel: function () { 27 | return this.nbInitHotel - this.nbSellHotel; 28 | }, 29 | sellHotel:function(){ 30 | this.nbSellHotel--; 31 | }, 32 | sellHouse:function(){ 33 | this.nbSellHouse--; 34 | }, 35 | buyHouse: function () { 36 | if (!this.isFreeHouse()) { 37 | throw "Impossible d'acheter une maison." 38 | } 39 | this.nbSellHouse++; 40 | }, 41 | buyHouses: function (nb) { 42 | if (nb > this.getRestHouse()) { 43 | throw "Impossible d'acheter les maisons." 44 | } 45 | this.nbSellHouse += nb; 46 | }, 47 | /* Achete / vend le nombre d'hotels indiques (verification simple sur le nombre d'hotel) */ 48 | buyHotels: function (nb) { 49 | if (nb > this.getRestHotel()) { 50 | throw "Impossible d'acheter les hotels." 51 | } 52 | this.nbSellHotel += nb; 53 | }, 54 | buyHotel: function () { 55 | if (!this.isFreeHouse()) { 56 | throw "Impossible d'acheter un hotel." 57 | } 58 | this.nbSellHotel++; 59 | // On libere les maisons liees (4) 60 | this.nbSellHouse -= 4; 61 | }, 62 | /* Calcule les restes finaux en maison / hotel. Renvoie egalement le delta */ 63 | // On calcule en premier les ventes de maisons 64 | // On calcule les achats d'hotels (qui necessitent des maisons puis des hotels) 65 | // Pour finir, on calcule l'achat de maison 66 | // Chaque projet contient la couleur du groupe, le from (nb, type) et le to (nb, type) 67 | simulateBuy: function (projects) { 68 | // Projects est un tableau de from:{type,nb},to:{type,nb} 69 | let simulation = { 70 | achat: { 71 | maison: 0, 72 | hotel: 0 73 | }, 74 | reste: { 75 | maison: this.nbInitHouse - this.nbSellHouse, 76 | hotel: this.nbInitHotel - this.nbSellHotel 77 | } 78 | }; 79 | let actions = { 80 | venteMaison: function (p, simulation) { 81 | if (p.from.type === "maison" && p.to.type === "maison" && p.from.nb > p.to.nb) { 82 | changeMaison(p,simulation); 83 | } 84 | }, 85 | achatHotel: function (p, simulation) { 86 | // Pour valider un hotel, il faut que les autres proprietes aient au moins 4 maisons. On les achete maintenant si on les trouve 87 | if (p.from.type === "maison" && p.to.type === "hotel") { 88 | // On achete les maisons sur le meme groupe s'il y en a 89 | for (let project in projects) { 90 | let p2 = projects[project]; 91 | if (p2.color === p.color && p2.from.type === "maison" && p2.to.type === "maison" && p2.to.nb === 4) { 92 | // On les achete maintenant 93 | actions.achatMaison(p2, simulation); 94 | } 95 | } 96 | // Verifie qu'il y a assez de maison disponible 97 | const resteMaison = 4 - p.from.nb; 98 | if (resteMaison > simulation.reste.maison) { 99 | // Impossible, pas assez de maison, on renvoie un nombre de maison negatif et on sort 100 | simulation.reste.maison -= resteMaison; 101 | return; 102 | } else { 103 | // On achete un hotel 104 | simulation.reste.hotel--; 105 | simulation.achat.hotel++; 106 | simulation.reste.maison += p.from.nb; 107 | simulation.achat.maison -= p.from.nb; 108 | } 109 | p.done = true; 110 | } 111 | }, 112 | venteHotel: function (p, simulation) { 113 | if (p.from.type === "hotel" && p.to.type === "maison") { 114 | // Verifie qu'il y a assez de maison disponible 115 | if (p.to.nb > simulation.reste.maison) { 116 | // Impossible, pas assez de maison, on renvoie un nombre de maison negatif et on sort 117 | simulation.reste.maison -= p.to.nb; 118 | return; 119 | } else { 120 | // On vend l'hotel et on place des maisons 121 | simulation.reste.hotel++; 122 | simulation.achat.hotel--; 123 | simulation.reste.maison -= p.to.nb; 124 | simulation.achat.maison += p.to.nb; 125 | } 126 | p.done = true; 127 | } 128 | }, 129 | achatMaison: function (p, simulation) { 130 | if (p.from.type === "maison" && p.to.type === "maison" && p.from.nb < p.to.nb) { 131 | changeMaison(p,simulation); 132 | } 133 | } 134 | }; 135 | let changeMaison = (p,simulation)=>{ 136 | let nb = p.from.nb - p.to.nb; 137 | simulation.achat.maison -= nb; 138 | simulation.reste.maison += nb; 139 | p.done = true; 140 | } 141 | for (let a in actions) { 142 | let action = actions[a]; 143 | for (let index in projects) { 144 | let p = projects[index]; 145 | if (p.done == null) { 146 | try { 147 | action(p, simulation); 148 | } catch (e) { 149 | // Exception levee si le traitement doit etre interrompu 150 | console.log("exception",e); 151 | return simulation; 152 | } 153 | } 154 | } 155 | } 156 | return simulation; 157 | } 158 | } 159 | 160 | export {GestionConstructions}; -------------------------------------------------------------------------------- /test/test-maisons-js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test Monopoly 4 | 5 | 6 | 7 | 8 | 199 | 200 | 201 | Lien jeu 202 |
203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /js/entity/comportement.js: -------------------------------------------------------------------------------- 1 | /* Gestion du comportement*/ 2 | 3 | import {GestionFiche} from "../display/case_jeu.js"; 4 | import {GestionJoueur} from "../core/gestion_joueurs.js"; 5 | 6 | /* @Abstract */ 7 | /* Objet qui gere le comportement (rapport a l'argent). Integre la prise de risque (position du jour) */ 8 | /* @risque : prise de risque entre 0 et 1 */ 9 | 10 | class Comportement { 11 | constructor(risque, name, id) { 12 | this.risque = risque; 13 | this.probaDes = [0, 2.77, 5.55, 8.33, 11.1, 13.8, 16.7, 13.8, 11.1, 8.33, 5.55, 2.77]; 14 | this.name = name; 15 | this.id = id; 16 | } 17 | 18 | /* Indique le risque global a depenser cette somme pour le joueur */ 19 | /* Se base sur 3 informations : 20 | 1 : le montant a depenser par rapport a l'argent disponible. 21 | 2 : le risque de tomber prochainement sur un loyer eleve 22 | 3 : le cout du plus fort loyer du plateau 23 | Plus le risque est grand, plus il est important 24 | */ 25 | getRisqueTotal(joueur, cout) { 26 | let risque1 = this.calculMargeMontant(joueur, cout); 27 | let risque2 = this.calculRisque(joueur, joueur.montant); 28 | 29 | return risque1 * (risque2 / 100 + 1); 30 | } 31 | 32 | /* Determine le budget max pour un indicateur de strategie donne */ 33 | /* Plafonne l'enchere max (comme les propositions ?) */ 34 | getMaxBudgetForStrategie (joueur, strategieValue) { 35 | // Renvoie la valeur du cout pour que getRisqueTotal = strategieValue 36 | let risque = this.calculRisque(joueur, joueur.montant); 37 | let marge = strategieValue / (risque / 100 + 1); 38 | // On ajoute un random sur 10% du prix => moins previsible 39 | let montant = this.findCoutFromFixMarge(joueur, marge); 40 | montant+=Math.round((montant*0.1)*(((Math.random()* 1000)%10 -5)/10)); 41 | return Math.min(montant, joueur.montant - joueur.minimumPriceHouse()); 42 | } 43 | 44 | /* Appele lorsqu'une proposition a deja ete faite et qu'elle etait insuffisante */ 45 | getFactorForProposition() { 46 | return 1 + this.risque; 47 | } 48 | 49 | /* Calcul le budget depensable pour la construction de maison / hotel */ 50 | /* Prendre en compte l'achat potentiel de nouveau terrain. Pour la strategie, on calcule les terrains qui interessent */ 51 | /* @param forceHypotheque : si vrai, on force l'usage de l'argent dispo apres hypotheque */ 52 | getBudget (joueur, forceHypotheque) { 53 | let assiette = joueur.montant; // Utilise pour calculer les risques 54 | // Si le joueur est une charogne, on utilise l'argent dispo avec les possibles hypotheques (tous les terrains sauf les groupes). 55 | // Utilise uniquement pour le calcul de risque, pas pour l'achat (pour ne pas hypothequer lors de l'achat). 56 | if (forceHypotheque === true || this.risque > 0.6) { 57 | assiette = joueur.getStats().argentDispoHypo; 58 | } 59 | // On prend le plus fort loyer du plateau 60 | let maxLoyer = this.plusFortLoyer(joueur); 61 | // On prend l'argent pondere par le risque 62 | let risque = this.calculRisque(joueur, assiette); 63 | // On pondere le loyer max par le carre du risque afin d'augmenter exponentiellement son importance 64 | return Math.round((joueur.montant - maxLoyer * (1 - this.risque * this.risque)) * (1 - risque / 100)); 65 | } 66 | 67 | /* Calcul le terrain du joueur sur lesquels les adversaires peuvent tomber */ 68 | /* @param seuil : seuil a partir duquel on renvoie les maisons */ 69 | getNextProprietesVisitees (joueur) { //,seuil){ 70 | let maisons = []; 71 | GestionJoueur.forEach(j=>{ 72 | if (!j.equals(joueur)) { 73 | // On parcours toutes les statistiques et on mesure le risque de tomber sur une propriete du joueur 74 | for (let i = 1; i < 12; i++) { 75 | let fiche = GestionFiche.get(j.pion.deplaceValeursDes(i)); 76 | if (fiche.isTerrain() && fiche.joueurPossede != null && fiche.joueurPossede.equals(joueur)) { 77 | //maison visitable, on ajoute la maison avec la proba 78 | if (maisons[fiche.id] != null) { 79 | maisons[fiche.id].proba += this.probaDes[i] / 100; 80 | } else { 81 | maisons[fiche.id] = ({ 82 | proba: this.probaDes[i] / 100, 83 | maison: fiche 84 | }); 85 | } 86 | } 87 | } 88 | } 89 | },this); 90 | return maisons; 91 | } 92 | 93 | /* Calcule la marge d'achat par rapport au montant et le pondere par rapport a la prise de risque. */ 94 | /* Plus il est grand, plus c'est risque */ 95 | calculMargeMontant (joueur, cout) { 96 | let marge = cout / joueur.montant; // inferieur a 1 97 | return marge / this.risque; 98 | } 99 | 100 | /* Calcul le cout pour une marge donnee */ 101 | findCoutFromFixMarge (joueur, marge) { 102 | return (marge * this.risque) * joueur.montant; 103 | } 104 | 105 | /* Se base sur les prochaines cases a risque qui arrive, renvoi un pourcentage */ 106 | calculRisque (joueur, argent) { 107 | // On calcul le risque de tomber sur une case cher. 108 | // On considere un risque quand on est au dessus de risque * montant d'amende) 109 | let position = joueur.pion.position; 110 | let axe = joueur.pion.axe; 111 | let stats = 0; 112 | for (let i = 1; i <= 12; i++) { 113 | let pos = GestionFiche.nextPos(axe, position); 114 | axe = pos.axe; 115 | position = pos.position; 116 | let fiche = GestionFiche.getById(axe + "-" + position); 117 | if (fiche != null && fiche.getLoyer != null && fiche.joueurPossede != null && !fiche.joueurPossede.equals(joueur) && (fiche.getLoyer() > (argent * this.risque))) { 118 | stats += this.probaDes[i - 1]; 119 | } 120 | } 121 | return stats; 122 | } 123 | 124 | // calcul le loyer le plus fort du joueur (et n'appartenant pas au joueur). Permet de connaitre la treso max que le joueur peut posseder sur lui 125 | plusFortLoyer (joueur) { 126 | let max = joueur.montantDepart; // Prix de l'impot sur le revenu, comme le depart 127 | let it = GestionFiche.iteratorTerrains(); 128 | while (it.hasNext()) { 129 | let f = it.next(); 130 | if (f.getLoyer != null && f.joueurPossede != null && !joueur.equals(f.joueurPossede) && f.getLoyer() > max) { 131 | max = f.getLoyer(); 132 | } 133 | } 134 | return max; 135 | } 136 | 137 | // calcul le loyer moyen que peut rencontrer le joueur 138 | getLoyerMoyen (joueur) { 139 | let montant = joueur.montantDepart; // Prix de l'impot sur le revenu, comme le depart 140 | let nb = 1; 141 | let it = GestionFiche.iteratorTerrains(); 142 | while (it.hasNext()) { 143 | let f = it.next(); 144 | if (f.getLoyer != null && f.joueurPossede != null && !joueur.equals(f.joueurPossede)) { 145 | montant += f.getLoyer(); 146 | nb++; 147 | } 148 | } 149 | return { 150 | montant: montant / nb, 151 | nb: nb 152 | }; 153 | } 154 | } 155 | 156 | /* Implementations */ 157 | 158 | class CheapComportement extends Comportement { 159 | constructor(){ 160 | super(0.25, "Prudent", 0); 161 | } 162 | } 163 | 164 | class MediumComportement extends Comportement { 165 | constructor() { 166 | super(0.5, "Normal", 1); 167 | } 168 | } 169 | 170 | class HardComportement extends Comportement { 171 | constructor() { 172 | super(0.8, "Fou", 2); 173 | } 174 | } 175 | 176 | let GestionComportement = { 177 | comportements : [CheapComportement,MediumComportement,HardComportement], 178 | create:function(id){ 179 | return new this.comportements[id](); 180 | }, 181 | createRandom:function(){ 182 | return this.create(Math.round(Math.random() * 1000)%this.comportements.length); 183 | }, 184 | getAll:function(){ 185 | return this.comportements; 186 | }, 187 | length:function(){ 188 | return this.comportements.length; 189 | } 190 | }; 191 | 192 | export {GestionComportement}; -------------------------------------------------------------------------------- /data/data-monopoly-jamesbond.json: -------------------------------------------------------------------------------- 1 | { 2 | "plateau":{ 3 | "subtitle":"James Bond Edition", 4 | "textColor":"white", 5 | "backgroundColor": "#999999", 6 | "background-image":"", 7 | "nomsJoueurs":["Daniel Craig","Roger Moore","Sean Connery","Timothy Dalton","Pierce Brosnan","Georges Lazenby"], 8 | "rollColor":"#444444", 9 | "depart":20000 10 | }, 11 | "currency": "£", 12 | "fiches": [ 13 | {"type": "depart","axe": 2,"pos": 0,"nom": "Départ"}, 14 | {"type": "propriete","groupe": "Violet","axe": 2,"pos": 1,"nom": "Docteur No","colors": ["#812B5C", "#DFA4C6"],"prix": 4000,"loyers": [200,1000,3000,9000,16000,25000],"prixMaison": 5000}, 15 | {"type": "communaute","axe": 2,"pos": 2}, 16 | {"type": "propriete","groupe": "Violet","axe": 2,"pos": 3,"nom": "Bons baisers de Russie","colors": ["#812B5C", "#DFA4C6"],"prix": 8000,"loyers": [400, 2000, 6000, 18000, 32000, 45000],"prixMaison": 5000}, 17 | {"type": "taxe","axe": 2,"pos": 4,"nom": "Impots sur le revenu","prix": 20000}, 18 | {"type": "gare","axe": 2,"pos": 5,"nom": "Aston Martin DB5","colors": ["#000000", "#ABABAB"],"prix": 20000,"loyers": [2500, 5000, 10000, 20000]}, 19 | {"type": "propriete","groupe": "Bleu clair","axe": 2,"pos": 6,"nom": "Goldfinger","colors": ["#119AEB", "#98CEEE"],"prix": 10000,"loyers": [600, 3000, 9000, 27000, 40000, 55000],"prixMaison": 5000}, 20 | {"type": "chance","axe": 2,"pos": 7}, 21 | {"type": "propriete","groupe": "Bleu clair","axe": 2,"pos": 8,"nom": "Opération Tonnerre","colors": ["#119AEB", "#98CEEE"],"prix": 10000,"loyers": [600, 3000, 9000, 27000, 40000, 55000],"prixMaison": 5000}, 22 | {"type": "propriete","groupe": "Bleu clair","axe": 2,"pos": 9,"nom": "On ne vit que deux fois","colors": ["#119AEB", "#98CEEE"],"prix": 12000,"loyers": [800, 4000, 10000, 30000, 45000, 60000],"prixMaison": 5000}, 23 | {"type": "special","axe": 3,"pos": 0,"nom": "Simple visite"}, 24 | {"type": "propriete","groupe": "Violet clair","axe": 3,"pos": 1,"nom": "Au Service secret de sa Majesté","colors": ["#73316F", "#DFB4DC"],"prix": 14000,"loyers": [1000, 5000, 15000, 45000, 62500, 75000],"prixMaison": 10000}, 25 | {"type": "compagnie","axe": 3,"pos": 2,"nom": "Universal Export","img":"compagnie-universal","colors": ["lightgreen"],"prix": 15000,"loyers": [400,1000]}, 26 | {"type": "propriete","groupe": "Violet clair","axe": 3,"pos": 3,"nom": "Les Diamants sont Eternels","colors": ["#73316F", "#DFB4DC"],"prix": 14000,"loyers": [1000, 5000, 15000, 45000, 62500, 75000],"prixMaison": 10000}, 27 | {"type": "propriete","groupe": "Violet clair","axe": 3,"pos": 4,"nom": "Vivre et Laisser Mourir","colors": ["#73316F", "#DFB4DC"],"prix": 16000,"loyers": [1200, 6000, 18000, 50000, 70000, 90000],"prixMaison": 10000}, 28 | {"type": "gare","axe": 3,"pos": 5,"nom": "Aston Martin AMV8","colors": ["#000000", "#ABABAB"],"prix": 20000,"loyers": [2500, 5000, 10000, 20000]}, 29 | {"type": "propriete","groupe": "Orange","axe": 3,"pos": 6,"nom": "L'homme au Pistolet d'Or","colors": ["#D16E2D", "#FECC84"],"prix": 18000,"loyers": [1400, 7000, 20000, 55000, 75000, 95000],"prixMaison": 10000}, 30 | {"type": "communaute","axe": 3,"pos": 7}, 31 | {"type": "propriete","groupe": "Orange","axe": 3,"pos": 8,"nom": "L'espion qui m'aimait","colors": ["#D16E2D", "#FECC84"],"prix": 18000,"loyers": [1400, 7000, 20000, 55000, 75000, 95000],"prixMaison": 10000}, 32 | {"type": "propriete","groupe": "Orange","axe": 3,"pos": 9,"nom": "Moonraker","colors": ["#D16E2D", "#FECC84"],"prix": 20000,"loyers": [1600, 8000, 22000, 60000, 80000, 100000],"prixMaison": 10000}, 33 | {"type": "parc","axe": 0,"pos": 0,"nom": "Parc Gratuit"}, 34 | {"type": "propriete","groupe": "Rouge","axe": 0,"pos": 1,"nom": "Rien que pour vos yeux","colors": ["#D32C19", "#F9AEA6"],"prix": 22000,"loyers": [1800, 9000, 25000, 70000, 87500, 105000],"prixMaison": 15000}, 35 | {"type": "chance","axe": 0,"pos": 2}, 36 | {"type": "propriete","groupe": "Rouge","axe": 0,"pos": 3,"nom": "Octopussy","colors": ["#D32C19", "#F9AEA6"],"prix": 22000,"loyers": [1800, 9000, 25000, 70000, 87500, 105000],"prixMaison": 15000}, 37 | {"type": "propriete","groupe": "Rouge","axe": 0,"pos": 4,"nom": "Dangereu- sement vôtre","colors": ["#D32C19", "#F9AEA6"],"prix": 24000,"loyers": [2000, 10000, 30000, 75000, 92500, 110000],"prixMaison": 15000}, 38 | {"type": "gare","axe": 0,"pos": 5,"nom": "Aston Martin Vanquish","colors": ["#000000", "#ABABAB"],"prix": 20000,"loyers": [2500, 5000, 10000, 20000]}, 39 | {"type": "propriete","groupe": "Jaune","axe": 0,"pos": 6,"nom": "Tuer n'est pas jouer","colors": ["#E6E018", "#F8F587"],"prix": 26000,"loyers": [2200, 11000, 33000, 80000, 97500, 115000],"prixMaison": 15000}, 40 | {"type": "propriete","groupe": "Jaune","axe": 0,"pos": 7,"nom": "Permis de Tuer","colors": ["#E6E018", "#F8F587"],"prix": 26000,"loyers": [2200, 11000, 33000, 80000, 97500, 115000],"prixMaison": 15000}, 41 | {"type": "compagnie","axe": 0,"pos": 8,"nom": "S.P.E.C.T.R.E.","img":"compagnie-spectre","colors": ["lightgreen"],"prix": 15000,"loyers": [400,1000]}, 42 | {"type": "propriete","groupe": "Jaune","axe": 0,"pos": 9,"nom": "Goldeneye","colors": ["#E6E018", "#F8F587"],"prix": 28000,"loyers": [2400, 12000, 36000, 85000, 102500, 120000],"prixMaison": 15000}, 43 | {"type": "prison","axe": 1,"pos": 0,"nom": "Allez en prison"}, 44 | {"type": "propriete","groupe": "Vert","axe": 1,"pos": 1,"nom": "Demain ne Meurt Jamais","colors": ["#11862E", "#93D1A2"],"prix": 30000,"loyers": [2600, 13000, 39000, 90000, 110000, 127500],"prixMaison": 20000}, 45 | {"type": "propriete","groupe": "Vert","axe": 1,"pos": 2,"nom": "Le Monde ne Suffit pas","colors": ["#11862E", "#93D1A2"],"prix": 30000,"loyers": [2600, 13000, 39000, 90000, 110000, 127500],"prixMaison": 20000}, 46 | {"type": "communaute","axe": 1,"pos": 3}, 47 | {"type": "propriete","groupe": "Vert","axe": 1,"pos": 4,"nom": "Meurs un Autre Jour","colors": ["#11862E", "#93D1A2"],"prix": 32000,"loyers": [2800, 15000, 45000, 100000, 120000, 140000],"prixMaison": 20000}, 48 | {"type": "gare","axe": 1,"pos": 5,"nom": "Aston Martin DBS","colors": ["#000000", "#ABABAB"],"prix": 20000,"loyers": [2500, 5000, 10000, 20000]}, 49 | {"type": "chance","axe": 1,"pos": 6}, 50 | {"type": "propriete","groupe": "Bleu","axe": 1,"pos": 7,"nom": "Casino Royale","colors": ["#132450", "#808EB0"],"prix": 35000,"loyers": [3500, 17500, 50000, 110000, 130000, 150000],"prixMaison": 20000}, 51 | {"type": "taxe","axe": 1,"pos": 8,"nom": "Champagne","prix": 10000}, 52 | {"type": "propriete","groupe": "Bleu","axe": 1,"pos": 9,"nom": "Quantum of Solace","colors": ["#132450", "#808EB0"],"prix": 40000,"loyers": [5000, 20000, 60000, 140000, 170000, 200000],"prixMaison": 20000} 53 | ], 54 | "images":{ 55 | "gare":{ 56 | "src" : "img/jamesbond/aston_martin.png", 57 | "width":60, 58 | "height":20, 59 | "margin":15 60 | }, 61 | "chance":{ 62 | "src" : "img/jamesbond/gadget.png", 63 | "width":32, 64 | "height":70, 65 | "margin":-5 66 | }, 67 | "caisseDeCommunaute":{ 68 | "src" : "img/jamesbond/woman.png", 69 | "width":50, 70 | "height":60, 71 | "margin":0 72 | }, 73 | "compagnie-spectre":{ 74 | "src" : "img/jamesbond/spectre.png", 75 | "width":45, 76 | "height":45, 77 | "margin":0 78 | }, 79 | "compagnie-universal":{ 80 | "src" : "img/jamesbond/universal_export.png", 81 | "width":40, 82 | "height":40, 83 | "margin":5 84 | } 85 | }, 86 | "titles":{ 87 | "chance":"Gadgets", 88 | "communaute":"James Bond Girl", 89 | "taxe":"Champagne" 90 | }, 91 | "chance":{ 92 | "cartes":[ 93 | {"nom":"L'aile avant de la DBS a été abimée, montant des réparations, £ 15.000","type":"taxe","montant":15000}, 94 | {"nom":"Amende pour excès de vitesse : £ 1.500","type":"taxe","montant":1500}, 95 | {"nom":"Casino de Monte Carlo, vous gagner 10000£ au bacarra","type":"prime","montant":10000}, 96 | {"nom":"Une partie de poker improvisée sur un yacht vous rapporte £ 5.000","type":"prime","montant":5000}, 97 | {"nom":"Vous récupérez la malette d'un terroriste, elle contient £ 15.000","type":"prime","montant":15000}, 98 | {"nom":"Amende pour ivresse : £ 2.000","type":"taxe","montant":2000}, 99 | {"nom":"Rendez vous à la Perlas de las Dunas","type":"goto","axe":1,"pos":9}, 100 | {"nom":"Q a un cadeau pour vous, avancez jusqu'à la case Départ","type":"goto","axe":2,"pos":0}, 101 | {"nom":"Regardez OHMSS. Si vous passez par la case Départ, recevez £ 20.000","type":"goto","axe":3,"pos":1}, 102 | {"nom":"Allez en prison. Rendez vous directement à la prison. Ne franchissez par la case Départ. Ne touchez pas £ 20.000","type":"goto","axe":1,"pos":0,"direct":true} 103 | ] 104 | }, 105 | "communaute":{ 106 | "cartes":[ 107 | {"nom":"Une partie de golf vous rapporte £ 5.000","type":"prime","montant":5000}, 108 | {"nom":"Excès de vitesse, payer £ 5.000","type":"taxe","montant":5000}, 109 | {"nom":"Prime du MI6 pour service rendu £ 10.000","type":"prime","montant":10000}, 110 | {"nom":"Toujours un double six au backgammon. Recevez £ 1.000","type":"prime","montant":1000}, 111 | {"nom":"Les faux papiers coutent chers £ 5.000","type":"taxe","montant":5000}, 112 | {"nom":"Sanchez vous remercie pour vos informations, il vous donne £ 2.000","type":"prime","montant":2000}, 113 | {"nom":"Erreur de la Banque en votre faveur Recevez £ 20.000","type":"prime","montant":2000}, 114 | {"nom":"Recevez votre intérêt sur l'emprunt à 7 % £ 2.500","type":"prime","montant":2500}, 115 | {"nom":"Vous détruisez vos gadgets, Q demande £ 10.000","type":"taxe","montant":10000}, 116 | {"nom":"Vous héritez de £ 10.000","type":"prime","montant":10000} 117 | ] 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /data/data-monopoly-starwars.json: -------------------------------------------------------------------------------- 1 | { 2 | "plateau":{ 3 | "subtitle":"Star Wars", 4 | "backgroundColor": "#888888", 5 | "textColor": "#FFFFFF", 6 | "nomsJoueurs":["R2 D2","Dark Vador","Jango Feet","StormTrooper","Boba Feet","Clone","Chewbacca"], 7 | "colors":["#2551A9","#000000","#800000","#FFFFFF","#7A9B94","#FFD300","#7A9B94","#9EA3A5"], 8 | "imgJoueurs":["img/starwars/r2_d2.png","img/starwars/darth_vader.png","img/starwars/jango_fett.png","img/starwars/stormtrooper.png","img/starwars/boba_fett.png","img/starwars/clone_2.png"], 9 | "rollColor":"#999999", 10 | "depart":200, 11 | "argent":1500, 12 | "prison":50 13 | }, 14 | "currency":"7", 15 | "background-image":"", 16 | "fiches": [ 17 | {"type": "depart","axe": 2,"pos": 0,"nom": "Départ"}, 18 | {"type": "propriete","groupe": "Violet","axe": 2,"pos": 1,"nom": "Tatooine Mos Eisley","colors": ["#812B5C", "#DFA4C6"],"prix": 40,"loyers": [2,10,30,90,160,250],"prixMaison": 50}, 19 | {"type": "communaute","axe": 2,"pos": 2}, 20 | {"type": "propriete","groupe": "Violet","axe": 2,"pos": 3,"nom": "tatooine Mos Espa","colors": ["#812B5C", "#DFA4C6"],"prix": 80,"loyers": [4, 20, 60, 180, 320, 450],"prixMaison": 50}, 21 | {"type": "taxe","axe": 2,"pos": 4,"nom": "Impots sur le revenu","prix": 200}, 22 | {"type": "gare","axe": 2,"pos": 5,"nom": "Corellian Corvette","colors": ["#000000", "#ABABAB"],"prix": 200,"loyers": [25, 50, 100, 200]}, 23 | {"type": "propriete","groupe": "Bleu clair","axe": 2,"pos": 6,"nom": "Dagobah","colors": ["#119AEB", "#98CEEE"],"prix": 100,"loyers": [6, 30, 90, 270, 400, 550],"prixMaison": 50}, 24 | {"type": "chance","axe": 2,"pos": 7}, 25 | {"type": "propriete","groupe": "Bleu clair","axe": 2,"pos": 8,"nom": "Endor","colors": ["#119AEB", "#98CEEE"],"prix": 100,"loyers": [6, 30, 90, 270, 400, 550],"prixMaison": 50}, 26 | {"type": "propriete","groupe": "Bleu clair","axe": 2,"pos": 9,"nom": "Bespin","colors": ["#119AEB", "#98CEEE"],"prix": 120,"loyers": [8, 40, 100, 300, 450, 600],"prixMaison": 50}, 27 | {"type": "special","axe": 3,"pos": 0,"nom": "Simple visite"}, 28 | {"type": "propriete","groupe": "Violet clair","axe": 3,"pos": 1,"nom": "Hoth","colors": ["#73316F", "#DFB4DC"],"prix": 140,"loyers": [10, 50, 150, 450, 625, 750],"prixMaison": 100}, 29 | {"type": "compagnie","axe": 3,"pos": 2,"nom": " Death Star","colors": ["lightgreen"],"prix": 150,"loyers": [4,10]}, 30 | {"type": "propriete","groupe": "Violet clair","axe": 3,"pos": 3,"nom": "Dantooine","colors": ["#73316F", "#DFB4DC"],"prix": 140,"loyers": [10, 50, 150, 450, 625, 750],"prixMaison": 100}, 31 | {"type": "propriete","groupe": "Violet clair","axe": 3,"pos": 4,"nom": "Yavin 4","colors": ["#73316F", "#DFB4DC"],"prix": 160,"loyers": [12, 60, 180, 500, 700, 900],"prixMaison": 100}, 32 | {"type": "gare","axe": 3,"pos": 5,"nom": "Imperial Star Destroyer","colors": ["#000000", "#ABABAB"],"prix": 200,"loyers": [25, 50, 100, 200]}, 33 | {"type": "propriete","groupe": "Orange","axe": 3,"pos": 6,"nom": "Kashyyyk","colors": ["#D16E2D", "#FECC84"],"prix": 180,"loyers": [14, 70, 200, 550, 750, 950],"prixMaison": 100}, 34 | {"type": "communaute","axe": 3,"pos": 7}, 35 | {"type": "propriete","groupe": "Orange","axe": 3,"pos": 8,"nom": "Mustafar","colors": ["#D16E2D", "#FECC84"],"prix": 180,"loyers": [14, 70, 200, 550, 750, 950],"prixMaison": 100}, 36 | {"type": "propriete","groupe": "Orange","axe": 3,"pos": 9,"nom": "Utapau","colors": ["#D16E2D", "#FECC84"],"prix": 200,"loyers": [16, 80, 220, 600, 800, 1000],"prixMaison": 100}, 37 | {"type": "parc","axe": 0,"pos": 0,"nom": "Parc Gratuit"}, 38 | {"type": "propriete","groupe": "Rouge","axe": 0,"pos": 1,"nom": "Kessel","colors": ["#D32C19", "#F9AEA6"],"prix": 220,"loyers": [18, 90, 250, 700, 875, 1050],"prixMaison": 100}, 39 | {"type": "chance","axe": 0,"pos": 2}, 40 | {"type": "propriete","groupe": "Rouge","axe": 0,"pos": 3,"nom": "Sullust","colors": ["#D32C19", "#F9AEA6"],"prix": 220,"loyers": [18, 9000, 250, 700, 875, 1000],"prixMaison": 150}, 41 | {"type": "propriete","groupe": "Rouge","axe": 0,"pos": 4,"nom": "Ord Mantell","colors": ["#D32C19", "#F9AEA6"],"prix": 240,"loyers": [20, 100, 300, 750, 925, 1100],"prixMaison": 150}, 42 | {"type": "gare","axe": 0,"pos": 5,"nom": "Republic Assault Ship","colors": ["#000000", "#ABABAB"],"prix": 200,"loyers": [25, 50, 100, 200]}, 43 | {"type": "propriete","groupe": "Jaune","axe": 0,"pos": 6,"nom": "Geonosis","colors": ["#E6E018", "#F8F587"],"prix": 260,"loyers": [20, 110, 330, 800, 975, 1150],"prixMaison": 150}, 44 | {"type": "propriete","groupe": "Jaune","axe": 0,"pos": 7,"nom": "Kamino","colors": ["#E6E018", "#F8F587"],"prix": 260,"loyers": [22, 110, 300, 800, 975, 1150],"prixMaison": 150}, 45 | {"type": "compagnie","axe": 0,"pos": 8,"nom": " Death Star II","colors": ["lightgreen"],"prix": 150,"loyers": [4,10]}, 46 | {"type": "propriete","groupe": "Jaune","axe": 0,"pos": 9,"nom": "Naboo","colors": ["#E6E018", "#F8F587"],"prix": 280,"loyers": [24, 120, 360, 850, 1025, 1200],"prixMaison": 150}, 47 | {"type": "prison","axe": 1,"pos": 0,"nom": "Allez en prison"}, 48 | {"type": "propriete","groupe": "Vert","axe": 1,"pos": 1,"nom": "Mon Calamari","colors": ["#11862E", "#93D1A2"],"prix": 300,"loyers": [26, 130, 390, 900, 1100, 1275],"prixMaison": 200}, 49 | {"type": "propriete","groupe": "Vert","axe": 1,"pos": 2,"nom": "Corellia","colors": ["#11862E", "#93D1A2"],"prix": 300,"loyers": [26, 130, 300, 900, 1100, 1275],"prixMaison": 200}, 50 | {"type": "communaute","axe": 1,"pos": 3}, 51 | {"type": "propriete","groupe": "Vert","axe": 1,"pos": 4,"nom": "Alderaan","colors": ["#11862E", "#93D1A2"],"prix": 320,"loyers": [28, 150, 450, 1000, 1200, 1400],"prixMaison": 200}, 52 | {"type": "gare","axe": 1,"pos": 5,"nom": "Super Star destroyer","colors": ["#000000", "#ABABAB"],"prix": 200,"loyers": [25, 50, 100, 200]}, 53 | {"type": "chance","axe": 1,"pos": 6}, 54 | {"type": "propriete","groupe": "Bleu","axe": 1,"pos": 7,"nom": "Coruscant Jedi Temple","colors": ["#132450", "#808EB0"],"prix": 350,"loyers": [35, 175, 500, 1100, 1300, 1500],"prixMaison": 200}, 55 | {"type": "taxe","axe": 1,"pos": 8,"nom": "Taxe de luxe","prix": 100}, 56 | {"type": "propriete","groupe": "Bleu","axe": 1,"pos": 9,"nom": "Coruscant Senate","colors": ["#132450", "#808EB0"],"prix": 400,"loyers": [50, 200, 600, 1400, 1700, 2000],"prixMaison": 200} 57 | ], 58 | "images":{ 59 | "taxe": { 60 | "src": "img/bijou.png", 61 | "width": 40, 62 | "height": 50 63 | }, 64 | "chance":{ 65 | "src": "img/starwars/sith_light_saber.png", 66 | "width": 50, 67 | "height": 70, 68 | "margin":-5 69 | }, 70 | "caisseDeCommunaute":{ 71 | "src": "img/starwars/jedi_light_saber.png", 72 | "width": 50, 73 | "height": 70, 74 | "margin":-5 75 | }, 76 | "compagnie":{ 77 | "src": "img/light.png", 78 | "width": 50, 79 | "height": 35, 80 | "margin": 5 81 | }, 82 | "gare":{ 83 | "src": "img/starwars/xwing.png", 84 | "width": 50, 85 | "height": 50 86 | } 87 | }, 88 | "titles":{ 89 | "chance":"Sith", 90 | "communaute":"Jedi" 91 | }, 92 | "chance":{ 93 | "cartes":[ 94 | {"nom":"Payer le carburant du landspeeder 7 150","type":"taxe","montant":150}, 95 | {"nom":"Amende pour excès de vitesse : 7 15","type":"taxe","montant":15}, 96 | {"nom":"Vous avez gagné le prix de tir. Recevez 7 100","type":"prime","montant":100}, 97 | {"nom":"La banque vous verse un dividende de 7 50","type":"prime","montant":50}, 98 | {"nom":"Votre immeuble et votre prêt rapportent. Vous devez toucher 7 150","type":"prime","montant":150}, 99 | {"nom":"Amende pour ivresse : 7 20","type":"taxe","montant":20}, 100 | {"nom":"Rendez vous à Ord Mantell. Si vous passez par la case Départ, recevez 7 200","type":"goto","axe":0,"pos":4}, 101 | {"nom":"Rendez vous au Coruscant Senate","type":"goto","axe":1,"pos":9}, 102 | {"nom":"Reculez de 3 cases","type":"move","nb":-3}, 103 | {"nom":"Avancez jusqu'à la case Départ","type":"goto","axe":2,"pos":0}, 104 | {"nom":"Rendez vous sur l'Imperial Star Destroyer. Si vous passez par la case Départ, recevez 7 200","type":"goto","axe":3,"pos":5}, 105 | {"nom":"Atterissez sur Hoth. Si vous passez par la case Départ, recevez 7 200","type":"goto","axe":3,"pos":1}, 106 | {"nom":"Allez en prison. Rendez vous directement à la prison. Ne franchissez par la case Départ. Ne touchez pas 7 200","type":"goto","axe":1,"pos":0,"direct":true} 107 | ] 108 | }, 109 | "communaute":{ 110 | "cartes":[ 111 | {"nom":"La vente de votre stock pour rapport 7 50","type":"prime","montant":50}, 112 | {"nom":"Payez votre Police d'Assurance s'élevant à 7 50","type":"taxe","montant":50}, 113 | {"nom":"Recevez votre revenu annuel 7 100","type":"prime","montant":100}, 114 | {"nom":"Vous avez gagné le deuxième Prix de Beauté. Recevez 7 10","type":"prime","montant":10}, 115 | {"nom":"Payez la note du Médecin 7 50","type":"taxe","montant":50}, 116 | {"nom":"Les Contributions vous remboursent la somme de 7 20","type":"prime","montant":20}, 117 | {"nom":"Erreur de la Banque en votre faveur Recevez 7 200","type":"prime","montant":200}, 118 | {"nom":"Recevez votre intérêt sur l'emprunt à 7 % 7 25","type":"prime","montant":25}, 119 | {"nom":"Payez à l'Hôpital 7 100","type":"taxe","montant":100}, 120 | {"nom":"Vous héritez 7 100","type":"prime","montant":100}, 121 | {"nom":"Placez vous sur la case Départ","type":"goto","axe":2,"pos":0}, 122 | {"nom":"Allez en prison. Avancez tout droit en prison. Ne passez par la case Départ. Ne touchez pas 7 200","type":"goto","axe":1,"pos":0,"direct":true}, 123 | {"nom":"Retournez à Tatooine Mos Eisley.","type":"goto","axe":2,"pos":1,"direct":true}, 124 | {"nom":"Vous êtes libéré de prison","type":"prison"} 125 | ] 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /data/data-monopoly-original.json: -------------------------------------------------------------------------------- 1 | { 2 | "plateau":{ 3 | "subtitle":"Original", 4 | "backgroundColor": "#A7E9DB", 5 | "textColor": "#000000", 6 | "nomsJoueurs":["Cat","Racecar","Top hat","Wheelbarrow","Thimble","Scottie Dog","Battleship","Boot"], 7 | "rollColor":"#999999", 8 | "depart":200 9 | }, 10 | "background-image":"", 11 | "currency": "$", 12 | "fiches": [ 13 | {"type": "depart","axe": 2,"pos": 0,"nom": "GO"}, 14 | {"type": "propriete","groupe": "Violet","axe": 2,"pos": 1,"nom": "Mediterranean Avenue","colors": ["#812B5C", "#DFA4C6"],"prix": 60,"loyers": [2,10,30,90,160,250],"prixMaison": 50}, 15 | {"type": "communaute","axe": 2,"pos": 2}, 16 | {"type": "propriete","groupe": "Violet","axe": 2,"pos": 3,"nom": "Baltic Avenue","colors": ["#812B5C", "#DFA4C6"],"prix": 60,"loyers": [4, 20, 60, 180, 320, 450],"prixMaison": 50}, 17 | {"type": "taxe","axe": 2,"pos": 4,"nom": "Impots sur le revenu","prix": 200}, 18 | {"type": "gare","axe": 2,"pos": 5,"nom": "Reading Railroad","colors": ["#000000", "#ABABAB"],"prix": 200,"loyers": [25, 50, 100, 200]}, 19 | {"type": "propriete","groupe": "Bleu clair","axe": 2,"pos": 6,"nom": "Oriental Avenue","colors": ["#119AEB", "#98CEEE"],"prix": 100,"loyers": [6, 30, 90, 270, 400, 550],"prixMaison": 50}, 20 | {"type": "chance","axe": 2,"pos": 7}, 21 | {"type": "propriete","groupe": "Bleu clair","axe": 2,"pos": 8,"nom": "Vermont Avenue","colors": ["#119AEB", "#98CEEE"],"prix": 100,"loyers": [6, 30, 90, 270, 400, 550],"prixMaison": 50}, 22 | {"type": "propriete","groupe": "Bleu clair","axe": 2,"pos": 9,"nom": "Connecticut Avenue","colors": ["#119AEB", "#98CEEE"],"prix": 120,"loyers": [8, 40, 100, 300, 450, 600],"prixMaison": 50}, 23 | {"type": "special","axe": 3,"pos": 0,"nom": "Simple visite"}, 24 | {"type": "propriete","groupe": "Violet clair","axe": 3,"pos": 1,"nom": "St. Charles Place","colors": ["#73316F", "#DFB4DC"],"prix": 140,"loyers": [10, 50, 150, 450, 625, 750],"prixMaison": 100}, 25 | {"type": "compagnie","axe": 3,"pos": 2,"nom": "Electric Company","colors": ["lightgreen"],"prix": 150,"loyers": [400,1000]}, 26 | {"type": "propriete","groupe": "Violet clair","axe": 3,"pos": 3,"nom": "States Avenue","colors": ["#73316F", "#DFB4DC"],"prix": 140,"loyers": [10, 50, 150, 450, 625, 750],"prixMaison": 100}, 27 | {"type": "propriete","groupe": "Violet clair","axe": 3,"pos": 4,"nom": "Virginia Avenue","colors": ["#73316F", "#DFB4DC"],"prix": 160,"loyers": [12, 60, 180, 500, 700, 900],"prixMaison": 100}, 28 | {"type": "gare","axe": 3,"pos": 5,"nom": "Pennsylvania Railroad","colors": ["#000000", "#ABABAB"],"prix": 200,"loyers": [25, 50, 100, 200]}, 29 | {"type": "propriete","groupe": "Orange","axe": 3,"pos": 6,"nom": "St. James Place","colors": ["#D16E2D", "#FECC84"],"prix": 180,"loyers": [14, 70, 200, 550, 750, 950],"prixMaison": 100}, 30 | {"type": "communaute","axe": 3,"pos": 7}, 31 | {"type": "propriete","groupe": "Orange","axe": 3,"pos": 8,"nom": "Tennessee Avenue","colors": ["#D16E2D", "#FECC84"],"prix": 180,"loyers": [14, 70, 200, 550, 750, 950],"prixMaison": 100}, 32 | {"type": "propriete","groupe": "Orange","axe": 3,"pos": 9,"nom": "New York Avenue","colors": ["#D16E2D", "#FECC84"],"prix": 200,"loyers": [16, 80, 220, 600, 800, 1000],"prixMaison": 100}, 33 | {"type": "parc","axe": 0,"pos": 0,"nom": "Parc Gratuit"}, 34 | {"type": "propriete","groupe": "Rouge","axe": 0,"pos": 1,"nom": "Kentucky Avenue","colors": ["#D32C19", "#F9AEA6"],"prix": 220,"loyers": [18, 90, 250, 700, 875, 1050],"prixMaison": 150}, 35 | {"type": "chance","axe": 0,"pos": 2}, 36 | {"type": "propriete","groupe": "Rouge","axe": 0,"pos": 3,"nom": "Indiana Avenue","colors": ["#D32C19", "#F9AEA6"],"prix": 220,"loyers": [18, 90, 250, 700, 875, 1050],"prixMaison": 150}, 37 | {"type": "propriete","groupe": "Rouge","axe": 0,"pos": 4,"nom": "Illinois Avenue","colors": ["#D32C19", "#F9AEA6"],"prix": 240,"loyers": [20, 100, 300, 750, 925, 1100],"prixMaison": 150}, 38 | {"type": "gare","axe": 0,"pos": 5,"nom": "B&O Railroad","colors": ["#000000", "#ABABAB"],"prix": 200,"loyers": [25, 50, 100, 200]}, 39 | {"type": "propriete","groupe": "Jaune","axe": 0,"pos": 6,"nom": "Atlantic Avenue","colors": ["#E6E018", "#F8F587"],"prix": 260,"loyers": [22, 110, 330, 800, 975, 1150],"prixMaison": 150}, 40 | {"type": "propriete","groupe": "Jaune","axe": 0,"pos": 7,"nom": "Ventnor Avenue","colors": ["#E6E018", "#F8F587"],"prix": 260,"loyers": [22, 110, 330, 800, 975, 1150],"prixMaison": 150}, 41 | {"type": "compagnie","axe": 0,"pos": 8,"nom": "Water Works","colors": ["lightgreen"],"prix": 150,"loyers": [400,1000]}, 42 | {"type": "propriete","groupe": "Jaune","axe": 0,"pos": 9,"nom": "Marvin Gardens","colors": ["#E6E018", "#F8F587"],"prix": 280,"loyers": [24, 120, 360, 850, 1025, 1200],"prixMaison": 150}, 43 | {"type": "prison","axe": 1,"pos": 0,"nom": "Go To Jail"}, 44 | {"type": "propriete","groupe": "Vert","axe": 1,"pos": 1,"nom": "Pacific Avenue","colors": ["#11862E", "#93D1A2"],"prix": 300,"loyers": [26, 130, 390, 900, 1100, 1275],"prixMaison": 200}, 45 | {"type": "propriete","groupe": "Vert","axe": 1,"pos": 2,"nom": "North Carolina Avenue","colors": ["#11862E", "#93D1A2"],"prix": 300,"loyers": [26, 130, 390, 900, 1100, 1275],"prixMaison": 200}, 46 | {"type": "communaute","axe": 1,"pos": 3}, 47 | {"type": "propriete","groupe": "Vert","axe": 1,"pos": 4,"nom": "Pennsylvania Avenue","colors": ["#11862E", "#93D1A2"],"prix": 320,"loyers": [28, 150, 450, 1000, 1200, 1400],"prixMaison": 200}, 48 | {"type": "gare","axe": 1,"pos": 5,"nom": "Short Line","colors": ["#000000", "#ABABAB"],"prix": 200,"loyers": [25, 50, 100, 200]}, 49 | {"type": "chance","axe": 1,"pos": 6}, 50 | {"type": "propriete","groupe": "Bleu","axe": 1,"pos": 7,"nom": "Park Place","colors": ["#132450", "#808EB0"],"prix": 350,"loyers": [35, 175, 500, 1100, 1300, 1500],"prixMaison": 200}, 51 | {"type": "taxe","axe": 1,"pos": 8,"nom": "Luxury Tax","prix": 100}, 52 | {"type": "propriete","groupe": "Bleu","axe": 1,"pos": 9,"nom": "Boardwalk","colors": ["#132450", "#808EB0"],"prix": 400,"loyers": [50, 200, 600, 1400, 1700, 2000],"prixMaison": 200} 53 | ], 54 | "images":{ 55 | "taxe": { 56 | "src": "img/bijou.png", 57 | "width": 40, 58 | "height": 50 59 | }, 60 | "chance":{ 61 | "src": "img/interrogation.png", 62 | "width": 50, 63 | "height": 60 64 | }, 65 | "caisseDeCommunaute":{ 66 | "src": "img/banque2.png", 67 | "width": 60, 68 | "height": 60 69 | }, 70 | "compagnie":{ 71 | "src": "img/light2.png", 72 | "width": 40, 73 | "height": 40, 74 | "margin": 5 75 | }, 76 | "gare":{ 77 | "src": "img/big_train.png", 78 | "width": 50, 79 | "height": 35, 80 | "margin": 5 81 | } 82 | }, 83 | "titles":{ 84 | "chance":"Chance", 85 | "communaute":"Community Chest" 86 | }, 87 | "chance":{ 88 | "cartes":[ 89 | {"nom":"You have been elected Chairman of the Board – Pay each player $50","type":"birthday","montant":50}, 90 | {"nom":"Make general repairs on all your property – For each house pay $25 – For each hotel $100","type":"repair","hotel":100,"maison":25}, 91 | {"nom":"Pay poor tax of $15","type":"taxe","montant":15}, 92 | {"nom":"You have won a crossword competition - Collect $100","type":"prime","montant":100}, 93 | {"nom":"Bank pays you dividend of $50","type":"prime","montant":50}, 94 | {"nom":"Your building loan matures – Collect $150","type":"prime","montant":150}, 95 | {"nom":"Advance to Illinois Ave. - If you pass Go, collect $200","type":"goto","axe":0,"pos":4}, 96 | {"nom":"Take a trip to Reading Railroad ","type":"goto","axe":2,"pos":5}, 97 | {"nom":"Take a walk on the Boardwalk","type":"goto","axe":1,"pos":9}, 98 | {"nom":"Go Back 3 Spaces","type":"move","nb":-3}, 99 | {"nom":"Advance to Go, collect $200 ","type":"goto","axe":2,"pos":0}, 100 | {"nom":"Advance to St. Charles Place – If you pass Go, collect $200","type":"goto","axe":3,"pos":1}, 101 | {"nom":"Go to Jail – Go directly to Jail – Do not pass Go, do not collect $200","type":"goto","axe":1,"pos":0,"direct":true}, 102 | {"nom":"Get out of Jail Free – This card may be kept until needed, or traded/sold","type":"prison"} 103 | ] 104 | }, 105 | "communaute":{ 106 | "cartes":[ 107 | {"nom":"From sale of stock you get $50","type":"prime","montant":50}, 108 | {"nom":"Grand Opera Night – Collect $50 from every player for opening night seats","type":"birthday","montant":50}, 109 | {"nom":"Life insurance matures – Collect $100","type":"prime","montant":100}, 110 | {"nom":"Holiday Fund matures - Receive $100 ","type":"prime","montant":100}, 111 | {"nom":"You have won second prize in a beauty contest – Collect $10","type":"prime","montant":10}, 112 | {"nom":"Doctor's fees – Pay $50 ","type":"taxe","montant":50}, 113 | {"nom":"It is your birthday - Collect $10 from each player","type":"birthday","montant":10}, 114 | {"nom":"Income tax refund – Collect $20","type":"prime","montant":20}, 115 | {"nom":"Bank error in your favor – Collect $200","type":"prime","montant":200}, 116 | {"nom":"Receive $25 consultancy fee","type":"prime","montant":25}, 117 | {"nom":"Pay hospital fees of $100","type":"taxe","montant":100}, 118 | {"nom":"Pay school fees of $150 ","type":"taxe","montant":150}, 119 | {"nom":"You inherit $100","type":"prime","montant":100}, 120 | {"nom":"Advance to Go","type":"goto","axe":2,"pos":0}, 121 | {"nom":"Go to Jail – Go directly to jail – Do not pass Go – Do not collect $200","type":"goto","axe":1,"pos":0,"direct":true}, 122 | {"nom":"Get out of Jail Free – This card may be kept until needed, or traded/sold","type":"prison"}, 123 | {"nom":"You are assessed for street repairs – $40 per house – $115 per hotel ","type":"repair","hotel":115,"maison":40} 124 | ] 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /js/ui/graphics.js: -------------------------------------------------------------------------------- 1 | 2 | /* Fournit les methodes de dessin d'un plateau de jeu standard (carré) */ 3 | 4 | import {bus} from "../bus_message.js"; 5 | 6 | let CURRENT_ID_COMPONENT = 0; // Permet de generer un numero de composant unique 7 | 8 | /* Fournit des methodes de dessins */ 9 | let DrawerHelper = { 10 | fontWeight:200, 11 | setFontWeight(font){ 12 | this.fontWeight = font; 13 | }, 14 | fromDegresToRad:function(angle){ 15 | return (angle/180)*Math.PI; 16 | }, 17 | drawCircle:function(canvas,color,rayon,center,strokeColor=color){ 18 | canvas.fillStyle=color; 19 | canvas.strokeColor = strokeColor; 20 | canvas.beginPath(); 21 | canvas.arc(center.x,center.y,rayon,0,2*Math.PI); 22 | canvas.fill(); 23 | canvas.closePath(); 24 | }, 25 | drawArcCircle:function(canvas,color,rayon,center,alphaStart,alphaEnd){ 26 | canvas.beginPath(); 27 | canvas.fillStyle = color; 28 | canvas.moveTo(center.x,center.y); 29 | canvas.arc(center.x,center.y,rayon,alphaStart,alphaEnd); 30 | canvas.fill(); 31 | canvas.closePath(); 32 | }, 33 | drawImage: function (canvas, img, x, y, width, height, rotate) { 34 | canvas.save(); 35 | canvas.translate(x, y); 36 | canvas.rotate(rotate); 37 | try{ 38 | canvas.drawImage(img, 0, 0, width, height); 39 | }catch(e){} 40 | canvas.restore(); 41 | }, 42 | /* Detect les sauts de ligne */ 43 | _splitLine:function(mots){ 44 | if(mots.some(function(m){return m.indexOf('\n')!==-1;})){ 45 | let mots2 = []; 46 | mots.forEach(function(m){ 47 | if(m.indexOf('\n')!==-1){ 48 | m.split('\n').forEach(function(mm){mots2.push(mm);}); 49 | } 50 | else{ 51 | mots2.push(m); 52 | } 53 | }); 54 | mots = mots2; 55 | } 56 | return mots; 57 | }, 58 | /* @param align : si null, center, sinon 'left' ou 'right' */ 59 | writeText: function (text, x, y, rotate, canvas, size="7", width = DrawerFactory.dimensions.largeur, align) { 60 | canvas.fillStyle=DrawerFactory.getInfo('textColor') || '#000000'; 61 | canvas.font = `${this.fontWeight} ` + size + "pt Arial"; 62 | 63 | let mots = this._splitLine([text]); 64 | 65 | let mots2 = []; 66 | mots.forEach(function(m){ 67 | if (canvas.measureText(m).width > width - 5) { 68 | // On split les mots intelligement (on regroupe) 69 | let splitMots = m.split(" "); 70 | let tempMots = []; 71 | let pos = 0; 72 | for (let i = 0; i < splitMots.length; i++) { 73 | if (pos > 0 && (canvas.measureText(tempMots[pos - 1]).width + canvas.measureText(splitMots[i]).width) < width - 5) { 74 | // on concatene 75 | tempMots[pos - 1] = tempMots[pos - 1] + " " + splitMots[i]; 76 | } else { 77 | tempMots[pos++] = splitMots[i]; 78 | } 79 | } 80 | // On ajoute les mots 81 | tempMots.forEach(function(m){mots2.push(m);}); 82 | }else{ 83 | mots2.push(m); 84 | } 85 | }); 86 | mots = mots2; 87 | 88 | canvas.save(); 89 | canvas.translate(x, y); 90 | canvas.rotate(rotate); 91 | const pas = 12; 92 | for (let i = 0; i < mots.length; i++) { 93 | let lng; 94 | switch(align){ 95 | case 'left':lng=0;break; 96 | case 'right':lng = width - canvas.measureText(mots[i]).width;break; 97 | default : lng = (width - canvas.measureText(mots[i]).width) / 2; 98 | } 99 | canvas.fillText(mots[i], lng, i * pas); 100 | } 101 | canvas.font = "6pt Times news roman"; 102 | canvas.restore(); 103 | } 104 | } 105 | 106 | // Class which represent any component 107 | class Component{ 108 | constructor(){ 109 | this.id = CURRENT_ID_COMPONENT++; 110 | } 111 | draw(){ 112 | throw "Not implemented"; 113 | } 114 | } 115 | 116 | /* En fonction du type de plateau (square, circle), fournit les objets permettant de le construire */ 117 | /* Renvoie uniquement des components (implemente draw) */ 118 | let DrawerFactory = { 119 | instances:[], 120 | type:null, 121 | infos:{}, // Infos communes 122 | dimensions:{ 123 | largeur:65, 124 | largeurPion:20, 125 | hauteur:100, 126 | bordure:40, 127 | plateauSize:800, 128 | innerPlateauSize:220, 129 | nbCases:10, 130 | textSize:7 131 | }, 132 | /*setSize(size){ 133 | $('#plateau').height(size+10).width(size+10); 134 | $('#canvas').width(size+10).height(size+10).attr('width',size+10).attr('height',size+10); 135 | $('#canvas_rt').width(size+10).height(size+10).attr('width',size+10).attr('height',size+10); 136 | this.dimensions.plateauSize = size; 137 | DrawerHelper.setFontWeight(800); 138 | this.computeDimensions(); 139 | },*/ 140 | setNbCases(nbCases = 10){ 141 | this.dimensions.nbCases = nbCases; 142 | this.computeDimensions(); 143 | }, 144 | computeDimensions(){ 145 | let targetWidth = 570; 146 | // Width of a case if computed for minimum size divide by nb inside cases 147 | this.dimensions.largeur = targetWidth/(this.dimensions.nbCases -1)+2; 148 | this.dimensions.hauteur = (this.dimensions.plateauSize - targetWidth -20)/2; 149 | this.dimensions.textSize = Math.round(this.dimensions.largeur/10); 150 | }, 151 | init:function(){ 152 | return this; 153 | }, 154 | /* Configure la factory */ 155 | setType:function(type){ 156 | this.type = type; 157 | }, 158 | addInfo:function(name,info){ 159 | this.infos[name] = info; 160 | }, 161 | getInfo:function(name){ 162 | return this.infos[name]; 163 | }, 164 | /* Ajoute une nouvelle implementation */ 165 | addInstance:function(instance){ 166 | this.instances[instance.type] = instance; 167 | }, 168 | getDes:function(x, y, width){ 169 | return this._instantiate('des',arguments); 170 | }, 171 | getDesRapide:function(x, y, width){ 172 | return this._instantiate('desRapide',arguments); 173 | }, 174 | getCase:function(pos,axe,color,nom,prix,img){ 175 | return this._instantiate('standardCase',arguments); 176 | }, 177 | getCaseSpeciale:function(axe,titre){ 178 | return this._instantiate('specialCase',arguments); 179 | }, 180 | getPionJoueur:function(color){ 181 | return this._instantiate('pionJoueur',arguments); 182 | }, 183 | getPlateau:function(x,y,width,height,color){ 184 | return this._instantiate('plateau',arguments); 185 | }, 186 | endPlateau:function(){ 187 | return this._instantiate('endPlateau',arguments); 188 | }, 189 | _instantiate:function(method,params){ 190 | if(this.instances[this.type] == null){ 191 | throw "Creation, type : " + this.type + " inconnu"; 192 | } 193 | if(this.instances[this.type][method] == null){ 194 | return null; 195 | } 196 | return new this.instances[this.type][method](...params); 197 | } 198 | }.init(); 199 | 200 | // Gere les dessins et les calques 201 | let Drawer = { 202 | components: [], // Un ordre est ajoute lors de l'insertion 203 | height: 0, 204 | width: 0, 205 | interval: null, 206 | intervalRT: null, 207 | canvas: null, 208 | canvasRT: null, //Canvas de temps reel 209 | // ajoute un composant. On indique le canvas sur lequel il s'affiche 210 | intervals: [], // Stocke les flags d'arret du refresh 211 | /* @param order : Si present, indique l'ordre d'affichage. Le plus petit en premier */ 212 | reset:function(){ 213 | this.components = []; 214 | if(this.canvas) { 215 | this.clear(this.canvas); 216 | } 217 | if(this.canvasRT) { 218 | this.clear(this.canvasRT); 219 | } 220 | }, 221 | add: function (component, order) { 222 | if(component == null){return;} 223 | component.getId = function () { 224 | return Drawer.canvas.canvas.id 225 | }; 226 | component.order = (order==null)?0:order; 227 | Drawer.components.push(component); 228 | }, 229 | addRealTime: function (component) { 230 | component.getId = function () { 231 | return Drawer.canvasRT.canvas.id 232 | }; 233 | Drawer.components.push(component); 234 | }, 235 | removeComponent: function (component) { 236 | // Boucle sur les composants et supprime si l'id est le meme 237 | for (let i = 0; i < this.components.length; i++) { 238 | if (this.components[i].id === component.id) { 239 | this.components.splice(i, 1); 240 | return; 241 | } 242 | } 243 | }, 244 | clear: function (canvas) { 245 | canvas.clearRect(0, 0, this.width, this.height); 246 | }, 247 | /* Rafraichit un seul canvas */ 248 | refresh: function (canvas) { 249 | Drawer.clear(canvas); 250 | for (let i = 0; i < Drawer.components.length; i++) { 251 | if (Drawer.components[i].getId() === canvas.canvas.id) { 252 | Drawer.components[i].draw(canvas); 253 | } 254 | } 255 | }, 256 | // Refraichissement du graphique, time en ms 257 | setFrequency: function (time, canvas) { 258 | if (Drawer.intervals[canvas.canvas.id] != null) { 259 | clearInterval(Drawer.intervals[canvas.canvas.id]); 260 | } 261 | Drawer.intervals[canvas.canvas.id] = setInterval(function () { 262 | Drawer.refresh(canvas); 263 | }, time); 264 | }, 265 | init: function (width, height) { 266 | if(document.getElementById('canvas') == null){ 267 | return; 268 | } 269 | // On tri les composants qui ont ete ajoutes 270 | this.components.sort(function(a,b){ 271 | return a.order - b.order; 272 | }); 273 | this.width = width; 274 | this.height = height; 275 | this.canvas = document.getElementById("canvas").getContext("2d"); 276 | this.canvasRT = document.getElementById("canvas_rt").getContext("2d"); 277 | this.canvas.strokeStyle = '#AA0000'; 278 | this.canvasRT.strokeStyle = '#AA0000'; 279 | // On ne recharge pas le plateau, il n'est charge qu'une seule fois (ou rechargement a la main) 280 | this.refresh(this.canvas); 281 | this.setFrequency(50, this.canvasRT); 282 | 283 | bus.watchRefresh(()=>Drawer.refresh(Drawer.canvas)); 284 | return this; 285 | } 286 | }; 287 | 288 | export {DrawerFactory,Drawer,Component,DrawerHelper}; 289 | -------------------------------------------------------------------------------- /data/data-monopoly.json: -------------------------------------------------------------------------------- 1 | { 2 | "plateau":{ 3 | "subtitle":"Classique", 4 | "backgroundColor": "#cee6d0", 5 | "textColor": "#000000", 6 | "nomsJoueurs":["Chat","Voiture","Chapeau","Brouette","Dés à Coudre","Chien","Navire"], 7 | "rollColor":"#999999", 8 | "nbCases": 10 9 | }, 10 | "background-image":"", 11 | "currency": "Fr.", 12 | "fiches": [ 13 | {"type": "depart","axe": 2,"pos": 0,"nom": "Départ"}, 14 | {"type": "propriete","groupe": "Violet","axe": 2,"pos": 1,"nom": "Boulevard de Belleville","colors": ["#812B5C", "#DFA4C6"],"prix": 4000,"loyers": [200,1000,3000,9000,16000,25000],"prixMaison": 5000}, 15 | {"type": "communaute","axe": 2,"pos": 2}, 16 | {"type": "propriete","groupe": "Violet","axe": 2,"pos": 3,"nom": "Rue Lecourbe","colors": ["#812B5C", "#DFA4C6"],"prix": 8000,"loyers": [400, 2000, 6000, 18000, 32000, 45000],"prixMaison": 5000}, 17 | {"type": "taxe","axe": 2,"pos": 4,"nom": "Impots sur le revenu","prix": 20000}, 18 | {"type": "gare","axe": 2,"pos": 5,"nom": "Gare Montparnasse","colors": ["#000000", "#ABABAB"],"prix": 20000,"loyers": [2500, 5000, 10000, 20000]}, 19 | {"type": "propriete","groupe": "Bleu clair","axe": 2,"pos": 6,"nom": "Rue de Vaugirard","colors": ["#119AEB", "#98CEEE"],"prix": 10000,"loyers": [600, 3000, 9000, 27000, 40000, 55000],"prixMaison": 5000}, 20 | {"type": "chance","axe": 2,"pos": 7}, 21 | {"type": "propriete","groupe": "Bleu clair","axe": 2,"pos": 8,"nom": "Rue de Courcelles","colors": ["#119AEB", "#98CEEE"],"prix": 10000,"loyers": [600, 3000, 9000, 27000, 40000, 55000],"prixMaison": 5000}, 22 | {"type": "propriete","groupe": "Bleu clair","axe": 2,"pos": 9,"nom": "Avenue de la Republique","colors": ["#119AEB", "#98CEEE"],"prix": 12000,"loyers": [800, 4000, 10000, 30000, 45000, 60000],"prixMaison": 5000}, 23 | {"type": "special","axe": 3,"pos": 0,"nom": "Simple visite"}, 24 | {"type": "propriete","groupe": "Violet clair","axe": 3,"pos": 1,"nom": "Boulevard de la Villette","colors": ["#73316F", "#DFB4DC"],"prix": 14000,"loyers": [1000, 5000, 15000, 45000, 62500, 75000],"prixMaison": 10000}, 25 | {"type": "compagnie","axe": 3,"pos": 2,"nom": "Compagnie de distribution d'électricité","colors": ["lightgreen"],"prix": 15000,"loyers": [400,1000],"img": "light"}, 26 | {"type": "propriete","groupe": "Violet clair","axe": 3,"pos": 3,"nom": "Avenue de Neuilly","colors": ["#73316F", "#DFB4DC"],"prix": 14000,"loyers": [1000, 5000, 15000, 45000, 62500, 75000],"prixMaison": 10000}, 27 | {"type": "propriete","groupe": "Violet clair","axe": 3,"pos": 4,"nom": "Rue de Paradis","colors": ["#73316F", "#DFB4DC"],"prix": 16000,"loyers": [1200, 6000, 18000, 50000, 70000, 90000],"prixMaison": 10000}, 28 | {"type": "gare","axe": 3,"pos": 5,"nom": "Gare de Lyon","colors": ["#000000", "#ABABAB"],"prix": 20000,"loyers": [2500, 5000, 10000, 20000]}, 29 | {"type": "propriete","groupe": "Orange","axe": 3,"pos": 6,"nom": "Avenue Mozart","colors": ["#D16E2D", "#FECC84"],"prix": 18000,"loyers": [1400, 7000, 20000, 55000, 75000, 95000],"prixMaison": 10000}, 30 | {"type": "communaute","axe": 3,"pos": 7}, 31 | {"type": "propriete","groupe": "Orange","axe": 3,"pos": 8,"nom": "Boulevard Saint-Michel","colors": ["#D16E2D", "#FECC84"],"prix": 18000,"loyers": [1400, 7000, 20000, 55000, 75000, 95000],"prixMaison": 10000}, 32 | {"type": "propriete","groupe": "Orange","axe": 3,"pos": 9,"nom": "Place Pigalle","colors": ["#D16E2D", "#FECC84"],"prix": 20000,"loyers": [1600, 8000, 22000, 60000, 80000, 100000],"prixMaison": 10000}, 33 | {"type": "parc","axe": 0,"pos": 0,"nom": "Parc Gratuit"}, 34 | {"type": "propriete","groupe": "Rouge","axe": 0,"pos": 1,"nom": "Avenue Matignon","colors": ["#D32C19", "#F9AEA6"],"prix": 22000,"loyers": [1800, 9000, 25000, 70000, 87500, 105000],"prixMaison": 15000}, 35 | {"type": "chance","axe": 0,"pos": 2}, 36 | {"type": "propriete","groupe": "Rouge","axe": 0,"pos": 3,"nom": "Boulevard Malsherbes","colors": ["#D32C19", "#F9AEA6"],"prix": 22000,"loyers": [1800, 9000, 25000, 70000, 87500, 105000],"prixMaison": 15000}, 37 | {"type": "propriete","groupe": "Rouge","axe": 0,"pos": 4,"nom": "Avenue Henri-Martin","colors": ["#D32C19", "#F9AEA6"],"prix": 24000,"loyers": [2000, 10000, 30000, 75000, 92500, 110000],"prixMaison": 15000}, 38 | {"type": "gare","axe": 0,"pos": 5,"nom": "Gare du Nord","colors": ["#000000", "#ABABAB"],"prix": 20000,"loyers": [2500, 5000, 10000, 20000]}, 39 | {"type": "propriete","groupe": "Jaune","axe": 0,"pos": 6,"nom": "Boulevard Saint-Honoré","colors": ["#E6E018", "#F8F587"],"prix": 26000,"loyers": [2200, 11000, 33000, 80000, 97500, 115000],"prixMaison": 15000}, 40 | {"type": "propriete","groupe": "Jaune","axe": 0,"pos": 7,"nom": "Place de la Bourse","colors": ["#E6E018", "#F8F587"],"prix": 26000,"loyers": [2200, 11000, 33000, 80000, 97500, 115000],"prixMaison": 15000}, 41 | {"type": "compagnie","axe": 0,"pos": 8,"nom": "Compagnie de distribution des eaux","colors": ["lightgreen"],"prix": 15000,"loyers": [400,1000],"img": "water"}, 42 | {"type": "propriete","groupe": "Jaune","axe": 0,"pos": 9,"nom": "Rue Lafayette","colors": ["#E6E018", "#F8F587"],"prix": 28000,"loyers": [2400, 12000, 36000, 85000, 102500, 120000],"prixMaison": 15000}, 43 | {"type": "prison","axe": 1,"pos": 0,"nom": "Allez en prison"}, 44 | {"type": "propriete","groupe": "Vert","axe": 1,"pos": 1,"nom": "Avenue de Breteuil","colors": ["#11862E", "#93D1A2"],"prix": 30000,"loyers": [2600, 13000, 39000, 90000, 110000, 127500],"prixMaison": 20000}, 45 | {"type": "propriete","groupe": "Vert","axe": 1,"pos": 2,"nom": "Avenue Foch","colors": ["#11862E", "#93D1A2"],"prix": 30000,"loyers": [2600, 13000, 39000, 90000, 110000, 127500],"prixMaison": 20000}, 46 | {"type": "communaute","axe": 1,"pos": 3}, 47 | {"type": "propriete","groupe": "Vert","axe": 1,"pos": 4,"nom": "Boulevard des Capucines","colors": ["#11862E", "#93D1A2"],"prix": 32000,"loyers": [2800, 15000, 45000, 100000, 120000, 140000],"prixMaison": 20000}, 48 | {"type": "gare","axe": 1,"pos": 5,"nom": "Gare Saint-Lazarre","colors": ["#000000", "#ABABAB"],"prix": 20000,"loyers": [2500, 5000, 10000, 20000]}, 49 | {"type": "chance","axe": 1,"pos": 6}, 50 | {"type": "propriete","groupe": "Bleu","axe": 1,"pos": 7,"nom": "Avenue des Champs Elysées","colors": ["#132450", "#808EB0"],"prix": 35000,"loyers": [3500, 17500, 50000, 110000, 130000, 150000],"prixMaison": 20000}, 51 | {"type": "taxe","axe": 1,"pos": 8,"nom": "Taxe de luxe","prix": 10000}, 52 | {"type": "propriete","groupe": "Bleu","axe": 1,"pos": 9,"nom": "Rue de la Paix","colors": ["#132450", "#808EB0"],"prix": 40000,"loyers": [5000, 20000, 60000, 140000, 170000, 200000],"prixMaison": 20000} 53 | ], 54 | "images":{ 55 | "taxe": { 56 | "src": "img/bijou.png", 57 | "width": 40, 58 | "height": 50 59 | }, 60 | "chance":{ 61 | "src": "img/interrogation.png", 62 | "width": 50, 63 | "height": 60 64 | }, 65 | "caisseDeCommunaute":{ 66 | "src": "img/banque2.png", 67 | "width": 60, 68 | "height": 60 69 | }, 70 | "compagnie":{ 71 | "src": "img/light2.png", 72 | "width": 28, 73 | "height": 28, 74 | "margin": 17 75 | }, 76 | "water":{ 77 | "src": "img/tap.png", 78 | "width": 28, 79 | "height": 28, 80 | "margin": 17 81 | }, 82 | "light":{ 83 | "src": "img/light2.png", 84 | "width": 28, 85 | "height": 28, 86 | "margin": 17 87 | }, 88 | "gare":{ 89 | "src": "img/big_train.png", 90 | "width": 50, 91 | "height": 35, 92 | "margin": 5 93 | } 94 | }, 95 | "titles":{ 96 | "chance":"Chance", 97 | "communaute":"Caisse de Communauté" 98 | }, 99 | "chance":{ 100 | "cartes":[ 101 | {"nom":"C'est votre anniversaire, chaque joueur vous donne F 1.000","type":"birthday","montant":1000}, 102 | {"nom":"Faites des réparations dans toutes vos maisons : F 11.500 par hôtel et F 4.500 par maison","type":"repair","hotel":11500,"maison":4500}, 103 | {"nom":"Payer pour frais de scolarité F 15.000","type":"taxe","montant":15000}, 104 | {"nom":"Amende pour excès de vitesse : F 1.500","type":"taxe","montant":1500}, 105 | {"nom":"Vous avez gagné le prix de mots croisés. Recevez F 10.000","type":"prime","montant":10000}, 106 | {"nom":"La banque vous verse un dividende de F 5.000","type":"prime","montant":5000}, 107 | {"nom":"Votre immeuble et votre prêt rapportent. Vous devez toucher F 15.000","type":"prime","montant":15000}, 108 | {"nom":"Amende pour ivresse : F 2.000","type":"taxe","montant":2000}, 109 | {"nom":"Rendez vous à l'avenue Henri-Martin. Si vous passez par la case Départ, recevez F 20.000","type":"goto","axe":0,"pos":4}, 110 | {"nom":"Rendez vous à la Rue de la Paix","type":"goto","axe":1,"pos":9}, 111 | {"nom":"Reculez de 3 cases","type":"move","nb":-3,"primeDepart": false,"direct": true}, 112 | {"nom":"Avancez jusqu'à la case Départ","type":"goto","axe":2,"pos":0}, 113 | {"nom":"Rendez vous à la gare de Lyon. Si vous passez par la case Départ, recevez F 20.000","type":"goto","axe":3,"pos":5}, 114 | {"nom":"Avancez au Boulevard de la Villette. Si vous passez par la case Départ, recevez F 20.000","type":"goto","axe":3,"pos":1}, 115 | {"nom":"Allez en prison. Rendez vous directement à la prison. Ne franchissez par la case Départ. Ne touchez pas F 20.000","type":"goto","axe":1,"pos":0,"direct":true} 116 | ] 117 | }, 118 | "communaute":{ 119 | "cartes":[ 120 | {"nom":"La vente de votre stock pour rapport F 5.000","type":"prime","montant":5000}, 121 | {"nom":"Payez votre Police d'Assurance s'élevant à F 5.000","type":"taxe","montant":5000}, 122 | {"nom":"Recevez votre revenu annuel F 10.000","type":"prime","montant":10000}, 123 | {"nom":"Vous avez gagné le deuxième Prix de Beauté. Recevez F 1.000","type":"prime","montant":1000}, 124 | {"nom":"Payez la note du Médecin F 5.000","type":"taxe","montant":5000}, 125 | {"nom":"Les Contributions vous remboursent la somme de F 2.000","type":"prime","montant":2000}, 126 | {"nom":"Erreur de la Banque en votre faveur Recevez F 20.000","type":"prime","montant":20000}, 127 | {"nom":"Recevez votre intérêt sur l'emprunt à 7 % F 2.500","type":"prime","montant":2500}, 128 | {"nom":"Payez à l'Hôpital F 10.000","type":"taxe","montant":10000}, 129 | {"nom":"Vous héritez F 10.000","type":"prime","montant":10000}, 130 | {"nom":"Placez vous sur la case Départ","type":"goto","axe":2,"pos":0}, 131 | {"nom":"Allez en prison. Avancez tout droit en prison. Ne passez par la case Départ. Ne touchez pas F 20.000","type":"goto","axe":1,"pos":0,"direct":true}, 132 | {"nom":"Retournez à Belleville.","type":"goto","axe":2,"pos":1,"direct":true,"primeDepart": false}, 133 | {"nom":"Vous êtes libéré de prison","type":"prison"} 134 | ] 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /js/entity/strategie.js: -------------------------------------------------------------------------------- 1 | /* Gestion de la strategie */ 2 | import {GestionFiche} from "../display/case_jeu.js"; 3 | import {ETAT_LIBRE} from "../display/case_jeu.js"; 4 | import {GestionJoueur} from "../core/gestion_joueurs.js"; 5 | 6 | /* @Abstract */ 7 | /* Objet qui gere la strategie. IL y a differentes implementations */ 8 | /* @colors : liste des groupes qui interessent le joueur */ 9 | /* @param agressif : plus il est eleve, plus le joueur fait de l'antijeu (achat des terrains recherches par les adversaires) */ 10 | class Strategie { 11 | constructor(colors, agressif, name, id, interetGare=(Math.round(Math.random() * 1000) % 3 === 0)) { 12 | this.groups = colors; 13 | this.agressif = agressif; 14 | this.interetGare = interetGare; // Interet pour gare 15 | this.name = name; 16 | this.id = id; 17 | } 18 | 19 | _containGroup(value) { 20 | for (let val in this.groups) { 21 | if (this.groups[val] === value) { 22 | return true; 23 | } 24 | } 25 | return false; 26 | } 27 | 28 | /* Renvoie des stats sur les proprietes concernees par cette strategie : nombre de propriete, nombre de libre... */ 29 | getStatsProprietes(){ 30 | let stats = { 31 | color: { 32 | total: 0, 33 | libre: 0, 34 | achete: 0, 35 | pourcent: 0 36 | }, 37 | all: { 38 | total: 0, 39 | libre: 0, 40 | achete: 0, 41 | pourcent: 0 42 | } 43 | }; 44 | let it = GestionFiche.iteratorTerrains(); 45 | while (it.hasNext()) { 46 | let fiche = it.next(); 47 | if (fiche.statut != null) { 48 | stats.all.total++; 49 | if (fiche.statut === ETAT_LIBRE) { 50 | stats.all.libre++; 51 | } else { 52 | stats.all.achete++; 53 | } 54 | if (this._containGroup(fiche.color)) { 55 | stats.color.total++; 56 | if (fiche.statut === ETAT_LIBRE) { 57 | stats.color.libre++; 58 | } else { 59 | stats.color.achete++; 60 | } 61 | } 62 | } 63 | } 64 | stats.color.pourcent = (stats.color.libre / stats.color.total) * 100; 65 | stats.all.pourcent = (stats.all.libre / stats.all.total) * 100; 66 | return stats; 67 | } 68 | 69 | /* Calcul l'interet global du joueur pour une propriete */ 70 | /* Prend en compte l'interet propre (liste d'achat) ainsi que l'etat du groupe */ 71 | /* Si ce n'est pas un terrain (gare, compagnie), la valeur est plus faible */ 72 | /* @param isEnchere : indique que la mesure est faite pour une enchere */ 73 | /* Cas des encheres, on ajoute un critere qui determine si le terrain est indispensable pour la strategie : autre groupe, autre terrain... */ 74 | interetGlobal(propriete, joueur, isEnchere) { 75 | let i1 = this.interetPropriete(propriete); 76 | let statutGroup = this.statutGroup(propriete, joueur, isEnchere); 77 | let i2 = statutGroup.statut; 78 | let interet = {interet:1}; 79 | if (i1 === false && i2 === 0) { 80 | interet = {interet:0.2}; // Permet en cas de situation tres confortable de continuer a investir 81 | } 82 | // Realise un blocage 83 | if (i1 === false && i2 === 2) { 84 | interet = {interet:this.agressif,joueur:statutGroup.joueur}; 85 | } 86 | if (i1 === true && i2 === 3) { 87 | interet = {interet:4}; 88 | } 89 | // Pas dans la strategie mais permet de completer le groupe et de construire 90 | if (i1 === false && i2 === 3) { 91 | interet = {interet:2}; 92 | } 93 | // En possede deja 94 | if(i1 === true && i2 === 5){ 95 | interet = {interet:1.5}; 96 | } 97 | if(isEnchere){ 98 | interet = {interet:this.interetProprieteInStrategie(propriete)}; 99 | } 100 | if(!propriete.isTerrain()){ 101 | interet.interet/=2; 102 | } 103 | 104 | return interet; 105 | } 106 | 107 | /* Determine l'interet de la propriete par rapport a l'etat de la strategie */ 108 | /* @return : un nombre inférieur à 1 */ 109 | interetProprieteInStrategie (){ 110 | let stats = this.getStatsProprietes(); 111 | /* Si les terrains sont presques tous libres, renvoie un calcul logarithmique (entre 67 et 100% de terrain libre) */ 112 | return 0.5 + parseFloat((Math.log(((Math.min(100-stats.color.pourcent,32.5))/50)+1)).toFixed(2)); 113 | } 114 | 115 | /* Calcul l'interet pour la maison (a partir des groupes interessant) */ 116 | interetPropriete (propriete) { 117 | for (let color in this.groups) { 118 | if (this.groups[color] === propriete.color || (propriete.type === 'gare' && this.interetGare)) { 119 | return true; 120 | } 121 | } 122 | return false; 123 | } 124 | 125 | /* Renvoie le statut de la famille : 126 | 0 : toutes les proprietes sont libres 127 | 1 : s'il reste des libres apres celle ci 128 | 2 : si toutes appartiennent a une meme personne sauf celle-ci 129 | 3 : si toutes appartiennent au joueur sauf celle-ci 130 | 4 : autres 131 | 5 : joueur en possede deja une de la famille */ 132 | /* @param isEnchere : achat du terrain a un autre joueur, on ne prend pas en compte le statut libre */ 133 | statutGroup (propriete, joueur, isEnchere) { 134 | let nbTotal = 0; 135 | let nbLibre = 0; 136 | let dernierJoueur = null; 137 | let nbEquals = 0; 138 | let nbPossede = 0; 139 | for (let id in propriete.groupe.fiches) { 140 | let fiche = propriete.groupe.fiches[id]; 141 | nbTotal++; 142 | if (fiche.statut === ETAT_LIBRE) { 143 | nbLibre++; 144 | } else { 145 | if (fiche.joueurPossede.equals(joueur)) { 146 | nbPossede++; 147 | } else { 148 | if (dernierJoueur == null || fiche.joueurPossede.equals(dernierJoueur)) { 149 | nbEquals++; 150 | } 151 | } 152 | dernierJoueur = fiche.joueurPossede; 153 | } 154 | } 155 | if (nbLibre === nbTotal) { 156 | return {statut:0}; 157 | } 158 | 159 | if (nbLibre === 1 && nbEquals === nbTotal - 1) { 160 | return {statut:2,joueur:dernierJoueur}; 161 | } 162 | /* Cas ou seul terrain manquant */ 163 | if ((nbLibre === 1 || isEnchere) && nbPossede === nbTotal - 1) { 164 | return {statut:3}; 165 | } 166 | /* Cas ou on en possede deja 1 */ 167 | if (nbPossede > 0 && nbLibre > 0) { 168 | return {statut:5}; 169 | } 170 | if (nbLibre > 0) { 171 | return {statut:1}; 172 | } 173 | return {statut:4}; 174 | } 175 | 176 | /* Calcule le fait d'accepter un terrain d'un joueur. 177 | * Se base sur le fait que le joueur a un deja un groupe, qu'il n'en a aucun. 178 | * Renvoie un facteur jouant sur le calcul final. 0 est bloquant, 1 est neutre... 179 | * @param otherInteresets : autres terrains qui interesent le joueur 180 | * @param interestGroupe : indique que le groupe interesse aussi le joueur 181 | */ 182 | acceptSwapTerrain (terrain, joueur, otherInterests, interestGroupe) { 183 | /* Calcule si le proprio est le seul fournisseur */ 184 | let alone = GestionJoueur.getNb() > 2; // Faux si seulement 2 joueurs 185 | /* Seul groupe qui m'interesse, on refuse */ 186 | if ((interestGroupe === true && otherInterests.length === 1) || terrain.isGrouped()) { 187 | return 0; 188 | } 189 | for (let idx in otherInterests) { 190 | if (!otherInterests[idx].maison.joueurPossede.equals(terrain.joueurPossede)) { 191 | alone = false; 192 | } 193 | } 194 | let nbGroups = joueur.maisons.findConstructiblesGroupes().size(); 195 | /* Le proprio est le seul a pouvoir aider le demandeur et il n'a pas encore de groupe */ 196 | if (nbGroups === 0 && otherInterests.length === 0 && alone) { 197 | return this.agressif === 2 ? 0.5 : 1; 198 | } 199 | /* Beaucoup de groupe et seul fournisseur, on bloque si on est vicieux, on monte sinon */ 200 | if (nbGroups >= 2 && alone) { 201 | return this.agressif > 0 ? 0 : 0.5; 202 | } 203 | 204 | /* Personne n'a de groupe et pas seul fournisseur */ 205 | if (nbGroups === 0) { 206 | return 1.5; 207 | } 208 | 209 | /* Beaucoup de groupe mais pas le seul fournisseur, on ne bloque pas */ 210 | if (nbGroups >= 2) { 211 | return this.agressif > 0 ? 0.5 : 1; 212 | } 213 | return 1; 214 | } 215 | 216 | toString(){ 217 | return this.name + ((this.interetGare)?' ':''); 218 | } 219 | } 220 | 221 | /* Achete en priorite les terrains les moins chers : bleu marine-812B5C, bleu clair-119AEB, violet-73316F et orange-D16E2D */ 222 | class CheapStrategie extends Strategie { 223 | constructor() { 224 | super(["#812B5C", "#119AEB", "#73316F", "#D16E2D"], 0, "Econome", 0); 225 | } 226 | } 227 | 228 | /* Achete en priorite les terrains moyennement chers : violet-73316F, orange-D16E2D, rouge-D32C19 et jaune-E6E018 */ 229 | class MediumStrategie extends Strategie { 230 | constructor() { 231 | super(["#73316F", "#D16E2D", "#D32C19", "#E6E018"], 1, "Normal", 1); 232 | } 233 | } 234 | 235 | /* Achete en priorite les terrains les plus chers : rouge-D32C19, jaune-E6E018, vert-11862E et bleu fonce-132450 */ 236 | class HardStrategie extends Strategie { 237 | constructor() { 238 | super(["#D32C19", "#E6E018", "#11862E", "#132450"], 2, "Luxe", 2); 239 | } 240 | } 241 | 242 | /* Achete en priorite les terrains les meilleurs (gare, orange-D16E2D, rouge-D32C19, jaune-E6E018) */ 243 | class SmartStrategie extends Strategie { 244 | constructor() { 245 | super(["#D16E2D", "#D32C19", "#E6E018"], 2, "Futé", 3, true); 246 | } 247 | } 248 | 249 | /* Achete tout */ 250 | class CrazyStrategie extends Strategie { 251 | constructor() { 252 | super(["#812B5C", "#119AEB", "#73316F", "#D16E2D", "#D32C19", "#E6E018", "#11862E", "#132450"], 4, "Dingue", 3, true); 253 | } 254 | } 255 | 256 | let GestionStrategie = { 257 | strategies : [CheapStrategie, MediumStrategie, HardStrategie,SmartStrategie,CrazyStrategie], 258 | create:function(id){ 259 | return new this.strategies[id](); 260 | }, 261 | createRandom:function(){ 262 | return this.create(Math.round(Math.random() * 1000)%this.strategies.length); 263 | }, 264 | getAll:function(){ 265 | return this.strategies; 266 | }, 267 | length:function(){ 268 | return this.strategies.length; 269 | } 270 | }; 271 | 272 | export {GestionStrategie}; -------------------------------------------------------------------------------- /js/core/gestion_joueurs.js: -------------------------------------------------------------------------------- 1 | import {Joueur,NetworkJoueur} from '../entity/joueur.js' 2 | import {RemotePlayer,MasterRemotePlayer,LocalPlayer} from '../entity/network/local_joueur.js' 3 | import {JoueurOrdinateur,NetworkJoueurOrdinateur} from "../entity/joueur_robot.js"; 4 | import {CURRENCY,VARIANTES,globalStats,restartMonopoly} from "./monopoly.js" 5 | import {GestionFiche} from "../display/case_jeu.js"; 6 | import {GestionEchange} from "./enchere.js"; 7 | import {GestionDes} from "../entity/dices.js"; 8 | import {infoMessage} from "../display/message.js"; 9 | import {bus} from "../bus_message.js"; 10 | import {dialog} from "../display/displayers.js"; 11 | import {disableActions, enableActions} from "../utils.js"; 12 | 13 | /* Gere les joueurs : creation, changement... */ 14 | let GestionJoueur = { 15 | colorsJoueurs : ["#383C89", "#A6193E", "#C58F01", "#086B3D", "#B9B29B","#663300"], 16 | imgJoueurs : [], 17 | joueurs:[], 18 | joueurCourant:null, 19 | getJoueurCourant(){ 20 | return this.joueurCourant; 21 | }, 22 | setColors(colors){ 23 | if(colors !=null){ 24 | this.colorsJoueurs = colors; 25 | } 26 | }, 27 | setImgJoueurs(img){ 28 | if(img != null){ 29 | this.imgJoueurs = img; 30 | } 31 | }, 32 | getNbJoueurs(){ 33 | return this.joueurs.length; 34 | }, 35 | // renvoi le nombre de joueur dans la partie 36 | getNb(){ 37 | return this.joueurs.reduce((somme,j)=>somme+(!j.defaite?1:0),0); 38 | }, 39 | createAndLoad(isRobot,i,nom,data,montantDepart){ 40 | let clazzPlayer = isRobot ? JoueurFactory.getRobotPlayer():JoueurFactory.getCurrentPlayer() 41 | return this.create(clazzPlayer,i,nom,data.defaite,0,montantDepart).saver.load(data); 42 | }, 43 | init(){ 44 | document.querySelectorAll('.panneau_joueur').forEach(e=>e.innerHTML = ''); 45 | this.joueurs = []; 46 | this.joueurCourant = null; 47 | }, 48 | lancerDes(){ 49 | this.joueurCourant.lancerDes(); 50 | }, 51 | displayLineByGroupsAsElement(){ 52 | let groups = GestionFiche.getGroups(); 53 | const sub = (groups.size()+1) * 5; 54 | const size = `calc((45vw - ${sub}px) / ${groups.size()+1})`; 55 | let sizeBlock = 35 / groups.size(); 56 | let div = document.createElement('div'); 57 | div.classList.add('count-property'); 58 | for(let name in groups){ 59 | let color = name.replace(/ /g,""); 60 | div.insertAdjacentHTML('beforeend',`0`); 61 | } 62 | return div; 63 | }, 64 | create(clazz, i, nom,defaite, argentDepart, montantDepart){ 65 | let id = `joueur${i}`; 66 | let color = this.colorsJoueurs[i]; 67 | let img = this.imgJoueurs[i]; 68 | let joueur = new clazz(i, nom, color,argentDepart,montantDepart); 69 | joueur.enPrison = false; 70 | joueur.defaite = defaite; 71 | joueur.setEnableMouseFunction(JoueurFactory.mouseFunction); 72 | const parent = document.querySelector(`.player_${i%2===0?'left':'right'}`) 73 | const div = document.createElement('div'); 74 | div.id = id; 75 | if(defaite){ 76 | div.classList.add('defaite'); 77 | } 78 | 79 | div.insertAdjacentHTML('beforeend',`
${joueur.nom} : ${CURRENCY}
`) 80 | div.querySelectorAll('div.joueur-bloc').forEach(d=>d.style.setProperty('background-image', `linear-gradient(to right,white 50%,${color})`)); 81 | div.append(this.displayLineByGroupsAsElement()); 82 | 83 | parent.insertAdjacentHTML('beforeend','
'); 84 | 85 | parent.append(div); 86 | joueur.setDiv(div); 87 | joueur.setPion(color,img,montantDepart); 88 | 89 | bus.send('monopoly.newPlayer', { 90 | joueur: joueur 91 | }); 92 | this.joueurs.push(joueur); 93 | return joueur; 94 | }, 95 | // Search by numero or playerID 96 | getByUniqueId(id){ 97 | return this.joueurs.find(j=>j.uniqueID != null && j.uniqueID === id); 98 | }, 99 | getById(id){ 100 | return this.joueurs.find(j=>j.numero ===parseInt(id) || j.playerID === id); 101 | }, 102 | /* @param idInitialJoueur : si present, joueur qui debute */ 103 | change(idInitialJoueur){ 104 | if(this.joueurCourant != null){ 105 | this.joueurCourant.endTurn(); 106 | } 107 | if(idInitialJoueur!=null){ 108 | let joueur = this.getById(idInitialJoueur); 109 | if(joueur!=null){ 110 | joueur.notifySelect(); 111 | return this._select(joueur); 112 | } 113 | } 114 | // Si un echange est en cours, on ne change pas de joueur 115 | if (GestionEchange.running) { 116 | return; 117 | } 118 | // Joueur bloque, on le debloque avant de continuer 119 | let joueur = null; 120 | try { 121 | joueur = this.next(); 122 | if(joueur != null && (GestionJoueur.getJoueurCourant() == null || joueur.id !== GestionJoueur.getJoueurCourant().id)) { 123 | bus.debug({ 124 | message: `Change player to ${joueur.nom}` 125 | }); 126 | } 127 | } catch (gagnant) { 128 | this._showVainqueur(gagnant); 129 | return null; 130 | } 131 | if (joueur == null) { 132 | return null; 133 | } 134 | if(GestionJoueur.getJoueurCourant() == null || joueur.id !== GestionJoueur.getJoueurCourant().id){ 135 | joueur.notifySelect(); 136 | GestionDes.resetDouble(); 137 | } 138 | this._select(joueur); 139 | }, 140 | // Renvoie la liste des joueurs disponibles 141 | getAvailableSlots(){ 142 | return this.joueurs.filter(j=>j.isSlotFree()); 143 | }, 144 | _showVainqueur(gagnant){ 145 | bus.send('monopoly.victoire',{joueur:gagnant}); 146 | // On affiche les resultats complets 147 | const perdants = this.joueurs.filter(function(j){return j.defaite;}); 148 | perdants.sort((a,b)=>{ 149 | if(a.tourDefaite === b.tourDefaite){ 150 | return b.numero - a.numero; 151 | } 152 | return b.tourDefaite - a.tourDefaite; 153 | }); 154 | let message = `Le joueur ${gagnant.nom} a gagné en ${this._formatTempsJeu(globalStats.heureDebut)}.
`; 155 | message+=`1 - ${gagnant.nom}
`; 156 | 157 | message+= perdants.map((a,i)=>`${(i+2)} - ${a.nom}`).join("
") 158 | let score = this._calculateScore(gagnant); 159 | infoMessage.create(this.joueurCourant,`Fin de partie : ${score} Points`, "green", message, ()=>{}, null, true,{"Recommencer":()=>{ 160 | dialog.close(); 161 | restartMonopoly(); 162 | }}); 163 | }, 164 | /* Calcule un score de victoire */ 165 | /* Prend en compte l'argent, le nombre de terrains, le nombre de constructions, le nombre de tours des joueurs adverses */ 166 | /* On pondere par rapport au nombre de joueur (plus il est grand, plus le nombre de maison a de l'importance) */ 167 | _calculateScore(joueur){ 168 | let statsJoueur = joueur.getStats(); 169 | let critere1 = statsJoueur.argent/10000; 170 | let critere2 = (joueur.maisons.maisons.length*this.joueurs.length)/4; // < 1 171 | let critere3 = 1 + statsJoueur.hotel/12 + statsJoueur.maison/32; // < 2 172 | let critere4 = (statsJoueur.tour+1) * this.joueurs.length; // ~5 * nbJoueurs 173 | let critere5 = 1; // ~5 * nbJoueurs 174 | this.joueurs.forEach(j=>{ 175 | critere5+=(!j.equals(joueur))?j.pion.stats.tour:0; 176 | }); 177 | let score = (critere4 - critere5) * critere1 * critere2 * critere3; 178 | return Math.round(score); 179 | }, 180 | _formatTempsJeu(beginTime){ 181 | let time = Math.round((new Date().getTime() - beginTime)/1000); 182 | if(time < 60){ 183 | return time + " sec"; 184 | } 185 | const sec = time%60; 186 | time = Math.round(time/60); 187 | return `${time} min et ${sec} sec`; 188 | }, 189 | _select(joueur){ 190 | if(!joueur.canPlay){ 191 | disableActions(); 192 | }else{ 193 | enableActions(); 194 | } 195 | if(VARIANTES.echangeApresVente && GestionFiche.isFreeFiches()){ 196 | document.getElementById('idEchangeTerrains').setAttribute('disabled','disabled'); 197 | document.getElementById('idEchangeTerrains').classList.add('disabled'); 198 | } 199 | if (!joueur.equals(this.joueurCourant)) { 200 | document.querySelectorAll('.joueurCourant').forEach(d=>d.classList.remove('joueurCourant')) 201 | if(this.joueurCourant!=null && this.joueurCourant.pion !=null){ 202 | this.joueurCourant.pion.pion.setSelected(false); 203 | } 204 | } 205 | 206 | this.joueurCourant = joueur; 207 | this.joueurCourant.pion.pion.setSelected(true); 208 | joueur.select(); 209 | }, 210 | getWinner() { 211 | let defaites = 0; 212 | let gagnantProbable; 213 | for (let index in this.joueurs) { 214 | if (this.joueurs[index].defaite === true) { 215 | defaites++; 216 | } else { 217 | gagnantProbable = this.joueurs[index]; 218 | } 219 | } 220 | if (defaites === this.joueurs.length - 1) { 221 | return gagnantProbable; 222 | } 223 | return null; 224 | }, 225 | next(){ 226 | if (this.joueurCourant == null) { 227 | return this.joueurs[0]; 228 | } 229 | // On verifie s'il y a encore des joueurs "vivants" 230 | if (this.joueurCourant.bloque) { 231 | return null; 232 | } 233 | let gagnant = this.getWinner(); 234 | if (gagnant != null) { 235 | // On a un vainqueur 236 | throw gagnant; 237 | } 238 | let joueur = this.joueurCourant; 239 | /* Changement de joueur */ 240 | if(!GestionDes.continuePlayer() && !GestionDes.isSpecificAction()){ 241 | let pos = 0; 242 | joueur = this.joueurs[(joueur.numero + 1) % (this.joueurs.length)]; 243 | while (joueur.defaite === true && pos++ < this.joueurs.length) { 244 | joueur = this.joueurs[(joueur.numero + 1) % (this.joueurs.length)]; 245 | } 246 | // On incremente le nb de tours 247 | if (joueur.numero < this.joueurCourant.numero) { 248 | globalStats.nbTours++; 249 | } 250 | } 251 | if(GestionDes.isSpecificAction()){ 252 | GestionDes.doSpecificAction(); 253 | // On ne laisse pas le joueur jouer, on enchaine l'action 254 | return null; 255 | } 256 | return joueur; 257 | }, 258 | /* @param element : sera represente par "this" dans la methode callback */ 259 | forEach(callback,element){ 260 | this.joueurs.forEach(callback,element); 261 | } 262 | }; 263 | 264 | 265 | window.gestion = GestionJoueur; 266 | 267 | // Construit les joueurs en fonction du context 268 | let JoueurFactory = { 269 | // Play with network 270 | network:false, 271 | // Current instance is master of network game 272 | master:true, 273 | setMouseFunction(fct){ 274 | this.mouseFunction = fct; 275 | }, 276 | useNetwork(){ 277 | this.network = true; 278 | }, 279 | useLocal(){ 280 | this.network = false; 281 | }, 282 | setMaster(){ 283 | this.master = true; 284 | }, 285 | setSlave(){ 286 | this.master = false; 287 | }, 288 | getRobotPlayer(){ 289 | return this.network ? NetworkJoueurOrdinateur:JoueurOrdinateur; 290 | }, 291 | getCurrentPlayer(){ 292 | return this.network ? (this.master ? NetworkJoueur : LocalPlayer) : Joueur; 293 | }, 294 | getOtherPlayer(){ 295 | return this.network ? (this.master ? MasterRemotePlayer : RemotePlayer) : Joueur; 296 | } 297 | }; 298 | 299 | export {GestionJoueur,JoueurFactory}; 300 | -------------------------------------------------------------------------------- /js/ui/circle_graphics.js: -------------------------------------------------------------------------------- 1 | import {DrawerFactory,Component} from "./graphics.js"; 2 | import {Des,DesRapide} from './square_graphics.js' 3 | import {DrawerHelper} from "./graphics.js"; 4 | import {VARIANTES} from "../core/monopoly.js"; 5 | import {bus} from "../bus_message.js"; 6 | import {GestionFiche} from "../display/case_jeu.js"; 7 | import {deepCopy} from "../utils.js"; 8 | 9 | /* Implementation pour plateau carree */ 10 | 11 | let pasAngle = (2 * Math.PI)/40; // Nombre de case du jeu 12 | let nbJoueurs = 0; // Permet de definir la position des joueurs 13 | 14 | function getCoords(angle,rayon){ 15 | return { 16 | y:(Math.sin(angle))*rayon, 17 | x:(Math.cos(angle))*rayon 18 | } 19 | } 20 | 21 | function convertAxePos(axe,pos){ 22 | return ((axe+2)%4)*10 + pos; 23 | } 24 | 25 | // Represente un pion d'un joueur 26 | class CirclePionJoueur extends Component { 27 | constructor(color, largeur,img) { 28 | super(); 29 | this.color = color; 30 | this.isSelected = false; 31 | this.largeur = largeur / 2; // Largeur du pion 32 | this.img = null; 33 | if (img != null) { 34 | this.img = new Image(); 35 | this.img.src = img; 36 | this.largeur += 10; 37 | } 38 | this.init(2,0); 39 | } 40 | 41 | init(axe,pos){ 42 | let center = DrawerFactory.dimensions.plateauSize/2; 43 | this.pos = convertAxePos(axe,pos); 44 | /* Gere le decalage de chaque joueur lors de la creation */ 45 | this._rayon = center- (70 + (nbJoueurs%3)*25); 46 | this._angle = 0.5 + ((nbJoueurs%2)?1:-1) * 0.25; 47 | nbJoueurs++; 48 | } 49 | 50 | draw (canvas) { 51 | let centrePlateau = DrawerFactory.dimensions.plateauSize/2; 52 | let centre = getCoords((this.pos+this._angle)*pasAngle,this._rayon); 53 | if(this.isSelected){ 54 | DrawerHelper.drawCircle(canvas,"#FFFFFF",(this.largeur+2) / 2,{x:centre.x+centrePlateau,y:centre.y+centrePlateau},"#FF0000"); 55 | } 56 | if(this.img!=null){ 57 | DrawerHelper.drawImage(canvas, this.img, centre.x+centrePlateau-this.largeur/2, centre.y+centrePlateau-this.largeur/2, this.largeur, this.largeur, 0); 58 | } 59 | else{ 60 | DrawerHelper.drawCircle(canvas,this.color,this.largeur,{x:centre.x+centrePlateau,y:centre.y+centrePlateau},"#FF0000"); 61 | } 62 | } 63 | setSelected (value){ 64 | this.isSelected = value; 65 | } 66 | 67 | _moveTo (ciblePos,callback,pas){ 68 | if(this.pos!==ciblePos){ 69 | setTimeout(()=>{ 70 | this.pos=parseFloat((this.pos + pas).toFixed(1)); 71 | if(this.pos>=40){ 72 | this.pos = 0; 73 | } 74 | this._moveTo(ciblePos,callback,pas); 75 | },30); 76 | } 77 | else{ 78 | callback(); 79 | } 80 | } 81 | 82 | // Se dirige vers une cellule donnee. Se deplace sur la case suivante et relance l'algo 83 | goto(axe, pos, callback) { 84 | if(VARIANTES.quickMove){ 85 | return this.gotoDirect(axe,pos,callback); 86 | } 87 | let ciblePos = convertAxePos(axe,pos); 88 | this._moveTo(ciblePos,callback,0.1); 89 | } 90 | 91 | gotoDirect (axe, pos, callback){ 92 | if (axe == null || pos == null) { 93 | return; 94 | } 95 | let ciblePos = convertAxePos(axe,pos); 96 | this._moveTo(ciblePos,callback,1); 97 | } 98 | } 99 | 100 | class CircleCase extends Component{ 101 | constructor(pos, axe, color, title, prix, img) { 102 | super(); 103 | this.pos = convertAxePos(axe, pos); 104 | this.color = color; 105 | this.title = title; 106 | this.prix = prix; 107 | this.img = img; 108 | this.nbMaison = 0; 109 | this.data = {}; 110 | this.imageMaison = new Image(); 111 | this.imageHotel = new Image(); 112 | this.joueurPossede = null; 113 | 114 | this.init(); 115 | } 116 | setJoueur(joueur){ 117 | this.joueurPossede = joueur; 118 | } 119 | 120 | setNbMaison(nbMaison){ 121 | this.nbMaison = nbMaison; 122 | } 123 | 124 | init(){ 125 | this.imageMaison.src = "img/maison.png"; 126 | this.imageHotel.src = "img/hotel.png"; 127 | 128 | if (this.img != null) { 129 | this.img = deepCopy(DrawerFactory.infos.defaultImage, this.img); 130 | let image = new Image(); 131 | image.addEventListener('load',()=>{ 132 | bus.refresh(); 133 | }); 134 | image.src = this.img.src; 135 | image.height = Math.min(this.img.height,30); 136 | image.width = Math.min(this.img.width,40); 137 | image.margin = this.img.marginTop || 0; 138 | image.marginLeft = this.img.marginLeft || 0; 139 | image.rotate = this.img.rotate || 0; 140 | if(this.pos > 10 && this.pos < 30){ 141 | image.rotate = (image.rotate+180)%360; 142 | image.marginLeft = image.marginLeft +0.5; 143 | image.margin=Math.max(0,image.margin-20); 144 | } 145 | this.data.image = image; 146 | } 147 | } 148 | 149 | _drawColorGroup(canvas,param){ 150 | let bgColor = DrawerFactory.getInfo("backgroundColor"); 151 | if(this.color!=null){ 152 | canvas.beginPath(); 153 | canvas.fillStyle=this.color; 154 | canvas.moveTo(param.centre,param.centre); 155 | canvas.arc(param.centre,param.centre,param.centre,(this.pos)*pasAngle,(this.pos+1)*pasAngle); 156 | canvas.fill(); 157 | canvas.closePath(); 158 | canvas.beginPath(); 159 | canvas.fillStyle=bgColor; 160 | canvas.moveTo(param.centre,param.centre); 161 | canvas.arc(param.centre,param.centre,param.centre-param.bordure,this.pos*pasAngle,(this.pos+1)*pasAngle); 162 | canvas.fill(); 163 | canvas.closePath(); 164 | } 165 | } 166 | 167 | _drawPossede(canvas,param){ 168 | if(this.joueurPossede!=null){ 169 | canvas.beginPath(); 170 | canvas.strokeStyle=this.joueurPossede.color; 171 | canvas.lineWidth = 10; 172 | canvas.arc(param.centre,param.centre,param.centre-DrawerFactory.dimensions.innerPlateauSize+15,(this.pos)*pasAngle,(this.pos+1)*pasAngle); 173 | canvas.stroke(); 174 | canvas.closePath(); 175 | } 176 | } 177 | _writeText(canvas,value,param,angle){ 178 | DrawerHelper.writeText(value,param.p.x + param.centre,param.p.y + param.centre, angle*pasAngle, canvas,9,param.length,param.align); 179 | } 180 | _drawTitle(canvas,param){ 181 | if(this.title!=null){ 182 | if(this.pos > 10 && this.pos < 30){ 183 | param.p = getCoords((this.pos+0.7)*pasAngle,param.centre-param.bordure -5); 184 | param.align='left'; 185 | this._writeText(canvas,this.title,param,((this.pos+20)%40 + 0.8)); 186 | }else{ 187 | param.length = Math.min(param.length,canvas.measureText(this.title).width + 30); 188 | param.p = getCoords((this.pos+0.3)*pasAngle,param.centre -param.length - param.bordure -5); 189 | param.align='right'; 190 | this._writeText(canvas,this.title,param,this.pos + 0.2); 191 | } 192 | } 193 | } 194 | _drawPrice(canvas,param){ 195 | if(this.prix!=null){ 196 | if(this.pos > 10 && this.pos < 30){ 197 | param.p = getCoords((this.pos+0.15)*pasAngle,param.centre-param.bordure -5); 198 | param.align='left'; 199 | this._writeText(canvas,this.prix,param,((this.pos+20)%40 + 0.5)); 200 | }else{ 201 | param.length = Math.min(param.length,canvas.measureText(this.prix).width + 30); 202 | param.p = getCoords((this.pos+0.9)*pasAngle,param.centre - param.length- param.bordure -5); 203 | param.align='right'; 204 | this._writeText(canvas,this.prix,param,this.pos + 0.7); 205 | } 206 | } 207 | } 208 | _drawHouses(canvas,param){ 209 | if(this.nbMaison <= 4){ 210 | for(let i = 0 ; i < this.nbMaison ; i++){ 211 | let coords = getCoords((this.pos + 0.25*i)*pasAngle,param.centre-4); 212 | DrawerHelper.drawImage(canvas, this.imageMaison, param.centre+coords.x, param.centre+coords.y, 16,16, this.pos*pasAngle + Math.PI/2) 213 | } 214 | } 215 | else{ 216 | let coords = getCoords((this.pos + 0.4)*pasAngle,param.centre-4); 217 | DrawerHelper.drawImage(canvas, this.imageHotel, param.centre+coords.x, param.centre+coords.y, 16,16, this.pos*pasAngle + Math.PI/2) 218 | } 219 | } 220 | _drawImage(canvas,param){ 221 | if (this.data.image != null) { 222 | // Margin left est defini en portion d'angle (1 correspond a la largeur de la case), margin top joue sur la longueur du rayon 223 | let coords = getCoords((this.pos + this.data.image.marginLeft)*pasAngle,param.centre-param.bordure -5 - this.data.image.margin); 224 | let angle = DrawerHelper.fromDegresToRad(this.data.image.rotate) + this.pos*pasAngle + Math.PI/2; 225 | DrawerHelper.drawImage(canvas, this.data.image, param.centre+coords.x, param.centre+coords.y, this.data.image.width,this.data.image.height, angle); 226 | } 227 | } 228 | 229 | draw(canvas){ 230 | let centre = DrawerFactory.dimensions.plateauSize/2; 231 | let bordure = DrawerFactory.dimensions.bordure/2; 232 | let pA = getCoords(this.pos*pasAngle,centre); 233 | let pB = getCoords(this.pos*pasAngle,centre-DrawerFactory.dimensions.innerPlateauSize); 234 | 235 | canvas.fillStyle='#FFFFFF'; 236 | canvas.lineWidth=0.5; 237 | canvas.moveTo(centre - pA.x,centre - pA.y); 238 | canvas.lineTo(centre - pB.x,centre - pB.y); 239 | canvas.stroke(); 240 | let param = {centre:centre,bordure:bordure,length:140}; 241 | this._drawColorGroup(canvas,param); 242 | this._drawTitle(canvas,param); 243 | this._drawPrice(canvas,param); 244 | this._drawImage(canvas,param); 245 | this._drawHouses(canvas,param); 246 | this._drawPossede(canvas,param); 247 | } 248 | } 249 | 250 | class CircleCaseSpeciale extends Component{ 251 | constructor(axe, title){ 252 | super(); 253 | this.pos = convertAxePos(axe,0); 254 | this.title = title; 255 | } 256 | draw (canvas){ 257 | let centre = DrawerFactory.dimensions.plateauSize/2; 258 | let pA = getCoords(this.pos*pasAngle,centre); 259 | let pB = getCoords(this.pos*pasAngle,centre-DrawerFactory.dimensions.innerPlateauSize); 260 | let bgColor = DrawerFactory.getInfo("backgroundColor"); 261 | canvas.fillStyle=bgColor; 262 | canvas.lineWidth=0.5; 263 | canvas.moveTo(centre - pA.x,centre - pA.y); 264 | canvas.lineTo(centre - pB.x,centre - pB.y); 265 | canvas.stroke(); 266 | DrawerHelper.drawArcCircle(canvas,bgColor,centre,{x:centre,y:centre},this.pos*pasAngle,(this.pos+1)*pasAngle); 267 | let maxLength = 120; 268 | if(this.title!=null){ 269 | if(this.pos > 10 && this.pos < 30){ 270 | let p = getCoords((this.pos+0.5)*pasAngle,centre-15); 271 | DrawerHelper.writeText(this.title, p.x + centre,p.y + centre, ((this.pos+20)%40 + 0.6)*pasAngle, canvas,9,maxLength,'left'); 272 | }else{ 273 | let p = getCoords((this.pos+0.6)*pasAngle,centre -maxLength - 15); 274 | DrawerHelper.writeText(this.title, p.x + centre,p.y + centre, (this.pos + 0.2)*pasAngle, canvas,9,maxLength,'right'); 275 | } 276 | } 277 | } 278 | } 279 | 280 | /* Represente un dé physique */ 281 | class CircleDes extends Des { 282 | constructor(x, y, width) { 283 | super(x + 195, y + 100, width); 284 | } 285 | } 286 | 287 | class CircleDesRapide extends DesRapide{ 288 | constructor(x,y,width) { 289 | super(x + 190, y + 100, width); 290 | } 291 | } 292 | 293 | class CirclePlateau extends Component{ 294 | constructor(x,y,width,height,color){ 295 | super(); 296 | this.data = { 297 | x: x, 298 | y: y, 299 | width: width, 300 | height: height, 301 | color:color 302 | }; 303 | } 304 | 305 | draw(canvas) { 306 | DrawerHelper.drawCircle(canvas,this.data.color,this.data.width/2,{x:this.data.width/2,y:this.data.width/2}); 307 | } 308 | enableCaseDetect(callback){ 309 | const plateau = this; 310 | document.getElementById('canvas_rt').onmousedown = function(e){ 311 | const x = e.clientX - this.parentNode.offsetLeft - this.width/2; 312 | const y = (e.clientY - this.parentNode.offsetTop - this.height/2) * -1; 313 | let angle = (Math.atan(y/x) / (2*Math.PI)) *360; 314 | if(x < 0){ 315 | angle+=180; 316 | }else{ 317 | if(y< 0){ 318 | angle += 360; 319 | } 320 | } 321 | const fichePosition = (59 - Math.floor(angle / 9)) % 40; 322 | plateau.disableCaseDetect(); 323 | callback(GestionFiche.fiches[fichePosition]) 324 | } 325 | } 326 | disableCaseDetect(){ 327 | document.getElementById('canvas_rt').onmousedown = null; 328 | } 329 | } 330 | 331 | /* Dessiné en dernier sur le plateau */ 332 | class EndCirclePlateau extends Component{ 333 | constructor(){ 334 | super(); 335 | } 336 | 337 | draw(canvas){ 338 | let centre = DrawerFactory.dimensions.plateauSize/2; 339 | let innerPlateau = DrawerFactory.dimensions.innerPlateauSize; 340 | DrawerHelper.drawCircle(canvas,'#000000',centre-innerPlateau,{x:centre,y:centre}); 341 | DrawerHelper.drawCircle(canvas,'#FFFFFF',centre-innerPlateau - 2,{x:centre,y:centre}); 342 | DrawerHelper.drawArcCircle(canvas,'#FF0000',centre-innerPlateau -2,{x:centre,y:centre},-Math.PI,0); 343 | DrawerHelper.drawCircle(canvas,'#FFFFFF',centre-innerPlateau - 50,{x:centre,y:centre}); 344 | } 345 | } 346 | 347 | function initCircleInstance(){ 348 | let instance = { 349 | type:'circle', 350 | standardCase:CircleCase, 351 | specialCase:CircleCaseSpeciale, 352 | pionJoueur:CirclePionJoueur, 353 | des:CircleDes, 354 | desRapide:CircleDesRapide, 355 | plateau:CirclePlateau, 356 | endPlateau:EndCirclePlateau 357 | }; 358 | DrawerFactory.addInstance(instance); 359 | } 360 | 361 | initCircleInstance(); -------------------------------------------------------------------------------- /lib/circletype.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * circletype 2.3.2 3 | * A JavaScript library that lets you curve type on the web. 4 | * Copyright © 2014-2020 Peter Hrynkow 5 | * Licensed MIT 6 | * https://github.com/peterhry/CircleType#readme 7 | */ 8 | !function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.CircleType=n():t.CircleType=n()}(window,(function(){return function(t){var n={};function e(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,e),i.l=!0,i.exports}return e.m=t,e.c=n,e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{enumerable:!0,get:r})},e.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},e.t=function(t,n){if(1&n&&(t=e(t)),8&n)return t;if(4&n&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(e.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&n&&"string"!=typeof t)for(var i in t)e.d(r,i,function(n){return t[n]}.bind(null,i));return r},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},e.p="",e(e.s=28)}([function(t,n,e){var r=e(13)("wks"),i=e(12),o=e(1).Symbol,u="function"==typeof o;(t.exports=function(t){return r[t]||(r[t]=u&&o[t]||(u?o:i)("Symbol."+t))}).store=r},function(t,n){var e=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=e)},function(t,n){var e=t.exports={version:"2.6.11"};"number"==typeof __e&&(__e=e)},function(t,n,e){var r=e(4),i=e(11);t.exports=e(6)?function(t,n,e){return r.f(t,n,i(1,e))}:function(t,n,e){return t[n]=e,t}},function(t,n,e){var r=e(5),i=e(33),o=e(34),u=Object.defineProperty;n.f=e(6)?Object.defineProperty:function(t,n,e){if(r(t),n=o(n,!0),r(e),i)try{return u(t,n,e)}catch(t){}if("get"in e||"set"in e)throw TypeError("Accessors not supported!");return"value"in e&&(t[n]=e.value),t}},function(t,n,e){var r=e(10);t.exports=function(t){if(!r(t))throw TypeError(t+" is not an object!");return t}},function(t,n,e){t.exports=!e(18)((function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a}))},function(t,n){var e={}.hasOwnProperty;t.exports=function(t,n){return e.call(t,n)}},function(t,n){var e=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:e)(t)}},function(t,n){t.exports=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t}},function(t,n){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},function(t,n){var e=0,r=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++e+r).toString(36))}},function(t,n,e){var r=e(2),i=e(1),o=i["__core-js_shared__"]||(i["__core-js_shared__"]={});(t.exports=function(t,n){return o[t]||(o[t]=void 0!==n?n:{})})("versions",[]).push({version:r.version,mode:e(16)?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},function(t,n){t.exports={}},function(t,n,e){var r=e(13)("keys"),i=e(12);t.exports=function(t){return r[t]||(r[t]=i(t))}},function(t,n){t.exports=!1},function(t,n,e){var r=e(1),i=e(2),o=e(3),u=e(20),c=e(21),a=function(t,n,e){var f,s,l,p,h=t&a.F,v=t&a.G,d=t&a.S,y=t&a.P,m=t&a.B,g=v?r:d?r[n]||(r[n]={}):(r[n]||{}).prototype,_=v?i:i[n]||(i[n]={}),x=_.prototype||(_.prototype={});for(f in v&&(e=n),e)l=((s=!h&&g&&void 0!==g[f])?g:e)[f],p=m&&s?c(l,r):y&&"function"==typeof l?c(Function.call,l):l,g&&u(g,f,l,t&a.U),_[f]!=l&&o(_,f,p),y&&x[f]!=l&&(x[f]=l)};r.core=i,a.F=1,a.G=2,a.S=4,a.P=8,a.B=16,a.W=32,a.U=64,a.R=128,t.exports=a},function(t,n){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,n,e){var r=e(10),i=e(1).document,o=r(i)&&r(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,n,e){var r=e(1),i=e(3),o=e(7),u=e(12)("src"),c=e(35),a=(""+c).split("toString");e(2).inspectSource=function(t){return c.call(t)},(t.exports=function(t,n,e,c){var f="function"==typeof e;f&&(o(e,"name")||i(e,"name",n)),t[n]!==e&&(f&&(o(e,u)||i(e,u,t[n]?""+t[n]:a.join(String(n)))),t===r?t[n]=e:c?t[n]?t[n]=e:i(t,n,e):(delete t[n],i(t,n,e)))})(Function.prototype,"toString",(function(){return"function"==typeof this&&this[u]||c.call(this)}))},function(t,n,e){var r=e(36);t.exports=function(t,n,e){if(r(t),void 0===n)return t;switch(e){case 1:return function(e){return t.call(n,e)};case 2:return function(e,r){return t.call(n,e,r)};case 3:return function(e,r,i){return t.call(n,e,r,i)}}return function(){return t.apply(n,arguments)}}},function(t,n,e){var r=e(42),i=e(9);t.exports=function(t){return r(i(t))}},function(t,n){var e={}.toString;t.exports=function(t){return e.call(t).slice(8,-1)}},function(t,n,e){var r=e(8),i=Math.min;t.exports=function(t){return t>0?i(r(t),9007199254740991):0}},function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,n,e){var r=e(4).f,i=e(7),o=e(0)("toStringTag");t.exports=function(t,n,e){t&&!i(t=e?t:t.prototype,o)&&r(t,o,{configurable:!0,value:n})}},function(t,n,e){var r=e(9);t.exports=function(t){return Object(r(t))}},function(t,n,e){e(29);var r=e(54).default;t.exports=r},function(t,n,e){e(30),e(47),t.exports=e(2).Array.from},function(t,n,e){"use strict";var r=e(31)(!0);e(32)(String,"String",(function(t){this._t=String(t),this._i=0}),(function(){var t,n=this._t,e=this._i;return e>=n.length?{value:void 0,done:!0}:(t=r(n,e),this._i+=t.length,{value:t,done:!1})}))},function(t,n,e){var r=e(8),i=e(9);t.exports=function(t){return function(n,e){var o,u,c=String(i(n)),a=r(e),f=c.length;return a<0||a>=f?t?"":void 0:(o=c.charCodeAt(a))<55296||o>56319||a+1===f||(u=c.charCodeAt(a+1))<56320||u>57343?t?c.charAt(a):o:t?c.slice(a,a+2):u-56320+(o-55296<<10)+65536}}},function(t,n,e){"use strict";var r=e(16),i=e(17),o=e(20),u=e(3),c=e(14),a=e(37),f=e(26),s=e(46),l=e(0)("iterator"),p=!([].keys&&"next"in[].keys()),h=function(){return this};t.exports=function(t,n,e,v,d,y,m){a(e,n,v);var g,_,x,b=function(t){if(!p&&t in S)return S[t];switch(t){case"keys":case"values":return function(){return new e(this,t)}}return function(){return new e(this,t)}},w=n+" Iterator",O="values"==d,j=!1,S=t.prototype,M=S[l]||S["@@iterator"]||d&&S[d],T=M||b(d),P=d?O?b("entries"):T:void 0,A="Array"==n&&S.entries||M;if(A&&(x=s(A.call(new t)))!==Object.prototype&&x.next&&(f(x,w,!0),r||"function"==typeof x[l]||u(x,l,h)),O&&M&&"values"!==M.name&&(j=!0,T=function(){return M.call(this)}),r&&!m||!p&&!j&&S[l]||u(S,l,T),c[n]=T,c[w]=h,d)if(g={values:O?T:b("values"),keys:y?T:b("keys"),entries:P},m)for(_ in g)_ in S||o(S,_,g[_]);else i(i.P+i.F*(p||j),n,g);return g}},function(t,n,e){t.exports=!e(6)&&!e(18)((function(){return 7!=Object.defineProperty(e(19)("div"),"a",{get:function(){return 7}}).a}))},function(t,n,e){var r=e(10);t.exports=function(t,n){if(!r(t))return t;var e,i;if(n&&"function"==typeof(e=t.toString)&&!r(i=e.call(t)))return i;if("function"==typeof(e=t.valueOf)&&!r(i=e.call(t)))return i;if(!n&&"function"==typeof(e=t.toString)&&!r(i=e.call(t)))return i;throw TypeError("Can't convert object to primitive value")}},function(t,n,e){t.exports=e(13)("native-function-to-string",Function.toString)},function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,n,e){"use strict";var r=e(38),i=e(11),o=e(26),u={};e(3)(u,e(0)("iterator"),(function(){return this})),t.exports=function(t,n,e){t.prototype=r(u,{next:i(1,e)}),o(t,n+" Iterator")}},function(t,n,e){var r=e(5),i=e(39),o=e(25),u=e(15)("IE_PROTO"),c=function(){},a=function(){var t,n=e(19)("iframe"),r=o.length;for(n.style.display="none",e(45).appendChild(n),n.src="javascript:",(t=n.contentWindow.document).open(),t.write("