Traduciendo desde react...

master
Araozu 2020-10-03 10:14:47 -05:00
parent 210f2c06af
commit 2f69afb6b8
30 changed files with 2001 additions and 166 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
[*]
indent_style = space
indent_size = 4
[*.sass]
indent_size = 2
[*.json]
indent_size = 2
[*.yaml]
indent_size = 2

View File

@ -3,14 +3,15 @@
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"serve": "vue-cli-service serve --port 3000",
"build": "vue-cli-service build"
},
"dependencies": {
"core-js": "^3.6.5",
"vue": "^3.0.0-0",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0"
"vuex": "^4.0.0-0",
"vuex-persist": "^3.1.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
@ -19,6 +20,8 @@
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0-0",
"pug": "2.0.4",
"pug-plain-loader": "1.0.0",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"typescript": "~3.9.3"

View File

@ -3,13 +3,16 @@ dependencies:
vue: 3.0.0
vue-router: 4.0.0-beta.12_vue@3.0.0
vuex: 4.0.0-beta.4_vue@3.0.0
vuex-persist: 3.1.3_vuex@4.0.0-beta.4
devDependencies:
'@vue/cli-plugin-babel': 4.5.6_@vue+cli-service@4.5.6+vue@3.0.0
'@vue/cli-plugin-router': 4.5.6_@vue+cli-service@4.5.6
'@vue/cli-plugin-typescript': 4.5.6_aad4367e7c82e10ca22fc4a15cb1f747
'@vue/cli-plugin-vuex': 4.5.6_@vue+cli-service@4.5.6
'@vue/cli-service': 4.5.6_0521301699d431e160df5d1bd3c2a02e
'@vue/cli-service': 4.5.6_3c24ad0de21a253f90051e57049d7938
'@vue/compiler-sfc': 3.0.0_vue@3.0.0
pug: 2.0.4
pug-plain-loader: 1.0.0_pug@2.0.4
sass: 1.26.11
sass-loader: 8.0.2_sass@1.26.11
typescript: 3.9.7
@ -1056,6 +1059,16 @@ packages:
dev: true
resolution:
integrity: sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==
/@types/babel-types/7.0.9:
dev: true
resolution:
integrity: sha512-qZLoYeXSTgQuK1h7QQS16hqLGdmqtRmN8w/rl3Au/l5x/zkHx+a4VHrHyBsi1I1vtK2oBHxSzKIu0R5p6spdOA==
/@types/babylon/6.16.5:
dependencies:
'@types/babel-types': 7.0.9
dev: true
resolution:
integrity: sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==
/@types/body-parser/1.19.0:
dependencies:
'@types/connect': 3.4.33
@ -1344,7 +1357,7 @@ packages:
dependencies:
'@babel/core': 7.11.6
'@vue/babel-preset-app': 4.5.6_vue@3.0.0
'@vue/cli-service': 4.5.6_0521301699d431e160df5d1bd3c2a02e
'@vue/cli-service': 4.5.6_3c24ad0de21a253f90051e57049d7938
'@vue/cli-shared-utils': 4.5.6
babel-loader: 8.1.0_d2a654c8b7ff226093b38eb7b56a78a8
cache-loader: 4.1.0_webpack@4.44.2
@ -1358,7 +1371,7 @@ packages:
integrity: sha512-jkeXIpvxg2Og+6igsck6qBMFwFN5poqbgDL7JEQP94DPRMAGt+AOoEz6Ultwvykd9lRDD/xLmzZ2MTeXvrpq4A==
/@vue/cli-plugin-router/4.5.6_@vue+cli-service@4.5.6:
dependencies:
'@vue/cli-service': 4.5.6_0521301699d431e160df5d1bd3c2a02e
'@vue/cli-service': 4.5.6_3c24ad0de21a253f90051e57049d7938
'@vue/cli-shared-utils': 4.5.6
dev: true
peerDependencies:
@ -1368,7 +1381,7 @@ packages:
/@vue/cli-plugin-typescript/4.5.6_aad4367e7c82e10ca22fc4a15cb1f747:
dependencies:
'@types/webpack-env': 1.15.3
'@vue/cli-service': 4.5.6_0521301699d431e160df5d1bd3c2a02e
'@vue/cli-service': 4.5.6_3c24ad0de21a253f90051e57049d7938
'@vue/cli-shared-utils': 4.5.6
'@vue/compiler-sfc': 3.0.0_vue@3.0.0
cache-loader: 4.1.0_webpack@4.44.2
@ -1394,13 +1407,13 @@ packages:
integrity: sha512-zr/N1hX5gQQjR2BBFJdZPXatyKC9Scaw8vRDUhu6AE8phcQqf81DhRRVHICss9mMt7DTLKEHHjcYgFrotjEaew==
/@vue/cli-plugin-vuex/4.5.6_@vue+cli-service@4.5.6:
dependencies:
'@vue/cli-service': 4.5.6_0521301699d431e160df5d1bd3c2a02e
'@vue/cli-service': 4.5.6_3c24ad0de21a253f90051e57049d7938
dev: true
peerDependencies:
'@vue/cli-service': ^3.0.0 || ^4.0.0-0
resolution:
integrity: sha512-cWxj0jIhhupU+oFl0mc1St3ig9iF5F01XKwAhKEbvvuHR97zHxLd29My/vvcRwojZMy4aY320oJ+0ljoCIbueQ==
/@vue/cli-service/4.5.6_0521301699d431e160df5d1bd3c2a02e:
/@vue/cli-service/4.5.6_3c24ad0de21a253f90051e57049d7938:
dependencies:
'@intervolga/optimize-cssnano-plugin': 1.0.6_webpack@4.44.2
'@soda/friendly-errors-webpack-plugin': 1.7.1_webpack@4.44.2
@ -1447,6 +1460,7 @@ packages:
pnp-webpack-plugin: 1.6.4_typescript@3.9.7
portfinder: 1.0.28
postcss-loader: 3.0.0
pug-plain-loader: 1.0.0_pug@2.0.4
sass-loader: 8.0.2_sass@1.26.11
ssri: 7.1.0
terser-webpack-plugin: 2.3.8_webpack@4.44.2
@ -1751,12 +1765,32 @@ packages:
node: '>= 0.6'
resolution:
integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
/acorn-globals/3.1.0:
dependencies:
acorn: 4.0.13
dev: true
resolution:
integrity: sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=
/acorn-walk/7.2.0:
dev: true
engines:
node: '>=0.4.0'
resolution:
integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
/acorn/3.3.0:
dev: true
engines:
node: '>=0.4.0'
hasBin: true
resolution:
integrity: sha1-ReN/s56No/JbruP/U2niu18iAXo=
/acorn/4.0.13:
dev: true
engines:
node: '>=0.4.0'
hasBin: true
resolution:
integrity: sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=
/acorn/6.4.1:
dev: true
engines:
@ -1811,6 +1845,16 @@ packages:
dev: true
resolution:
integrity: sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==
/align-text/0.1.4:
dependencies:
kind-of: 3.2.2
longest: 1.0.1
repeat-string: 1.6.1
dev: true
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=
/alphanum-sort/1.0.2:
dev: true
resolution:
@ -1955,6 +1999,10 @@ packages:
node: '>=0.10.0'
resolution:
integrity: sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
/asap/2.0.6:
dev: true
resolution:
integrity: sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
/asn1.js/5.4.1:
dependencies:
bn.js: 4.11.9
@ -2073,6 +2121,27 @@ packages:
dev: true
resolution:
integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==
/babel-runtime/6.26.0:
dependencies:
core-js: 2.6.11
regenerator-runtime: 0.11.1
dev: true
resolution:
integrity: sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
/babel-types/6.26.0:
dependencies:
babel-runtime: 6.26.0
esutils: 2.0.3
lodash: 4.17.20
to-fast-properties: 1.0.3
dev: true
resolution:
integrity: sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=
/babylon/6.18.0:
dev: true
hasBin: true
resolution:
integrity: sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
/balanced-match/1.0.0:
dev: true
resolution:
@ -2453,6 +2522,12 @@ packages:
dev: true
resolution:
integrity: sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=
/camelcase/1.2.1:
dev: true
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=
/camelcase/5.3.1:
dev: true
engines:
@ -2488,6 +2563,15 @@ packages:
dev: true
resolution:
integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
/center-align/0.1.3:
dependencies:
align-text: 0.1.4
lazy-cache: 1.0.4
dev: true
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-qg0yYptu6XIgBBHL1EYckHvCt60=
/chalk/1.1.3:
dependencies:
ansi-styles: 2.2.1
@ -2529,6 +2613,12 @@ packages:
optional: true
resolution:
integrity: sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
/character-parser/2.2.0:
dependencies:
is-regex: 1.1.1
dev: true
resolution:
integrity: sha1-x84o821LzZdE5f/CxfzeHHMmH8A=
/check-types/8.0.3:
dev: true
resolution:
@ -2655,6 +2745,14 @@ packages:
node: '>=8'
resolution:
integrity: sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==
/cliui/2.1.0:
dependencies:
center-align: 0.1.3
right-align: 0.1.3
wordwrap: 0.0.2
dev: true
resolution:
integrity: sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=
/cliui/5.0.0:
dependencies:
string-width: 3.1.0
@ -2837,6 +2935,15 @@ packages:
node: '>= 0.10.0'
resolution:
integrity: sha512-Nhl1wzCslqXYTJVDyJCu3ODohy9OfBMB5uD2BiBTzd7w+QY0lBzafkR8y8755yMYHAaMD4NuzbAw03/xzfw+eQ==
/constantinople/3.1.2:
dependencies:
'@types/babel-types': 7.0.9
'@types/babylon': 6.16.5
babel-types: 6.26.0
babylon: 6.18.0
dev: true
resolution:
integrity: sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==
/constants-browserify/1.0.0:
dev: true
resolution:
@ -2917,6 +3024,12 @@ packages:
dev: true
resolution:
integrity: sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==
/core-js/2.6.11:
deprecated: 'core-js@<3 is no longer maintained and not recommended for usage due to the number of issues. Please, upgrade your dependencies to the actual version of core-js@3.'
dev: true
requiresBuild: true
resolution:
integrity: sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
/core-js/3.6.5:
requiresBuild: true
resolution:
@ -3282,10 +3395,8 @@ packages:
resolution:
integrity: sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==
/deepmerge/4.2.2:
dev: true
engines:
node: '>=0.10.0'
optional: true
resolution:
integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
/default-gateway/4.2.0:
@ -3424,6 +3535,10 @@ packages:
dev: true
resolution:
integrity: sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=
/doctypes/1.1.0:
dev: true
resolution:
integrity: sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=
/dom-converter/0.2.0:
dependencies:
utila: 0.4.0
@ -4029,6 +4144,10 @@ packages:
node: '>=8'
resolution:
integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
/flatted/3.1.0:
dev: false
resolution:
integrity: sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==
/flush-write-stream/1.1.1:
dependencies:
inherits: 2.0.4
@ -4945,6 +5064,13 @@ packages:
hasBin: true
resolution:
integrity: sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==
/is-expression/3.0.0:
dependencies:
acorn: 4.0.13
object-assign: 4.1.1
dev: true
resolution:
integrity: sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=
/is-extendable/0.1.1:
dev: true
engines:
@ -5055,6 +5181,10 @@ packages:
node: '>=0.10.0'
resolution:
integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
/is-promise/2.2.2:
dev: true
resolution:
integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
/is-regex/1.1.1:
dependencies:
has-symbols: 1.0.1
@ -5172,6 +5302,10 @@ packages:
node: '>=1.0.0'
resolution:
integrity: sha1-NiITz4YPRo8BJfxslqvBdCUx+Ug=
/js-stringify/1.0.2:
dev: true
resolution:
integrity: sha1-Fzb939lyTyijaCrcYjCufk6Weds=
/js-tokens/3.0.2:
dev: true
resolution:
@ -5275,6 +5409,13 @@ packages:
'0': node >=0.6.0
resolution:
integrity: sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
/jstransformer/1.0.0:
dependencies:
is-promise: 2.2.2
promise: 7.3.1
dev: true
resolution:
integrity: sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=
/killable/1.0.1:
dev: true
resolution:
@ -5320,6 +5461,12 @@ packages:
dev: true
resolution:
integrity: sha512-On+V7K2uZK6wK7x691ycSUbLD/FyKKelArkbaAMSSJU8JmqmhwN2+mnJDNINuJWSrh2L0kDk+ZQtbC/gOWUwLw==
/lazy-cache/1.0.4:
dev: true
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-odePw6UEdMuAhF07O24dpJpEbo4=
/leven/3.1.0:
dev: true
engines:
@ -5436,6 +5583,12 @@ packages:
node: '>= 0.6.0'
resolution:
integrity: sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==
/longest/1.0.1:
dev: true
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=
/loose-envify/1.4.0:
dependencies:
js-tokens: 4.0.0
@ -6855,6 +7008,12 @@ packages:
dev: true
resolution:
integrity: sha1-mEcocL8igTL8vdhoEputEsPAKeM=
/promise/7.3.1:
dependencies:
asap: 2.0.6
dev: true
resolution:
integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
/proxy-addr/2.0.6:
dependencies:
forwarded: 0.1.2
@ -6887,6 +7046,108 @@ packages:
dev: true
resolution:
integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==
/pug-attrs/2.0.4:
dependencies:
constantinople: 3.1.2
js-stringify: 1.0.2
pug-runtime: 2.0.5
dev: true
resolution:
integrity: sha512-TaZ4Z2TWUPDJcV3wjU3RtUXMrd3kM4Wzjbe3EWnSsZPsJ3LDI0F3yCnf2/W7PPFF+edUFQ0HgDL1IoxSz5K8EQ==
/pug-code-gen/2.0.2:
dependencies:
constantinople: 3.1.2
doctypes: 1.1.0
js-stringify: 1.0.2
pug-attrs: 2.0.4
pug-error: 1.3.3
pug-runtime: 2.0.5
void-elements: 2.0.1
with: 5.1.1
dev: true
resolution:
integrity: sha512-kROFWv/AHx/9CRgoGJeRSm+4mLWchbgpRzTEn8XCiwwOy6Vh0gAClS8Vh5TEJ9DBjaP8wCjS3J6HKsEsYdvaCw==
/pug-error/1.3.3:
dev: true
resolution:
integrity: sha512-qE3YhESP2mRAWMFJgKdtT5D7ckThRScXRwkfo+Erqga7dyJdY3ZquspprMCj/9sJ2ijm5hXFWQE/A3l4poMWiQ==
/pug-filters/3.1.1:
dependencies:
clean-css: 4.2.3
constantinople: 3.1.2
jstransformer: 1.0.0
pug-error: 1.3.3
pug-walk: 1.1.8
resolve: 1.17.0
uglify-js: 2.8.29
dev: true
resolution:
integrity: sha512-lFfjNyGEyVWC4BwX0WyvkoWLapI5xHSM3xZJFUhx4JM4XyyRdO8Aucc6pCygnqV2uSgJFaJWW3Ft1wCWSoQkQg==
/pug-lexer/4.1.0:
dependencies:
character-parser: 2.2.0
is-expression: 3.0.0
pug-error: 1.3.3
dev: true
resolution:
integrity: sha512-i55yzEBtjm0mlplW4LoANq7k3S8gDdfC6+LThGEvsK4FuobcKfDAwt6V4jKPH9RtiE3a2Akfg5UpafZ1OksaPA==
/pug-linker/3.0.6:
dependencies:
pug-error: 1.3.3
pug-walk: 1.1.8
dev: true
resolution:
integrity: sha512-bagfuHttfQOpANGy1Y6NJ+0mNb7dD2MswFG2ZKj22s8g0wVsojpRlqveEQHmgXXcfROB2RT6oqbPYr9EN2ZWzg==
/pug-load/2.0.12:
dependencies:
object-assign: 4.1.1
pug-walk: 1.1.8
dev: true
resolution:
integrity: sha512-UqpgGpyyXRYgJs/X60sE6SIf8UBsmcHYKNaOccyVLEuT6OPBIMo6xMPhoJnqtB3Q3BbO4Z3Bjz5qDsUWh4rXsg==
/pug-parser/5.0.1:
dependencies:
pug-error: 1.3.3
token-stream: 0.0.1
dev: true
resolution:
integrity: sha512-nGHqK+w07p5/PsPIyzkTQfzlYfuqoiGjaoqHv1LjOv2ZLXmGX1O+4Vcvps+P4LhxZ3drYSljjq4b+Naid126wA==
/pug-plain-loader/1.0.0_pug@2.0.4:
dependencies:
loader-utils: 1.4.0
pug: 2.0.4
dev: true
peerDependencies:
pug: ^2.0.0
resolution:
integrity: sha512-mDfq/qvJJ0xdug38mZ1ObW0BQTx9kAHnKqotXC+C00XQkKmsWaMe90JUg/kN4lS6MU7tpVsMZ+rmcnBSPfDtHA==
/pug-runtime/2.0.5:
dev: true
resolution:
integrity: sha512-P+rXKn9un4fQY77wtpcuFyvFaBww7/91f3jHa154qU26qFAnOe6SW1CbIDcxiG5lLK9HazYrMCCuDvNgDQNptw==
/pug-strip-comments/1.0.4:
dependencies:
pug-error: 1.3.3
dev: true
resolution:
integrity: sha512-i5j/9CS4yFhSxHp5iKPHwigaig/VV9g+FgReLJWWHEHbvKsbqL0oP/K5ubuLco6Wu3Kan5p7u7qk8A4oLLh6vw==
/pug-walk/1.1.8:
dev: true
resolution:
integrity: sha512-GMu3M5nUL3fju4/egXwZO0XLi6fW/K3T3VTgFQ14GxNi8btlxgT5qZL//JwZFm/2Fa64J/PNS8AZeys3wiMkVA==
/pug/2.0.4:
dependencies:
pug-code-gen: 2.0.2
pug-filters: 3.1.1
pug-lexer: 4.1.0
pug-linker: 3.0.6
pug-load: 2.0.12
pug-parser: 5.0.1
pug-runtime: 2.0.5
pug-strip-comments: 1.0.4
dev: true
resolution:
integrity: sha512-XhoaDlvi6NIzL49nu094R2NA6P37ijtgMDuWE+ofekDChvfKnzFal60bhSdiy8y2PBO6fmz3oMEIcfpBVRUdvw==
/pump/2.0.1:
dependencies:
end-of-stream: 1.4.4
@ -7060,6 +7321,10 @@ packages:
dev: true
resolution:
integrity: sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==
/regenerator-runtime/0.11.1:
dev: true
resolution:
integrity: sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
/regenerator-runtime/0.13.7:
dev: true
resolution:
@ -7247,6 +7512,14 @@ packages:
dev: true
resolution:
integrity: sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
/right-align/0.1.3:
dependencies:
align-text: 0.1.4
dev: true
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-YTObci/mo1FWiSENJOFMlhSGE+8=
/rimraf/2.7.1:
dependencies:
glob: 7.1.6
@ -8074,6 +8347,12 @@ packages:
dev: true
resolution:
integrity: sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
/to-fast-properties/1.0.3:
dev: true
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=
/to-fast-properties/2.0.0:
engines:
node: '>=4'
@ -8121,6 +8400,10 @@ packages:
node: '>=0.6'
resolution:
integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
/token-stream/0.0.1:
dev: true
resolution:
integrity: sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=
/toposort/1.0.7:
dev: true
resolution:
@ -8243,6 +8526,18 @@ packages:
hasBin: true
resolution:
integrity: sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
/uglify-js/2.8.29:
dependencies:
source-map: 0.5.7
yargs: 3.10.0
dev: true
engines:
node: '>=0.8.0'
hasBin: true
optionalDependencies:
uglify-to-browserify: 1.0.2
resolution:
integrity: sha1-KcVzMUgFe7Th913zW3qcty5qWd0=
/uglify-js/3.4.10:
dependencies:
commander: 2.19.0
@ -8253,6 +8548,11 @@ packages:
hasBin: true
resolution:
integrity: sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==
/uglify-to-browserify/1.0.2:
dev: true
optional: true
resolution:
integrity: sha1-bgkk1r2mta/jSeOabWMoUKD4grc=
/unicode-canonical-property-names-ecmascript/1.0.4:
dev: true
engines:
@ -8480,6 +8780,12 @@ packages:
dev: true
resolution:
integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
/void-elements/2.0.1:
dev: true
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
/vue-hot-reload-api/2.3.4:
dev: true
resolution:
@ -8542,6 +8848,16 @@ packages:
'@vue/shared': 3.0.0
resolution:
integrity: sha512-ZMrAARZ32sGIaYKr7Fk2GZEBh/VhulSrGxcGBiAvbN4fhjl3tuJyNFbbbLFqGjndbLoBW66I2ECq8ICdvkKdJw==
/vuex-persist/3.1.3_vuex@4.0.0-beta.4:
dependencies:
deepmerge: 4.2.2
flatted: 3.1.0
vuex: 4.0.0-beta.4_vue@3.0.0
dev: false
peerDependencies:
vuex: '>=2.5'
resolution:
integrity: sha512-QWOpP4SxmJDC5Y1+0+Yl/F4n7z27syd1St/oP+IYCGe0X0GFio0Zan6kngZFufdIhJm+5dFGDo3VG5kdkCGeRQ==
/vuex/4.0.0-beta.4_vue@3.0.0:
dependencies:
vue: 3.0.0
@ -8780,6 +9096,25 @@ packages:
hasBin: true
resolution:
integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
/window-size/0.1.0:
dev: true
engines:
node: '>= 0.8.0'
resolution:
integrity: sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=
/with/5.1.1:
dependencies:
acorn: 3.3.0
acorn-globals: 3.1.0
dev: true
resolution:
integrity: sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=
/wordwrap/0.0.2:
dev: true
engines:
node: '>=0.4.0'
resolution:
integrity: sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=
/worker-farm/1.7.0:
dependencies:
errno: 0.1.7
@ -8900,6 +9235,15 @@ packages:
node: '>=8'
resolution:
integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
/yargs/3.10.0:
dependencies:
camelcase: 1.2.1
cliui: 2.1.0
decamelize: 1.2.0
window-size: 0.1.0
dev: true
resolution:
integrity: sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=
/yorkie/2.0.0:
dependencies:
execa: 0.8.0
@ -8920,9 +9264,12 @@ specifiers:
'@vue/cli-service': ~4.5.0
'@vue/compiler-sfc': ^3.0.0-0
core-js: ^3.6.5
pug: 2.0.4
pug-plain-loader: 1.0.0
sass: ^1.26.5
sass-loader: ^8.0.2
typescript: ~3.9.3
vue: ^3.0.0-0
vue-router: ^4.0.0-0
vuex: ^4.0.0-0
vuex-persist: ^3.1.3

BIN
public/img/Dragon_azul.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/img/Dragon_rojo.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,17 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<html lang="es">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
<link href="https://fonts.googleapis.com/css2?family=PT+Serif&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Secular+One&display=swap" rel="stylesheet">
</head>
<body class="tema-automatico">
<noscript>
<strong>Necesitas activar JavaScript para continuar.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -1,30 +1,19 @@
<template>
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
<router-view/>
</template>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
<script lang="ts">
import {defineComponent, computed, watch} from "vue";
import { useStore } from "vuex";
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
export default defineComponent({
setup() {
const store = useStore();
const modoColor = computed<string>(() => store.state.modoColor);
watch(modoColor, (n) => {
console.log(`Modo color cambiado: ${n}`);
});
}
}
}
</style>
});
</script>

