This commit is contained in:
Aleksandr Zaitsev 2024-07-03 22:16:37 +03:00
commit b6b99c3ef4
112 changed files with 4046 additions and 0 deletions

30
.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

29
README.md Normal file
View File

@ -0,0 +1,29 @@
# wilg-game
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Game</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

8
jsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

1372
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "wilg-game",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.6.8",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"uuid": "^9.0.1",
"vue": "^3.4.21",
"vue-i18n": "^9.12.1",
"vue-router": "^4.3.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"vite": "^5.2.8"
}
}

5
postcss.config.js Normal file
View File

@ -0,0 +1,5 @@
export default {
plugins: {
autoprefixer: {},
},
}

BIN
public/audio/fish.wav Normal file

Binary file not shown.

BIN
public/audio/move.wav Normal file

Binary file not shown.

BIN
public/back/32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

BIN
public/back/33.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
public/back/40.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
public/back/41.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
public/back/42.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

BIN
public/back/45.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

BIN
public/back/47.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

BIN
public/back/50.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
public/images/grass/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 B

BIN
public/images/grass/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
public/images/grass/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
public/images/header.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
public/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

BIN
public/images/margin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

BIN
public/images/shore.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 953 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

BIN
public/images/sprites.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
public/images/water/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 B

BIN
public/images/water/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

BIN
public/images/water/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

206
src/App.vue Normal file
View File

@ -0,0 +1,206 @@
<script setup>
import { RouterView } from 'vue-router'
import router from "@/router";
import { useApplicationStore } from './stores/application';
import { storeToRefs } from "pinia";
import RangeSliderComponent from '@/components/common/RangeSliderComponent.vue'
import { usePlayerStore } from './stores/player';
import i18n from '@/i18n/i18n'
const application = useApplicationStore()
const player = usePlayerStore()
const { settings } = storeToRefs(application)
const changeLanguage = (lang) => {
application.language = lang
i18n.global.locale.value = application.language
}
const saveUsername = (event) => {
if (event.target.value.length > 0) player.username = event.target.value
}
</script>
<template>
<div class="header">
<div class="header-wrapper">
<button class="header-button " @click="application.openSettings()">{{ $t('button.settings') }}</button>
<button class="header-button " @click="application.openMenu()">{{ $t('button.menu') }}</button>
</div>
</div>
<div class="settings" v-if="application.showSettings">
<button class="button-close" @click="application.closeSettings()">{{ $t('button.close') }}</button>
<div class="settings-group">
<div class="settings-group-title">{{ $t('settings.sound.title') }}</div>
<div class="mute-wrapper">
<div class="mute-title">{{ $t('settings.sound.mute') }}</div>
<input type="checkbox" v-model="settings.sound.muted">
</div>
<RangeSliderComponent v-model="settings.sound.master" :name="$t('settings.sound.master')" />
<RangeSliderComponent v-model="settings.sound.music" :name="$t('settings.sound.music')" />
<RangeSliderComponent v-model="settings.sound.game" :name="$t('settings.sound.effects')" />
<RangeSliderComponent v-model="settings.sound.ambient" :name="$t('settings.sound.ambient')" />
</div>
</div>
<div class="menu" v-if="application.showMenu">
<button class="button-close" @click="application.closeMenu()">{{ $t('button.close') }}</button>
<div class="menu-group">
<div class="menu-group-title">{{ $t('title.player') }}</div>
<input class="input-menu-username" type="text" @change="saveUsername($event)" :value="player.username" />
<div class="menu-player-id">{{ player.id }}</div>
</div>
<div class="menu-group">
<div class="menu-group-title">{{ $t('title.game') }}</div>
<button class="menu-button" @click="router.replace({ name: 'index' }); application.closeMenu()">{{
$t('button.mainScreen') }}</button>
<button class="menu-button">{{ $t('button.instruction') }}</button>
<button class="menu-button">{{ $t('button.rules') }}</button>
</div>
<div class="menu-group-bottom">
<div class="menu-group-title">{{ $t('title.language') }}</div>
<button class="menu-button-language" @click="changeLanguage('en')">EN</button>
<button class="menu-button-language" @click="changeLanguage('ru')">RU</button>
</div>
</div>
<div class="menu-overlay" v-if="application.showMenu || application.showSettings"></div>
<RouterView />
</template>
<style scoped>
.header {
width: 100%;
align-items: center;
height: 100px;
margin-bottom: 20px;
position: sticky;
background-image: url('/public/images/header.png');
background-color: #72c37a;
}
.header-wrapper {
display: flex;
width: 100%;
justify-content: space-between;
padding: 0 1rem 0 1rem;
height: 62px;
}
.header-button {
margin-top: 10px;
height: 42px;
min-width: 130px;
border-radius: 0.5rem;
}
.menu-overlay {
height: 100%;
width: 100%;
position: fixed;
z-index: 98;
top:0;
right: 0;
backdrop-filter: blur(4px);
}
.menu {
position: fixed;
top: 0;
right: 0;
z-index: 99;
overflow-x: hidden;
background-color: white;
height: 100%;
width: 100%;
max-width: 32rem;
padding: 2rem;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
}
.menu-button {
display: block;
margin-left: auto;
margin-right: auto;
}
.menu-group {
margin-bottom: 2rem;
}
.menu-group-bottom {
margin-top: auto;
}
.menu-group-title {
font-size: x-large;
width: 100%;
text-align: center;
font-weight: 600;
margin-bottom: 1rem;
}
.menu-button-language {
border-radius: 0.5rem;
}
.menu-button-language+.menu-button-language {
margin-left: 1rem;
}
.menu-player-id {
width: 100%;
text-align: center;
font-size: smaller;
color: gray;
}
.settings {
position: fixed;
top: 0;
left: 0;
z-index: 99;
padding: 2rem;
overflow: hidden;
background-color: white;
height: 100%;
width: 100%;
max-width: 32rem;
}
.settings-group-title {
font-size: x-large;
width: 100%;
text-align: center;
font-weight: 600;
margin-bottom: 1rem;
}
.mute-wrapper {
width: 100%;
display: grid;
grid-template-columns: 20% 70% 10%;
align-content: baseline;
justify-content: center;
padding: 0 2rem 1rem 2rem;
}
.button-close {
width: 100%;
}
.input-menu-username {
width: 100%;
text-align: center;
color: black;
}
.input-menu-username &:hover,
&:focus,
&:active {
outline: none;
box-shadow: none;
border: none;
border-bottom: 1px solid gray;
}
</style>