View File

@ -1,62 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'HelloWorld',
props: {
msg: String,
},
});
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

186
src/components/carta.vue Normal file
View File

@ -0,0 +1,186 @@
<template lang="pug">
div(style="display: inline-block")
div.c-carta(v-if="valor === 0")
div.c-carta-oculta(v-html="'&nbsp;'")
div.c-carta(v-else-if="tipo === 2 || tipo === 3 || tipo === 4 || tipo === 5" :class="'carta-' + tipoCarta")
img.img-dragon(:src="'/img/Dragon_' + colorDragon + '.webp'" :alt="'Dragon ' + colorDragon")
div.c-carta(v-else :class="'carta-' + tipoCarta" v-html="valorC")
//
</template>
<script lang="ts">
import {defineComponent, computed} from "vue";
import {useDimensions} from "@/components/useDimensions";
import { useStore } from "vuex";
export default defineComponent({
name: "carta",
props: {
valor: {
type: Number,
default: 0
},
movimiento: {
type: String,
default: "none"
},
fnDescartar: {
type: Function,
required: false
},
escala: {
type: Number,
default: 1
}
},
setup(props) {
const {pH} = useDimensions();
const store = useStore();
const pxesc = computed(() => pH.value + "px");
const esOscuro = computed(() => {
if (store.state.modoColor === "oscuro") {
return true;
} else if (store.state.modoColor === "auto" && window.matchMedia("(prefers-color-scheme: dark)").matches) {
return true;
}
return false;
});
const tipo = computed<number>( () => (props.valor << 23) >>> 28);
const tipoCarta = computed<string>(() => {
switch (tipo.value) {
case 0:
return "cNegro";
case 1:
return "cRojo";
case 2:
return "dNegro"
case 3:
return "dRojo";
case 4:
return "dVerde";
case 5:
return "dAzul";
case 6:
case 7:
case 8:
return "cReyes";
default:
return "";
}
});
const valorC = computed(() => {
switch (tipo.value) {
case 0:
case 1: {
const valor = (props.valor << 27) >>> 28;
return valor === 1? "A": valor;
}
case 6:
return "J";
case 7:
return "Q";
case 8:
return "K";
default:
return "&nbsp;"
}
});
const colorDragon = computed<string>(() => {
if (esOscuro.value) return "blanco";
switch (tipo.value) {
case 2:
return "negro";
case 3:
return "rojo";
case 4:
return "verde";
default:
return "azul";
}
});
return {
tipo,
tipoCarta,
valorC,
colorDragon,
pxesc
}
}
});
</script>
<style lang="sass" vars="{pxesc, escala}">
.c-carta
position: relative
font:
size: calc(var(--pxesc) * 4 * var(--escala))
weight: normal
family: "Secular One", "Pt Serif", serif
display: table-cell
border: solid calc(var(--pxesc) * 0.225 * var(--escala)) var(--color-borde)
border-radius: 0.1rem
width: calc(var(--pxesc) * 5 * var(--escala))
height: calc(var(--pxesc) * 8.5 * var(--escala))
min-width: calc(var(--pxesc) * 5 * var(--escala))
text-align: center
vertical-align: middle
cursor: pointer
transition: transform 50ms, opacity 50ms
user-select: none
.c-carta-oculta
display: inline-block
width: 60%
height: 80%
border: solid calc(var(--pxesc) * 0.4 * var(--escala)) var(--color-texto)
border-radius: 0.1rem
opacity: 0.75
.img-dragon
width: 90%
height: auto
bottom: 0
vertical-align: middle
display: inline-block
.carta-cNegro
background-color: var(--color-fondo)
color: var(--color-texto)
.carta-cRojo
background-color: #b71c1c
color: var(--color-texto)
.carta-cReyes
background-color: #2E7D32
color: var(--color-texto)
.carta-dNegro
background-color: var(--color-fondo)
color: var(--color-texto)
.carta-dRojo
background-color: #b71c1c
color: var(--color-texto)
.carta-dVerde
background-color: #2E7D32
color: var(--color-texto)
.carta-dAzul
background-color: #1565C0
color: var(--color-texto)
.carta-
opacity: 0
//
</style>

View File

@ -0,0 +1,19 @@
<template lang="pug">
carta(v-for="(c, i) in cartas" :valor="c" :key="i")
//
</template>
<script lang="ts">
import {defineComponent} from "vue";
import carta from "@/components/carta.vue";
export default defineComponent({
name: "grupo-cartas",
components: {carta},
props: {
cartas: Array
}
});
</script>

View File

@ -0,0 +1,23 @@
import {ref, computed, onMounted, onUnmounted} from "vue";
export const useDimensions = () => {
const pH = ref(Math.floor(window.innerHeight / 100));
const pW = ref(Math.floor(window.innerWidth / 100));
const phx = computed(() => pH.value + "px");
const pwx = computed(() => pW.value + "px");
const listener = () => {
pH.value = Math.floor(window.innerHeight / 100);
pW.value = Math.floor(window.innerWidth / 100);
};
onMounted(() => {
window.addEventListener("resize", listener);
});
onUnmounted(() => {
window.removeEventListener("resize", listener);
});
return {pH, pW, phx, pwx};
};

View File

@ -1,6 +1,11 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import "./styles/global.sass";
createApp(App).use(store).use(router).mount('#app')
// @ts-ignore
createApp(App)
.use(store)
.use(router)
.mount('#app');

View File

@ -1,25 +1,34 @@
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '../views/Home.vue'
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import Inicio from "../views/Inicio/Inicio.vue";
import Sala from "@/views/Sala/Sala.vue";
import Juego from "@/views/Juego/Juego.vue";
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
{
path: '/',
name: 'Home',
component: Inicio
},
{
path: "/sala/:id",
name: "Sala",
component: Sala
},
{
path: "/juego/:id",
name: "Juego",
component: Juego
},
{
path: "/ayuda",
name: "Ayuda",
component: () => import(/* webpackChunkName: "ayuda" */ "../views/Ayuda/Ayuda.vue")
}
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
history: createWebHistory(process.env.BASE_URL),
routes
});
export default router
export default router;