61
src/assets/animation.css Normal file
View File

@ -0,0 +1,61 @@
@keyframes water {
0% {
background-image: url('/public/images/water/1.png');
}
25% {
background-image: url('/public/images/water/2.png');
}
50% {
background-image: url('/public/images/water/3.png');
}
75% {
background-image: url('/public/images/water/2.png');
}
}
@keyframes empty {
0% {
transform: scale(0);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 0.8;
}
}
@keyframes caught {
0% {
-webkit-transform: scale(0) rotate(360deg);
opacity: 0;
}
100% {
-webkit-transform: scale(1) rotate(-360deg);
opacity: 1;
}
}
@keyframes hit {
0% {
background-color: rgba(0, 102, 143, 1);
border-radius: 50%;
height: 30%;
width: 30%;
}
100% {
height: 100%;
width: 100%;
background-color: transparent;
}
}
.lake-cell {
animation: water 2s infinite;
}

106
src/assets/item.css Normal file
View File

@ -0,0 +1,106 @@
/* FISH */
.lobby {
[class*="fish-"]::after {
opacity: 1;
}
.margin::after {
opacity: 0.2;
}
}
[class*="fish-"]::after, .margin::after, .empty-empty::after {
content: " ";
position: absolute;
height: 48px;
width: 48px;
scale: 0.9;
opacity: 0.2;
background-repeat: no-repeat;
background-size: 80%;
background-position: center;
}
.hit[class*="fish-"]::after {
animation: caught 0.5s;
opacity: 1;
}
.hit::before {
content: " ";
position: absolute;
animation: hit 0.5s;
}
.fish-1::after {
background: url('/public/images/sprites.png') 0 0;
}
.fish-2::after {
background: url('/public/images/sprites.png') -48px 0;
}
.fish-3::after {
background: url('/public/images/sprites.png') -96px 0;
}
.fish-4::after {
background: url('/public/images/sprites.png') -144px 0;
}
.fish-5::after {
background: url('/public/images/sprites.png') -192px 0;
}
.fish-6::after {
background: url('/public/images/sprites.png') -240px 0;
}
.fish-7::after {
background: url('/public/images/sprites.png') -288px 0;
}
.fish-8::after {
background: url('/public/images/sprites.png') -336px 0;
}
.fish-9::after {
background: url('/public/images/sprites.png') -384px 0;
}
.fish-10::after {
background: url('/public/images/sprites.png') -432px 0;
}
.fish-11::after {
background: url('/public/images/sprites.png') 0 -48px;
}
.fish-12::after {
background: url('/public/images/sprites.png') -48px -48px;
}
.fish-13::after {
background: url('/public/images/sprites.png') -96px -48px;
}
.fish-14::after {
background: url('/public/images/sprites.png') -144px -48px;
}
.fish-15::after {
background: url('/public/images/sprites.png') -192px -48px;
}
.fish-16::after {
background: url('/public/images/sprites.png') -240px -48px;
}
.fish-17::after {
background: url('/public/images/sprites.png') -288px -48px;
}
.fish-18::after {
background: url('/public/images/sprites.png') -336px -48px;
}
.fish-19::after {
background: url('/public/images/sprites.png') -384px -48px;
}
.fish-20::after {
background: url('/public/images/sprites.png') -432px -48px;
}
.fish-21::after {
background: url('/public/images/sprites.png') 0 -96px;
}
.fish-22::after {
background: url('/public/images/sprites.png') -48px -96px;
}
.margin::after{
background: url('/public/images/sprites.png') -432px -432px;
opacity: 0.2;
}
.empty-empty::after {
background: url('/public/images/sprites.png') -432px -432px;
opacity: 0.8;
animation: empty 0.5s;
}

242
src/assets/main.css Normal file
View File

@ -0,0 +1,242 @@
@import url('./animation.css');
@import url('./item.css');
:root {
--cloud-color: rgba(255, 255, 255);
--shadow-color: rgba(0, 0, 0);
--water-color: rgba(111, 181, 213);
--water-deep-color: rgba(0, 74, 230);
--water-deep-border-color: rgba(0, 74, 230, 0.2);
--wood-color: rgba(147, 117, 96);
}
@font-face {
font-family: 'ShantellSansItalic';
src: url('./fonts/ShantellSans-Italic-VariableFont_BNCE,INFM,SPAC,wght.ttf') format("truetype-variations");
font-weight: 300 800;
}
@font-face {
font-family: 'ShantellSans';
src: url('./fonts/ShantellSans-VariableFont_BNCE,INFM,SPAC,wght.ttf') format("truetype-variations");
font-weight: 300 800;
}
html, body, #app {
height: 100%;
width: 100%;
overscroll-behavior: none;
font-family: 'ShantellSans';
font-weight: 400;
user-select: none;
overflow: hidden;
}
body {
background: url('/public/images/grass/1.png') 48px 48px,
url('/public/images/grass/2.png') 48px 48px,
url('/public/images/grass/3.png') 48px 48px;
}
h1 {
font-size: xx-large;
font-weight: 600;
text-align: center;
color: var(--cloud-color);
text-shadow: .5rem .5rem 1rem var(--shadow-color);
margin-bottom: 1rem;
}
h2 {
font-size: xx-large;
font-weight: 600;
text-align: center;
color: var(--cloud-color);
text-shadow: .5rem .5rem 1rem var(--shadow-color);
margin-bottom: 2rem;
}
button {
background-color: var(--wood-color);
font-size: 1.5rem;
color: var(--cloud-color);
padding: 0.1rem 1rem;
margin-bottom: 2rem;
}
.button-lobby {
margin-bottom: 0;
display: block;
margin-left: auto;
margin-right: auto;
border-radius: 0.5rem;
}
.button-lobby + .button-lobby {
margin-top: 0.7rem;
}
.game, .lobby {
display: grid;
justify-items: center;
grid-template-columns: 100%;
grid-template-rows: 100px auto;
grid-auto-rows: 0;
height: calc(100% - 120px);
width: 100%;
overflow: hidden;
}
.player-view, .opponent-view { width: 100%;
height: 100%;
overflow: hidden;
background-color: var(--water-color);
}
.lake-border {
position: relative;
overflow: hidden;
max-width: calc(16px + 10 * 50px);
width: 100%;
max-height: calc(25px + 10 * 50px);
height: 100%;
border: 18px solid transparent;
border-image: url('/public/images/shore.png') 18 / 18px repeat;
}
.lake {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: fit-content;
height: fit-content;
justify-content: center;
background-color: var(--water-color);
}
.lake-title {
text-align: center;
}
.lake-row {
display: flex;
flex-direction: row;
}
.lake-cell {
display: flex;
height: 48px;
width: 48px;
cursor: pointer;
background-image: url('/public/images/water/1.png');
align-items: center;
justify-content: center;
padding: 4px;
position: relative;
color: rgba(0, 102, 143, 0.3);
}
.lake-row + .lake-row {
border-top: 1px var(--water-deep-border-color) solid;
}
.lake-cell + .lake-cell {
border-left: 1px var(--water-deep-border-color) solid;
}
.hide {
height: 0;
animation: hide 2s;
}
.player-view.active {
animation: active 2s;
}
.player-view.hide {
height: 0;
animation: hide 2s;
}
.opponent-view.active {
animation: active-opponent 2s;
}
.opponent-view.hide {
height: 0;
animation: hide-opponent 2s;
}
@keyframes hide {
0% {
max-width: calc(16px + 10 * 50px);
width: 100%;
max-height: calc(25px + 10 * 50px);
height: 100%;
}
50% {
height: 0;
}
100% {
height: 0;
}
}
@keyframes active {
0% {
height: 0;
}
50% {
max-width: calc(16px + 10 * 50px);
width: 100%;
max-height: calc(25px + 10 * 50px);
height: 100%;
}
100% {
max-width: calc(16px + 10 * 50px);
width: 100%;
max-height: calc(25px + 10 * 50px);
height: 100%;
}
}
@keyframes hide-opponent {
0% {
/* border-top: 2rem #fff solid;*/
max-width: calc(16px + 10 * 50px);
width: 100%;
max-height: calc(25px + 10 * 50px);
height: 100%;
}
50% {
height: 0;
}
100% {
height: 0;
}
}
@keyframes active-opponent {
0% {
/* border-top: 2rem #fff solid;*/
height: 0;
}
50% {
max-width: calc(16px + 10 * 50px);
width: 100%;
max-height: calc(25px + 10 * 50px);
height: 100%;
}
100% {
max-width: calc(16px + 10 * 50px);
width: 100%;
max-height: calc(25px + 10 * 50px);
height: 100%;
}
}