View File

@ -1,12 +1,34 @@
import { createStore } from 'vuex'
import { createStore } from 'vuex';
import VuexPersistence from "vuex-persist";
export default createStore({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
const vuexLocal = new VuexPersistence({
storage: window.localStorage
});
export interface RiMaJonState {
idUsuario: string | undefined,
nombreUsuario: string | undefined,
modoColor: string
}
export default createStore<RiMaJonState>({
state: {
idUsuario: undefined,
nombreUsuario: undefined,
modoColor: "auto"
},
mutations: {
setIdUsuario(state, id) {
state.idUsuario = id;
},
setNombreUsuario(state, nombre) {
state.nombreUsuario = nombre;
},
setModoColor(state, modo) {
state.modoColor = modo;
}
},
actions: {},
modules: {},
plugins: [vuexLocal.plugin]
});

29
src/styles/global.sass Normal file
View File

@ -0,0 +1,29 @@
body
font-family: "Pt Serif", serif
background-color: var(--color-fondo)
color: var(--color-texto)
.tema-claro
--color-fondo: #ffffff
--color-texto: #151515
--color-borde: gray
.tema-oscuro
--color-fondo: #151515
--color-texto: #dedede
--color-borde: #c1c1c1
.tema-automatico
--color-fondo: #ffffff
--color-texto: #151515
--color-borde: gray
@media (prefers-color-scheme: dark)
.tema-automatico
--color-fondo: #151515
--color-texto: #dedede
--color-borde: #c1c1c1

3
src/variables.ts Normal file
View File

@ -0,0 +1,3 @@
export const servidor = "0.0.0.0:8080"; // "rimajonb.araozu.dev"; // "0.0.0.0:8080"; //
export const servidorF = `http://${servidor}`;
export const wsServidor = `ws://${servidor}`;

View File

@ -1,5 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

402
src/views/Ayuda/Ayuda.vue Normal file
View File

@ -0,0 +1,402 @@
<template lang="pug">
div
h1 Sobre el juego
p Ri Ma Jon es un juego inspirado en Mahjong, pero ejecutado con cartas.
h2 Cartas
p Existen 108 cartas en el juego:
carta(v-for="(c, i) in cartas" :valor="c" :key="i")
p 4 cartas de los números 1 al 10 de color negro y rojo, 4 cartas de dragones de 4 colores, 4 J, Q y K.
h2 Mano
p Cada mano se compone de 10 + 1 cartas aleatorias.
grupo-cartas(:cartas="cartasR")
h2 Flujo del juego
p.
Hay 4 jugadores, cada uno con una mano de 10 cartas. En cada turno el jugador extrae una carta
y la une a su mano. Luego, debe descartar 1 carta.
p El objetivo del juego es formar una mano con 3 grupos y 1 par de cartas.
h2 Par de cartas
p Un par son 2 cartas del mismo color y valor.
grupo-cartas(:cartas="parCartasR")
h2 Grupo de cartas
p Un grupo de cartas puede ser cualquiera de los siguientes:
div(style="padding-left: 2rem")
h3 Secuencia
p 3 cartas del mismo color y con números consecutivos
grupo-cartas(:cartas="seqCartasR")
p J, Q, K y los dragones no pueden formar sequencias.
p Los números deben ser consecutivos: 1 2 3 es válido, pero 9 10 1 no lo es.
h3 Triple
p 3 cartas del mismo color y valor.
grupo-cartas(:cartas="triCartasR")
h3 Cuádruple
p 4 cartas del mismo color y valor.
grupo-cartas(:cartas="cuaCartasR")
p Cuando se forma un cuádruple se agrega una carta a la mano.
h2 Mano lista
p Una mano está lista cuando solo le falta 1 carta para ganar
grupo-cartas(:cartas="[10, 10, 11, 34, 36, 38, 38, 38, 128, 128]")
p.
Por ejemplo, la mano anterior está lista, porque solo le falta un dragon verde
para completar sus 3 grupos y 1 par.
p.
En esa mano, el primer grupo es un triple negro, el segundo grupo una secuencia de A-2-3 rojo, el
par son dos 3 rojos, y el último grupo sería un triple de dragon verde.
h2 Robar cartas
p Puedes robar cartas que tus oponentes descartan para formar tu mano en varias situaciones:
div.pad
h3 Robar para formar una secuencia
p.
Si tu oponente a tu izquierda descarta una carta que necesitas para formar una secuencia,
puedes robarla y formar tu secuencia.
p Por ejemplo, a las siguientes dos cartas les falta un 3 o 6 rojo para formar una secuencia.
grupo-cartas(:cartas="[40, 42]")
br
p Al siguiente par le falta un 6 negro para formar una secuencia.
grupo-cartas(:cartas="[10, 14]")
h3 Robar para formar un triple
p.
Si algún oponente descarta una carta que necesitas para formar un triple,
puedes robarla.
p Por ejemplo, al siguiente par le falta una J verde para formar un triple
grupo-cartas(:cartas="[192, 192]")
h3 Robar para formar un cuádruple
p.
Si algún oponente descarta una carta que necesitas para formar un cuádruple,
puedes robarla.
p Por ejemplo. al siguiente triple le falta un dragon azul para formar un cuádruple.
grupo-cartas(:cartas="[160, 160, 160]")
h3 Robar para ganar
p.
Si tu mano está lista, y algún oponente descarta la carta que necesitas para ganar,
puedes robarla. No importa si hacerlo forma una secuencia, triple, cuádruple o par.
h3 Prioridad de los robos
p.
Existe la posibilidad de que varios jugadores puedan robar una carta a la vez.
Si esto sucede la prioridad es la siguiente:
ol
li Robar para ganar
li Robar para cuádruple
li Robar para triple
li Robar para secuencia
h3 Consecuencias del robo
p Cuando robas una carta suceden 2 cosas:
ul
li Todos pueden ver la secuencia/triple/cuádruple que formaste
li No puedes cambiar esa secuencia/triple/cuádruple
p Adicionalmente, al robar una carta tu mano se considera "abierta"
h2 Mano abierta y cerrada
p Se dice que tu mano está abierta cuando tus oponentes pueden ver uno o más grupos de tu mano.
p Robar una carta para ganar no cuenta como abrir tu mano.
h2 Restricciones para ganar
ol
li No puedes ganar robando una carta que descartaste. No importa cuando.
h2 Indicadores de bonus
div
p Todas las partidas tienen 10 cartas reservadas que todos pueden ver. Estos son los indicadores de bonus.
grupo-cartas(:cartas="[0, 0, 0, 0, 0]")
br
grupo-cartas(:cartas="[0, 0, 0, 0, 0]")
p Al inicio de la partida se revela el primer indicador:
grupo-cartas(:cartas="[14, 0, 0, 0, 0]")
br
grupo-cartas(:cartas="[0, 0, 0, 0, 0]")
p Los siguientes 4 indicadores se revelan despues de 32/16/8/4 turnos.
p Los últimos 5 indicadores se revelan cada vez que alguien declara un cuádruple, uno a la vez.
div.pad
h3 Funcionamiento
p Cada indicador apunta a un tipo de carta (llamado bonus), haciendo que esa carta valga 1 punto extra.
p Los bonus son:
ol
li Del mismo tipo que el indicador
li La carta que sigue al indicador
p Por ejemplo, si los indicadores son los siguientes:
grupo-cartas(:cartas="[48, 0, 0, 0, 0]")
br
grupo-cartas(:cartas="[0, 0, 0, 0, 0]")
p Entonces el bonus es el 9 rojo. Cada uno de ellos vale 1 punto.
grupo-cartas(:cartas="[50]")
br
br
p Si los indicadores son los siguientes:
grupo-cartas(:cartas="[2, 38, 52, 0, 0]")
br
grupo-cartas(:cartas="[96, 192, 0, 0, 0]")
p Los bonus son el 2 negro, 4 rojo, A rojo, dragon verde y Q
grupo-cartas(:cartas="[4, -1, 40, -1, 34, -1, 128, -1, 224]")
h3 Secuencia de las cartas
p La secuencia de las cartas es la siguiente:
grupo-cartas(:cartas="[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]")
p Cuando se llega a la última carta de un tipo, se regresa al inicio.
p Por ejemplo, si el indicador es el 10 negro, entonces el bonus es el A negro.
br
grupo-cartas(:cartas="[34, 36, 38, 40, 42, 44, 46, 48, 50, 52]")
p Si el indicador es el 10 rojo, entonces el bonus es el A rojo.
br
grupo-cartas(:cartas="[64, 96, 128, 160]")
p Si el indicador es el dragon azul, entonces el bonus es el dragon negro.
br
grupo-cartas(:cartas="[192, 224, 256]")
p Si el indicador es K, entonces el bonus es J.
br
h3 Indicadores y bonus repetidos
p Si algún bonus se repite, cada uno de ellos brinda 1 punto.
p Si algún indicador se repite, cada uno de los bonus a los que apunta brinda 1 punto.
p Por ejemplo, si los indicadores son los siguientes:
grupo-cartas(:cartas="[12, 12, 0, 0, 0]")
br
grupo-cartas(:cartas="[0, 0, 0, 0, 0]")
p.
Entonces cada 7 negro vale 2 puntos extra. Si juntas dos 7 negros tienes 4 puntos,
con tres 7 negros tienes 6 puntos, etc.
h2 Indicadores de dragones
div
p.
A cada partida se le asigna un color, y a cada jugador se le asigna otro. Esos colores indican
qué triple/cuáduple de dragones brindan 1 punto adicional.
p.
Por ejemplo, si la partida es de color rojo, y tu eres de color verde, entonces
cualquier triple/cuádruple del dragon rojo o verde da 3 puntos adicionales.
grupo-cartas(:cartas="[96, 96, 96, -1, 128, 128, 128]")
h2 Puntaje
p Cada mano completa vale una cantidad de puntos según las combinaciones de cartas que posee.
div.tabla-puntos
h3 1 punto - Bonus
div.yaku
p Cualquier bonus en una mano ganadora. 100% acumulable.
h3 3 puntos
div.yaku
p 2 secuencias iguales del mismo color.
grupo-cartas(:cartas="[2, 2, 4, 4, 6, 6, 44, 44, 44, 128, 128]")
div.yaku
p 1 triple o cuádruple de J, K o Q (acumulable).
grupo-cartas(:cartas="[5, 5, 5, 40, 42, 44, 128, 128, 256, 256, 256]")
div.yaku
p 3 secuencias
grupo-cartas(:cartas="[4, 6, 8, 12, 14, 16, 36, 38, 40, 96, 96]")
div.yaku
p 3 triples
grupo-cartas(:cartas="[6, 6, 6, 48, 48, 48, 160, 160, 160, 192, 192]")
div.yaku
p Solo números del 2 al 9
grupo-cartas(:cartas="[4, 4, 4, 4, 6, 8, 12, 12, 12, 40, 40]")
div.yaku
p Cada par o grupo contiene al menos un 1, 10, J, Q, K o dragón
grupo-cartas(:cartas="[2, 4, 6, 20, 20, 20, 48, 50, 52, 192, 192]")
div.yaku
p Solo cartas de color rojo
grupo-cartas(:cartas="[42, 42, 46, 48, 50, 52, 52, 52, 96, 96, 96,]")
div.yaku
p Solo cartas de color negro
grupo-cartas(:cartas="[4, 6, 8, 8, 8, 12, 14, 16, 64, 64, 64]")
div.yaku
p 1 triple/cuádruple del dragon del color de la partida o del jugador (acumulable).
grupo-cartas(:cartas="[6, 6, 6, 48, 48, 48, 160, 160, 160, 192, 192]")
h3 5 puntos
div.yaku
p 1,2,3,4,5,6,7,8,9 del mismo color
grupo-cartas(:cartas="[2, 4, 6, 8, 10, 12, 14, 16, 18, 128, 128]")
div.yaku
p 2,3,4,5,6,7,8,9,10 del mismo color
grupo-cartas(:cartas="[4, 6, 8, 10, 12, 14, 16, 18, 20, 41, 41]")
div.yaku
p 3 cuádruples
grupo-cartas(:cartas="[44, 44, -1, 128, 128, 128, 128, -1, 20, 20, 20, 20, -1, 40, 40, 40, 40]")
div.yaku
p 1 secuencia del mismo símbolo (no disponible)
h3 7 puntos
div.yaku
p 1,1,2,3,4,5,6,7,8,9,10 del mismo color
grupo-cartas(:cartas="[2, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]")
p No se acumula con 1,2,3,4,5,6,7,8,9.
div.yaku
p 1,2,3,4,5,6,7,8,9,10,10 del mismo color
grupo-cartas(:cartas="[34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 52]")
p No se acumula con 1,2,3,4,5,6,7,8,9.
div.yaku
p Solo 1 y 10
grupo-cartas(:cartas="[2, 2, 3, 20, 20, 21, 34, 35, 52, 52, 53]")
div.yaku
p 3 triples de J, Q y K
grupo-cartas(:cartas="[192, 192, 192, 224, 224, 224, 256, 256, 256, 16, 16]")
div.yaku
p 1 par del mismo número, mismo color y mismo símbolo (no disponible)
h3 10 puntos
div.yaku
p Solo dragones
grupo-cartas(:cartas="[64, 64, 64, 96, 96, 96, 128, 128, 128, 160, 160]")
div.yaku
p Solo cartas de color verde
grupo-cartas(:cartas="[128, 128, 128, 192, 192, 192, 224, 224, 224, 256, 256]")
h3 15 puntos
div.yaku
p Huerfanos: Exactamente esta mano
grupo-cartas(:cartas="[2, 20, 34, 52, 64, 96, 128, 160, 192, 224, 256]")
//
</template>
<script lang="ts">
import {defineComponent} from "vue";
import carta from "@/components/carta.vue";
import grupoCartas from "../../components/grupo-cartas.vue";
const cartas = [2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15,
16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 34, 34, 35, 35, 36, 36, 37, 37, 38, 38, 39, 39, 40, 40, 41,
41, 42, 42, 43, 43, 44, 44, 45, 45, 46, 46, 47, 47, 48, 48, 49, 49, 50, 50, 51, 51, 52, 52, 53, 53, 64, 64,
64, 64, 96, 96, 96, 96, 128, 128, 128, 128, 160, 160, 160, 160, 192, 192, 192, 192, 224, 224, 224, 224,
256, 256, 256, 256];
const cartasR = (() => {
const indices = [];
for (let i = 0; i < 11; i++) {
let sigIndice = Math.floor(Math.random() * cartas.length);
while (indices.find((s) => s === sigIndice) !== undefined) {
sigIndice = Math.floor(Math.random() * cartas.length);
}
indices.push(sigIndice);
}
const cartasN = indices.map((i) => cartas[i]);
const ultimaCarta = cartasN[10];
cartasN.pop();
const cartasN2 = cartasN.sort((x, y) => (x < y) ? -1 : 1);
cartasN2.push(-1);
cartasN2.push(ultimaCarta);
return cartasN2;
})();
const seqCartasR = (() => {
let base = Math.round(Math.random() * 9) + 1;
let nums;
switch (base) {
case 1:
nums = [1, 2, 3];
break;
case 10:
nums = [8, 9, 10];
break;
default:
const n = Math.random();
if (n < 0.33 && base !== 2) {
nums = [base - 2, base - 1, base]
} else if (n < 0.66 && base !== 9) {
nums = [base, base + 1, base + 2]
} else {
nums = [base - 1, base, base + 1]
}
}
return (Math.random() < 0.5)
? nums.map((n) => n * 2)
: nums.map((n) => (n + 16) * 2);
})();
const parCartasR = new Array(2).fill(cartas[Math.floor(Math.random() * cartas.length)]);
const triCartasR = new Array(3).fill(cartas[Math.floor(Math.random() * cartas.length)]);
const cuaCartasR = new Array(4).fill(cartas[Math.floor(Math.random() * cartas.length)]);
export default defineComponent({
name: "Ayuda",
components: {carta, grupoCartas},
setup() {
return {
cartas,
cartasR,
parCartasR,
seqCartasR,
triCartasR,
cuaCartasR
}
}
});
</script>
<style scoped lang="sass">
.pad
padding-left: 2rem
.tabla-puntos
padding-left: 2rem
.yaku
margin: 2rem 0
//
</style>

View File

@ -1,18 +0,0 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
export default defineComponent({
name: 'Home',
components: {
HelloWorld,
},
});
</script>

105
src/views/Inicio/Inicio.vue Normal file
View File

@ -0,0 +1,105 @@
<template lang="pug">
div
h1 Ri Ma Jon
p Ri Ma Jon Esmeralda!
div(v-if="datosVerificados")
div(v-if="idUsuario && nombreUsuario")
h2 Salas
p Bienvenido, {{nombreUsuario}}
entrar-sala
crear-sala
div(v-else)
crear-usuario
div(v-else)
p Conectando al servidor...
br
p
router-link(to="/ayuda") Ayuda
//
</template>
<script lang="ts">
import {defineComponent, computed, ref, onMounted} from "vue";
import {useStore} from "vuex";
import crearUsuario from "./components/crear-usuario.vue";
import crearSala from "./components/crear-sala.vue";
import entrarSala from "./components/entrar-sala.vue";
import { servidorF } from "@/variables";
export default defineComponent({
name: "Inicio",
components: {crearUsuario, crearSala, entrarSala},
setup() {
const store = useStore();
const datosVerificados = ref(false);
const idUsuario = computed(() => store.state.idUsuario);
const nombreUsuario = computed(() => store.state.nombreUsuario);
onMounted(async () => {
if (idUsuario.value && nombreUsuario.value) {
const solicitud = await fetch(`${servidorF}/usuario/validar`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
nombreUsuario: nombreUsuario.value,
idUsuario: idUsuario.value
})
});
if (solicitud.ok) {
const datos = await solicitud.json();
switch (datos.estado) {
case "ok": {
datosVerificados.value = true;
break;
}
case "nombreUsuarioInvalido": {
store.commit("setNombreUsuario", datos.nombreUsuario);
datosVerificados.value = true;
break;
}
case "idInvalido": {
console.log("El id provisto es invalido.");
store.commit("setIdUsuario", undefined);
store.commit("setNombreUsuario", undefined);
datosVerificados.value = true;
break;
}
}
} else {
// TODO
console.error("TODO");
}
} else {
datosVerificados.value = true;
}
});
return {
datosVerificados,
idUsuario,
nombreUsuario
}
}
});
</script>
<style scoped lang="sass">
//
</style>

View File

@ -0,0 +1,71 @@
<template lang="pug">
div
p Crear una sala:
form(@submit.prevent="crearSala")
input(type="submit" value="Crear!")
div(v-if="estado === 'creando'") Creando la sala...
div(v-if="estado === 'error'") Hubo un error al crear la sala
div(v-if="estado === 'listo'") La sala ha sido creada.
//
</template>
<script lang="ts">
import {defineComponent, ref, computed} from "vue";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
import { servidorF } from "@/variables";
export default defineComponent({
name: "crear-sala",
setup() {
const router = useRouter();
const store = useStore();
const estado = ref("inactivo");
const idUsuario = computed(() => store.state.idUsuario);
const crearSala = async () => {
estado.value = "creando";
try {
const url = `${servidorF}/partida`;
const respuesta = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({idUsuario: idUsuario.value})
});
if (respuesta.ok) {
const {id} = await respuesta.json();
estado.value = "listo";
router.push(`/sala/${id}`);
} else {
console.error(respuesta);
estado.value = "error";
}
} catch (e) {
console.log(e);
estado.value = "error";
}
};
return {
estado,
crearSala
}
}
});
</script>
<style scoped lang="sass">
//
</style>

View File

@ -0,0 +1,63 @@
<template lang="pug">
div
h2 Coloca un nombre para empezar:
form(@submit.prevent="registrar")
input(type="text" name="nombre_usuario" placeholder="Nombre" v-model="nombreUsuario")
input(type="submit" value="Registrar")
//
</template>
<script lang="ts">
import {defineComponent, ref} from "vue";
import { servidorF } from "@/variables";
import {useStore} from "vuex";
export default defineComponent({
name: "crear-usuario",
setup() {
const nombreUsuario = ref("");
const store = useStore();
const registrar = async () => {
try {
const peticion = await fetch(`${servidorF}/usuario/crear`, {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
nombreUsuario: nombreUsuario.value
})
});
if (peticion.ok) {
const {id} = await peticion.json();
store.commit("setIdUsuario", id);
store.commit("setNombreUsuario", nombreUsuario.value);
} else {
console.error("D:");
console.error(peticion);
}
} catch (e) {
console.error("D:");
console.error(e);
}
};
return {
nombreUsuario,
registrar
}
}
});
</script>
<style scoped lang="sass">
//
</style>