363
src/assets/preflight.css Normal file
View File

@ -0,0 +1,363 @@
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box; /* 1 */
border-width: 0; /* 2 */
border-style: solid; /* 2 */
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS
*/
html,
:host {
line-height: 1.5; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
-moz-tab-size: 4; /* 3 */
tab-size: 4; /* 3 */
/* font-family: theme('fontFamily.sans', ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); /* 4 */
/* font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings', normal); /* 5 */
/* font-variation-settings: theme('fontFamily.sans[1].fontVariationSettings', normal); /* 6 */
-webkit-tap-highlight-color: transparent; /* 7 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0; /* 1 */
line-height: inherit; /* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0; /* 1 */
color: inherit; /* 2 */
border-top-width: 1px; /* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0; /* 1 */
border-color: inherit; /* 2 */
border-collapse: collapse; /* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-feature-settings: inherit; /* 1 */
font-variation-settings: inherit; /* 1 */
font-size: 100%; /* 1 */
font-weight: inherit; /* 1 */
line-height: inherit; /* 1 */
letter-spacing: inherit; /* 1 */
color: inherit; /* 1 */
margin: 0; /* 2 */
padding: 0; /* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
input:where([type='button']),
input:where([type='reset']),
input:where([type='submit']) {
-webkit-appearance: button; /* 1 */
background-color: transparent; /* 2 */
background-image: none; /* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: none;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Reset default styling for dialogs.
*/
dialog {
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::placeholder,
textarea::placeholder {
opacity: 1; /* 1 */
color: #9ca3af; /* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block; /* 1 */
vertical-align: middle; /* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden] {
display: none;
}

View File

@ -0,0 +1,27 @@
const gameSound = {
move: new Audio('/audio/move.wav'),
fish: new Audio('/audio/fish.wav'),
}
export default class AudioPlayer {
constructor(settings) {
this.settings = settings
}
async gameAsync(name) {
if (!navigator.userActivation.isActive) return
return new Promise(ended=>{
let sound = this.game(name)
sound.onended = ended
})
}
game(name) {
if (!navigator.userActivation.isActive) return
gameSound[name].play()
gameSound[name].muted = this.settings.muted
gameSound[name].volume = (this.settings.game * this.settings.master) / 10000
return gameSound[name]
}
}

View File

@ -0,0 +1,185 @@
export default class boardConstructor {
constructor(boardSize = 10, availableItems) {
this.boardSize = boardSize;
this.availableItems = availableItems
}
generateItems() {
let items = {};
let itemsCount = 4;
let curentSize = 1;
for (let i = itemsCount; i > 0; i--) {
let key = this.__generateItemId();
items[key] = {
id: key,
size: curentSize,
type: "fish",
name: this.availableItems.pop(Math.floor(Math.random() * this.availableItems.length) + 1),
orientation: "h",
margin: 1,
cells: [],
marginCells: [],
};
if (i <= 1) {
i = itemsCount;
itemsCount--;
curentSize++;
}
}
return items;
}
generateBoard(items) {
Object.keys(items).map((id) => {
items[id].cells = [];
items[id].marginCells = [];
});
let itemlist = Object.values(items);
itemlist.sort((a, b) => b.size - a.size);
while (itemlist.length > 0) {
let currentItem = { ...itemlist.shift() };
currentItem.orientation = Math.random() < 0.5 ? "h" : "v";
while (!this.isItemAlreadyOnBoard(items[currentItem.id])) {
let targetCell = Math.floor(
Math.random() * this.boardSize * this.boardSize
);
currentItem.cells = this.getItemCells(targetCell, currentItem);
currentItem.marginCells = this.getItemMarginCells(currentItem);
if (
this.isItemHasMarginCollision(currentItem, items) ||
this.isItemHasCellsCollision(currentItem, items)
)
continue;
items[currentItem.id] = currentItem;
}
}
return items;
}
getItemCells(targetCell, item) {
let cells = [];
let cellRow = Math.floor(targetCell / this.boardSize);
for (let i = 0; i <= item.size - 1; i++) {
if (item.orientation == "h") {
let currentCell = targetCell + i;
if (
currentCell <=
cellRow * this.boardSize + (this.boardSize - 1)
) {
cells.push(currentCell);
} else {
cells.push(
targetCell +
(cellRow * this.boardSize +
(this.boardSize - 1) -
currentCell)
);
}
} else if (item.orientation == "v") {
let currentCell = targetCell + this.boardSize * i;
if (currentCell < this.boardSize * this.boardSize) {
cells.push(currentCell);
} else {
cells.push(targetCell - this.boardSize * (item.size - i));
}
}
}
return cells;
}
getItemMarginCells(item) {
if (item.margin <= 0 || item.cells.length <= 0) return [];
let marginCells = item.cells.map((cell) => {
return this.__getCellMargin(cell, item.margin);
});
return marginCells
.flat(1)
.filter((cell) => item.cells.indexOf(cell) < 0);
}
findItemIdInBoardCell(targetCell, items) {
let item = Object.keys(items).filter((id) => {
return items[id].cells.includes(targetCell);
});
return item[0];
}
isItemAlreadyOnBoard(item) {
return Object.values(item.cells).length > 0;
}
isItemHasMarginCollision(item, items) {
if (item.cells?.length <= 0 && item.cells.marginCells?.length <= 0)
return false;
return (
Object.keys(items).filter((id) => {
let collisionWithBoardItem =
items[id].marginCells.filter((itemMarginCell) =>
item.cells.includes(itemMarginCell)
).length > 0;
let collisionWithCurrentItem =
items[id].cells.filter((itemCell) =>
item.marginCells.includes(itemCell)
).length > 0;
return collisionWithBoardItem || collisionWithCurrentItem;
})?.length > 0
);
}
isItemHasCellsCollision(item, items) {
if (item.cells?.length <= 0) return false;
return (
Object.keys(items).filter((id) => {
return (
items[id].cells.filter((itemCell) =>
item.cells.includes(itemCell)
).length > 0
);
})?.length > 0
);
}
isAllItemsReady(items) {
return (
Object.keys(items).filter((id) => {
return items[id].cells?.length <= 0;
})?.length <= 0
);
}
__getCellMargin(cell, margin = 0) {
let cellMargin = [];
let cellRow = Math.floor(cell / this.boardSize);
let maxCells = this.boardSize * this.boardSize - 1;
let calcCell = null;
for (
let rowMargin = margin;
rowMargin >= -Math.abs(margin);
rowMargin--
) {
let currentRow = cellRow + rowMargin;
let rowOffset = this.boardSize * rowMargin;
let rowMin = currentRow * this.boardSize;
let rowMax = rowMin + this.boardSize - 1;
for (
let colMargin = margin;
colMargin >= -Math.abs(margin);
colMargin--
) {
calcCell = cell + rowOffset + colMargin;
if (
calcCell >= 0 &&
calcCell <= maxCells &&
calcCell >= rowMin &&
calcCell <= rowMax
)
cellMargin.push(calcCell);
}
}
return cellMargin;
}
__generateItemId() {
return Math.random().toString(36).replace("0.", "");
}
}

39
src/classes/scrollable.js Normal file
View File

@ -0,0 +1,39 @@
export default class Scrollable {
canScrolling = false;
x = 0;
y = 0;
constructor(elemet) {
this.elemet = elemet;
}
start(event) {
if (event.changedTouches != undefined) {
this.x = event.changedTouches[0].screenX;
this.y = event.changedTouches[0].screenY;
} else {
this.x = event.screenX;
this.y = event.screenY;
}
this.canScrolling = true;
}
move(event) {
if (this.canScrolling == false) return;
let x =
event.changedTouches != undefined
? event.changedTouches[0].screenX
: event.screenX;
let y =
event.changedTouches != undefined
? event.changedTouches[0].screenY
: event.screenY;
this.elemet.value.scrollLeft += this.x - x;
this.elemet.value.scrollTop += this.y - y;
this.x = x;
this.y = y;
}
end() {
this.canScrolling = false;
this.x = 0;
this.y = 0;
}
}

View File

@ -0,0 +1,130 @@
<script setup>
import { ref, onMounted, computed } from 'vue'
const props = defineProps({
name: {
default: '',
type: String
},
min: {
default: 0,
type: Number
},
max: {
default: 100,
type: Number
},
step: {
default: 5,
type: Number
},
})
const modelValue = defineModel()
onMounted(() => {
value.value = modelValue.value
draw(value.value)
})
const range = ref()
const selector = ref()
const isDragging = ref(false)
const value = ref(0)
const getValue = (event) => {
if (isDragging.value == false) return value.value
let rect = range.value.getBoundingClientRect()
let x = 0
if (event.changedTouches == undefined) {
x = event.clientX
} else {
x = event.changedTouches[0].clientX
}
let countedValue = props.min;
countedValue = x - rect.left;
countedValue = countedValue / rect.width;
countedValue = countedValue * props.max;
countedValue = Math.round(countedValue / props.step) * props.step;
countedValue = countedValue / (props.max - props.min);
countedValue = Math.round(countedValue * 100);
if (countedValue > props.max) countedValue = props.max;
if (countedValue < props.min) countedValue = props.min;
draw(countedValue)
return countedValue
}
const draw = (countedValue) => {
countedValue = countedValue * (props.max - props.min);
countedValue = Math.round(countedValue / props.max);
countedValue = props.max - countedValue;
if (countedValue > 100) countedValue = 100;
if (countedValue < 0) countedValue = 0;
selector.value.style.width = (100 - countedValue) + '%'
}
const start = (event) => {
isDragging.value = true
}
const select = (event) => {
let v = getValue(event)
value.value = v
}
const submit = (event) => {
select(event);
modelValue.value = value.value
isDragging.value = false
}
</script>
<template>
<div class="range">
<div class="range-label">{{ props.name }}</div>
<div class="range-wrapper">
<div class="range-slider" ref="range" @mousedown="start($event)" @mousemove="select($event)"
@mouseup="submit($event)" @touchstart="start($event)" @touchmove="select($event)"
@touchend="submit($event)">
<div class="range-slider-completed" ref="selector"></div>
<div class="range-value">{{ value }}</div>
</div>
</div>
</div>
</template>
<style>
.range-wrapper {
width: 100%;
display: flex;
align-content: baseline;
justify-content: center;
padding: 0 2rem 1rem 2rem;
}
.range-label {
text-align: center;
width: 100%;
font-size: large;
padding-bottom: 0.5rem;
}
.range-value {
font-size: large;
font-weight: 600;
}
.range-slider {
width: 100%;
height: 30px;
background-color: rgba(127, 165, 209, 0.349);
position: relative;
display: flex;
justify-content: center;
align-items: center;
user-select: none;
cursor: pointer;
}
.range-slider-completed {
width: 0%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: inherit;
transition: 0.1s;
}
</style>

20
src/i18n/i18n.js Normal file
View File

@ -0,0 +1,20 @@
import { createI18n } from 'vue-i18n'
import pluralRules from "./rules/pluralization.js"
import numberFormats from "./rules/numbers.js"
import datetimeFormats from "./rules/datetime.js"
import en from './locales/en.json'
import ru from './locales/ru.json'
const i18n = createI18n({
legacy: false,
globalInjection: true,
messages: {
en,
ru
},
pluralRules,
numberFormats,
datetimeFormats
})
export default i18n

44
src/i18n/locales/en.json Normal file
View File

@ -0,0 +1,44 @@
{
"application": {
"name": "Fishing"
},
"button": {
"exit": "Exit",
"close": "Close",
"newGame": "New Game",
"settings": "Settings",
"menu": "Menu",
"singlePlayer": "Single Player",
"multiPlayer": "Multi Player",
"generate": "Release the fish",
"ready": "Ready",
"cancel": "Cancel",
"mainScreen": "Go to main screen",
"instruction": "How to play",
"rules": "Game rules"
},
"settings": {
"sound": {
"title": "Sound",
"mute": "Mute",
"master": "Master",
"effects": "Effects",
"music": "Music",
"ambient": "Ambient"
}
},
"game": {
"yourTurn": "Your turn",
"opponentTurn": "Opponent`s turn",
"over": "Game Over",
"win": "You win!",
"loose": "You loose!"
},
"title": {
"game": "Game",
"language": "Language",
"player": "Player"
}
}

42
src/i18n/locales/ru.json Normal file
View File

@ -0,0 +1,42 @@
{
"application": {
"name": "Рыбалка"
},
"button": {
"exit": "Выйти",
"close": "Закрыть",
"newGame": "Новая игра",
"settings": "Настройки",
"menu": "Меню",
"singlePlayer": "Одиночная игра",
"multiPlayer": "Играть с другом",
"generate": "Запустить рыбу",
"ready": "Готов",
"cancel": "Отмена",
"mainScreen": "На главный экран",
"instruction": "Как играть",
"rules": "Правила"
},
"settings": {
"sound": {
"title": "Звук",
"mute": "Выключить",
"master": "Общая",
"effects": "Эффекты",
"music": "Музыка",
"ambient": "Окружение"
}
},
"game": {
"yourTurn": "Ваш ход",
"opponentTurn": "Ход соперника",
"over": "Игра окончена",
"win": "Вы выиграли!",
"loose": "Вы проиграли!"
},
"title": {
"game": "Игра",
"language": "Язык",
"player": "Игрок"
}
}

View File

@ -0,0 +1,20 @@
export default {
en: {
shortFormat: {
dateStyle: "short"
},
longFormat: {
year: 'numeric', month: 'short', day: 'numeric',
weekday: 'short', hour: 'numeric', minute: 'numeric'
}
},
ru: {
shortFormat: {
dateStyle: "short"
},
longFormat: {
year: 'numeric', month: 'short', day: 'numeric',
weekday: 'short', hour: 'numeric', minute: 'numeric'
}
}
}

14
src/i18n/rules/numbers.js Normal file
View File

@ -0,0 +1,14 @@
export default {
en: {
currencyFormat: {
style: "currency",
currency: "USD"
}
},
ru: {
currencyFormat: {
style: "currency",
currency: "RUB"
}
}
}

View File

@ -0,0 +1,20 @@
function ruPluralizationRules(
choice,
choicesLength
) {
if (choice === 0) {
return 0
}
const teen = choice > 10 && choice < 20
const endsWithOne = choice % 10 === 1
if (!teen && endsWithOne) {
return 1
}
if (!teen && choice % 10 >= 2 && choice % 10 <= 4) {
return 2
}
return choicesLength < 4 ? 2 : 3
}
export default {
ru: ruPluralizationRules
};

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