View File

@ -0,0 +1,57 @@
<template lang="pug">
div
p Entrar a una sala:
form(@submit.prevent="ingresar")
input(type="text" name="id_sala" placeholder="Codigo de sala" v-model="codigoSala")
input(type="submit" value="Entrar!")
//
</template>
<script lang="ts">
import {defineComponent, ref} from "vue";
import {useStore} from "vuex";
import {useRouter} from "vue-router";
import { servidorF } from "@/variables";
export default defineComponent({
name: "entrar-sala",
setup() {
const store = useStore();
const router = useRouter();
const codigoSala = ref("");
const ingresar = async () => {
const request = await fetch(`${servidorF}/partida-join`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({id: codigoSala.value})
});
if (request.ok) {
const data = await request.json();
if (data.ok) {
router.push(`/sala/${codigoSala.value}`);
}
} else {
console.error(request);
}
};
return {
codigoSala,
ingresar
}
}
});
</script>
<style scoped lang="sass">
//
</style>

239
src/views/Juego/Juego.vue Normal file
View File

@ -0,0 +1,239 @@
<template lang="pug">
div
contenedor-dora(:dora="dora" :doraOculto="doraOculto" :turnosRestantes="turnosDora")
div.con-int-juego
div.cont-2-juego
div.cont-cuadrante-cartas-juego
span(:style="{fontSize: `${pH * 10}px`}") {{ cartasRestantes }}
br
span(:style="{fontSize: `${pH * 2.5}px`}") Cartas restantes
mano(:mano="mano2" :posicion="2" :esTurnoActual="turnoActual === obtClave(map, '2')")
mano(:mano="mano3" :posicion="3" :esTurnoActual="turnoActual === obtClave(map, '3')")
mano(:mano="mano4" :posicion="4" :esTurnoActual="turnoActual === obtClave(map, '4')")
mano(:mano="mano1" :posicion="1" :esTurnoActual="turnoActual === obtClave(map, '1')")
//
</template>
<script lang="ts">
import {defineComponent, ref, computed, onMounted, onUnmounted} from "vue";
import { useRoute } from "vue-router";
import { useDimensions } from "@/components/useDimensions";
import { useStore } from "vuex";
import { wsServidor } from "@/variables";
import contenedorDora from "./components/contenedor-dora.vue"
import mano from "@/views/Juego/components/mano.vue";
const manoInicial = {
cartas: [],
allIn: false,
cartaSig: -1,
cartasReveladas: [],
descartes: [],
sigCarta: -1,
};
const obtClave = (obj: any, valor: string): string | undefined => {
for (const k in obj) if (obj.hasOwnProperty(k)) {
if (obj[k] === valor) return k;
}
}
export default defineComponent({
name: "Juego",
components: {contenedorDora, mano},
setup() {
const route = useRoute();
const store = useStore();
const {pH, pW} = useDimensions();
const ph = computed(() => pH.value + "px");
const pw = computed(() => pW.value + "px");
const esPantallaCompleta = ref(false);
const dora = ref([0, 0, 0, 0, 0]);
const doraOculto = ref([0, 0, 0, 0, 0]);
const turnoActual = ref<string | undefined>(undefined);
const cartasRestantes = ref(58);
const cartaDescartada = ref(false);
const turnosDora = ref(32);
const mano1 = ref(manoInicial);
const mano2 = ref(manoInicial);
const mano3 = ref(manoInicial);
const mano4 = ref(manoInicial);
const oportunidades = ref({});
const idJuego: string = route.params.id as string;
const idUsuario = store.state.idUsuario;
const pantallaCompleta = () => {
const elem = document.documentElement;
elem.requestFullscreen();
esPantallaCompleta.value = true;
}
const salirPantallaCompleta = () => {
document.exitFullscreen();
esPantallaCompleta.value = false;
};
let socket: WebSocket;
const map: any = {};
onMounted(() => {
if (!idJuego || !idUsuario) return;
socket = new WebSocket(`${wsServidor}/juego`);
socket.addEventListener("open", () => {
socket.send(JSON.stringify({
operacion: "conectar",
datos: JSON.stringify({
idJuego,
idUsuario
})
}));
});
socket.addEventListener("message", (ev) => {
const info = JSON.parse(ev.data);
switch (info.operacion) {
case "actualizar_datos": {
const d = info.datos;
console.log(info.datos);
dora.value = info.datos.dora;
console.log(dora.value);
doraOculto.value = info.datos.doraOculto;
turnosDora.value = info.datos.turnosHastaDora;
// Mapear IDS a posiciones
const turnoJugador = d.ordenJugadores.findIndex((id: string) => id === idUsuario);
map[idUsuario] = "1";
map[d.ordenJugadores[(turnoJugador + 1) % 4]] = "2";
map[d.ordenJugadores[(turnoJugador + 2) % 4]] = "3";
map[d.ordenJugadores[(turnoJugador + 3) % 4]] = "4";
for (const idUsuario in d.manos) {
const mano = d.manos[idUsuario];
const posMano = map[idUsuario];
const vSetMano = (() => {
switch (posMano) {
case "1": return mano1;
case "2": return mano2;
case "3": return mano3;
case "4": return mano4;
}
})();
vSetMano!!.value = mano;
}
cartasRestantes.value = d.cartasRestantes;
turnoActual.value = d.turnoActual;
break;
}
case "actualizar_manos": {
const d = info.datos;
console.log(info.datos);
dora.value = info.datos.dora;
doraOculto.value = info.datos.doraOculto;
cartaDescartada.value = false;
turnosDora.value = info.datos.turnosHastaDora;
for (const idUsuario in d.manos) {
const mano = d.manos[idUsuario];
const posMano = map[idUsuario];
console.log("idUsuario", idUsuario);
console.log("pos mano", posMano);
const vSetMano = (() => {
switch (posMano) {
case "1": return mano1;
case "2": return mano2;
case "3": return mano3;
case "4": return mano4;
}
})();
vSetMano!!.value = mano;
}
cartasRestantes.value = d.cartasRestantes;
turnoActual.value = d.turnoActual;
break;
}
case "oportunidad": {
oportunidades.value = info.datos;
}
}
});
});
onUnmounted(() => {
if (socket) socket.close();
});
const descartarCarta = (valorCarta: number) => {
if (turnoActual.value === obtClave(map, "1") && !cartaDescartada.value) {
cartaDescartada.value = true;
socket.send(JSON.stringify({
operacion: "descarte",
datos: JSON.stringify({
idJuego,
idUsuario,
carta: valorCarta
})
}));
}
};
return {
dora,
doraOculto,
turnosDora,
cartasRestantes,
mano1,
mano2,
mano3,
mano4,
turnoActual,
obtClave,
pH,
ph,
pw,
}
}
});
</script>
<style lang="sass" vars="{ph, pw}">
.con-int-juego
position: absolute
top: 0
left: calc((var(--pw) * 100 - var(--ph) * 100) / 2)
width: calc(var(--ph) * 100)
height: calc(var(--ph) * 100)
perspective: calc(var(--pw) * 10)
.cont-2-juego
position: absolute
border: solid 2px green
transform: rotateX(2deg)
width: 100%
height: 100%
.cont-cuadrante-cartas-juego
position: absolute
display: inline-block
width: 18%
height: 18%
bottom: 41%
right: 41%
text-align: center
//
</style>

View File

@ -0,0 +1,57 @@
<template lang="pug">
div.contenedor-dora
carta(v-for="(c, i) in dora" :valor="c" :escala="0.75" :key="i")
br
carta(v-for="(c, i) in doraOculto" :valor="c" :escala="0.75" :key="i")
//
</template>
<script lang="ts">
import {defineComponent, computed} from "vue";
import { useDimensions } from "@/components/useDimensions";
import carta from "@/components/carta.vue";
export default defineComponent({
name: "contenedor-dora",
components: {carta},
props: {
dora: {
type: Array,
default: [0, 0, 0, 0, 0]
},
doraOculto: {
type: Array,
default: [0, 0, 0, 0, 0]
},
turnosRestantes: {
type: Number,
default: 32
}
},
setup() {
const {pH} = useDimensions();
const pHc = computed(() => pH.value + "px");
return {
pHc
}
}
});
</script>
<style lang="sass" vars="{pHc}">
.contenedor-dora
position: fixed
top: calc(var(--pHc) * 2)
left: calc(var(--pHc) * 2)
padding: var(--pHc)
border-radius: calc(var(--pHc) / 2)
font-size: calc(var(--pHc) * 2.5)
box-shadow: 0 0 calc(var(--pHc) * 0.75) calc(var(--pHc) * 0.75) #dedede
background-color: var(--color-fondo)
//
</style>

View File

@ -0,0 +1,149 @@
<template lang="pug">
div.cont-cuadrante-2-mano
div.cuadrante-mano
carta(v-for="(c, i) in cartas" :valor="c" :movimiento="posiciones[i]" :fnDescartar="descartarCarta" :key="i")
//
</template>
<script lang="ts">
import { computed, defineComponent, ref, watch } from "vue";
import { useDimensions } from "@/components/useDimensions";
import carta from "@/components/carta.vue";
const estaOrdenado = (nums: number[]) => {
for (let i = 0, j = 1; j < nums.length ; i++, j++) {
if (nums[i] > nums[j]) return false;
}
return true;
};
const esperar = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export default defineComponent({
name: "mano",
components: {carta},
props: {
mano: Object,
posicion: Number,
esTurnoActual: Boolean,
oportunidades: Object,
fnDescartarCarta: Function
},
setup(props) {
const {pH, phx} = useDimensions();
const anchoCarta = computed(() => pH.value * 5 + 2 * (pH.value * 0.225))
const cartas = ref<number[]>(props.mano?.cartas);
const entrada = props.mano?.sigCarta;
const gruposAbiertos = props.mano?.cartasReveladas;
const descartes = props.mano?.descartes;
const ultimaCartaDescartada = props.oportunidades?.cartaDescartada ?? 0;
const posicion = props.posicion;
const esTurnoActual = props.esTurnoActual;
const oportunidades = props.oportunidades;
const descartarCarta = props.fnDescartarCarta ?? (() => {});
const posiciones = ref<string[]>(new Array(cartas.value.length).fill("none"));
const moverCarta = (posOriginal: number, posDestino: number, numCartas: number) => {
if (posDestino >= 0 && posDestino < numCartas) {
const cartasMovidas = -(posOriginal - posDestino);
return cartasMovidas === 0 ? `none` : `translateX(${anchoCarta.value * cartasMovidas}px)`
} else {
throw new Error("Movimiento (de posicion) de carta invalido");
}
};
const swap = (
posOriginalElem1: number,
posActualElem1: number,
posOriginalElem2: number,
posActualElem2: number,
numCartas: number
) => {
const operacion1 = moverCarta(posOriginalElem1, posActualElem2, numCartas);
const operacion2 = moverCarta(posOriginalElem2, posActualElem1, numCartas);
return [operacion1, operacion2];
};
const swapper = (arrSort: number[][], numElems: number) => (i: number, j: number) => {
const [op1, op2] = swap(arrSort[i][1], i, arrSort[j][1], j, numElems);
if (posiciones.value[arrSort[i][1]] !== op1) {
posiciones.value[arrSort[i][1]] = op1;
}
if (posiciones.value[arrSort[j][1]] !== op2) {
posiciones.value[arrSort[j][1]] = op2;
}
const swapT = arrSort[i];
arrSort[i] = arrSort[j];
arrSort[j] = swapT;
};
const retraso = 50;
const isort = async (arr: number[]) => {
const numElems = arr.length;
const arrSort = arr.map((x, p) => [x, p]);
const nSwap = swapper(arrSort, numElems);
for (let actualiter = 1; actualiter < numElems; actualiter++) {
let posActual = actualiter;
while (posActual > 0 && arrSort[posActual - 1][0] > arrSort[posActual][0]) {
nSwap(posActual - 1, posActual);
await esperar(retraso);
posActual--;
}
}
return arrSort;
};
const manoCartasWrapper = computed(() => props.mano?.cartas);
watch(manoCartasWrapper, (n) => {
cartas.value = n;
});
watch(cartas, (n) => {
if (estaOrdenado(n)) return;
// const arrOrdenado = await isort(cartas, setPosiciones);
// setCartas(arrOrdenado.map(x => x[0]));
cartas.value = n.sort((x, y) => (x < y)? -1 : 1);
posiciones.value = new Array(n.length).fill("none");
});
return {
cartas,
posiciones,
descartarCarta,
phx,
posicionW: computed(() => (90 * (5 - posicion!!)) + "deg")
}
}
});
</script>
<style scoped lang="sass" vars="{phx, posicionW}">
.cont-cuadrante-2-mano
position: absolute
width: 100%
height: 100%
transform: rotate(var(--posicionW))
.cuadrante-mano
position: absolute
width: 87%
height: 10%
bottom: 0
right: 0
text-align: left
//
</style>

128
src/views/Sala/Sala.vue Normal file
View File

@ -0,0 +1,128 @@
<template lang="pug">
div
div(v-if="!idSala || !idUsuario")
h1 Sala de espera - Error
p No te has conectado a ninguna sala
router-link(to="/") Regresar al inicio
div(v-else-if="estado === 'conectando'")
h1 Sala de espera - Conectando
h2 El código de la sala es {{idSala}}
p Conectando al servidor...
div(v-else-if="estado === 'conectado'")
h1 Sala de espera
h2 El código de la sala es {{idSala}}
p Jugadores conectados:
p(v-for="u in usuarios") Usuario: {{u.nombreUsuario}}
div(v-if="usuarios.length === 4")
button(@click="iniciarJuego") Iniciar el juego!
div(v-else-if="estado.startsWith('error') && estado.substr(6) === 'Sala no existe'")
h1 Sala de espera - Error
p No existe la sala {{idSala}}
router-link(to="/") Regresar al inicio
div(v-else)
p Algo salió mal.
router-link(to="/") Regresar al inicio
//
</template>
<script lang="ts">
import {defineComponent, computed, ref, onMounted, onUnmounted} from "vue";
import { useStore } from "vuex";
import { wsServidor } from "@/variables";
import router from "@/router";
import { useRoute } from "vue-router";
interface Usuario {
idUsuario: string,
nombreUsuario: string
}
export default defineComponent({
name: "Sala",
setup() {
const store = useStore();
const route = useRoute();
const estado = ref("conectando");
const usuarios = ref<Array<Usuario>>([]);
const idSala = route.params.id;
const idUsuario = computed(() => store.state.idUsuario);
const nombreUsuario = computed(() => store.state.nombreUsuario);
let socket: WebSocket;
onMounted(() => {
if (!idUsuario) return;
socket = new WebSocket(`${wsServidor}/socket`);
socket.addEventListener("open", () => {
socket.send(JSON.stringify({
operacion: "conectar",
datos: JSON.stringify({
idJuego: idSala,
idUsuario: idUsuario.value
})
}));
});
socket.addEventListener("message", (ev) => {
const datos = JSON.parse(ev.data);
switch (datos.operacion) {
case "conexion_exitosa": {
estado.value = "conectado";
const jugadores = datos.jugadores;
jugadores.push({idUsuario, nombreUsuario})
usuarios.value = jugadores;
break;
}
case "usuario_conectado": {
const idUsuario = datos.idUsuario;
const nombreUsuario = datos.nombreUsuario;
usuarios.value.push({idUsuario, nombreUsuario});
break;
}
case "juego_iniciado": {
router.push(`/juego/${idSala}`);
}
}
});
});
onUnmounted(() => {
socket.close();
});
const iniciarJuego = () => {
socket.send(JSON.stringify({
operacion: "iniciar",
datos: JSON.stringify({
idJuego: idSala
})
}));
};
return {
estado,
usuarios,
idSala,
idUsuario,
nombreUsuario,
iniciarJuego
}
}
});
</script>
<style scoped lang="sass">
//
</style>