From 2d890244ad61a9e766e3a14bd64beb0c56d02561 Mon Sep 17 00:00:00 2001 From: zaqw60 Date: Tue, 18 May 2021 00:30:22 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BA=20=D1=88=D0=B5=D1=81=D1=82=D0=BE=D0=BC=D1=83=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BD=D1=8F=D1=82=D0=B8=D1=8E=20=D1=81=D0=BE=D0=B2=D1=80?= =?UTF-8?q?.=20JS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 01.06/index.html | 96 +++++ 02,07/.idea/.gitignore | 8 + 02,07/.idea/misc.xml | 6 + 02,07/.idea/modules.xml | 8 + 02,07/.idea/project_markup.iml | 8 + 02,07/css/normalize.css | 349 ++++++++++++++++ 02,07/css/style.css | 171 ++++++++ 02,07/getProducts.json | 12 + 02,07/index.html | 40 ++ 02,07/js/CartComponent.js | 29 ++ 02,07/js/FilterComponent.js | 9 + 02,07/js/ProductComponent.js | 22 + 02,07/js/main.js | 281 +++++++++++++ 11.06/index.html | 20 + 11.06/js.js | 742 +++++++++++++++++++++++++++++++++ 11.06/style.css | 56 +++ 25.06/index.html | 46 ++ 28.05/index.html | 76 ++++ 8.06/index.html | 152 +++++++ index.html | 24 ++ 20 files changed, 2155 insertions(+) create mode 100644 01.06/index.html create mode 100644 02,07/.idea/.gitignore create mode 100644 02,07/.idea/misc.xml create mode 100644 02,07/.idea/modules.xml create mode 100644 02,07/.idea/project_markup.iml create mode 100644 02,07/css/normalize.css create mode 100644 02,07/css/style.css create mode 100644 02,07/getProducts.json create mode 100644 02,07/index.html create mode 100644 02,07/js/CartComponent.js create mode 100644 02,07/js/FilterComponent.js create mode 100644 02,07/js/ProductComponent.js create mode 100644 02,07/js/main.js create mode 100644 11.06/index.html create mode 100644 11.06/js.js create mode 100644 11.06/style.css create mode 100644 25.06/index.html create mode 100644 28.05/index.html create mode 100644 8.06/index.html create mode 100644 index.html diff --git a/01.06/index.html b/01.06/index.html new file mode 100644 index 0000000..fc7e5b0 --- /dev/null +++ b/01.06/index.html @@ -0,0 +1,96 @@ + + + + + + + + Document + + + + + + + + + + \ No newline at end of file diff --git a/02,07/.idea/.gitignore b/02,07/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/02,07/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/02,07/.idea/misc.xml b/02,07/.idea/misc.xml new file mode 100644 index 0000000..28a804d --- /dev/null +++ b/02,07/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/02,07/.idea/modules.xml b/02,07/.idea/modules.xml new file mode 100644 index 0000000..049f794 --- /dev/null +++ b/02,07/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/02,07/.idea/project_markup.iml b/02,07/.idea/project_markup.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/02,07/.idea/project_markup.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/02,07/css/normalize.css b/02,07/css/normalize.css new file mode 100644 index 0000000..192eb9c --- /dev/null +++ b/02,07/css/normalize.css @@ -0,0 +1,349 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers. + */ + +body { + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Remove the gray background on active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * 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; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove the border on images inside links in IE 10. + */ + +img { + border-style: none; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-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. + */ + +[type="search"]::-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 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + +/** + * Add the correct display in IE 10+. + */ + +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} diff --git a/02,07/css/style.css b/02,07/css/style.css new file mode 100644 index 0000000..801db80 --- /dev/null +++ b/02,07/css/style.css @@ -0,0 +1,171 @@ +body{ + font-family: 'SF Pro Display', sans-serif; +} +header{ + display: flex; + background-color: #2f2a2d; + justify-content: space-between; + color: #fafafa; + padding: 30px 80px; +} +button:focus{ + outline: none; +} +.logo{ + + text-transform: uppercase; + font-weight: bold; +} +.btn-cart{ + background-color: #fafafa; + padding: 10px 20px; + border: 1px solid transparent; + color: #2f2a2d; + border-radius: 5px; + transition: all ease-in-out .4s; + cursor: pointer; +} +.btn-cart:hover{ + background-color: transparent; + border-color: #fafafa; + color: #fafafa; +} +.btn-cart, .logo{ + align-self: center; +} +/*.products {*/ +/*display: flex;*/ +/*justify-content: space-between;*/ +/*flex-wrap: wrap;*/ +/*padding: 40px 80px;*/ +/*}*/ +.products{ + column-gap: 30px; + display: grid; + grid-template-columns: repeat(auto-fit, 200px); + grid-template-rows: 1fr; + padding: 40px 80px; + justify-content: space-between; +} +p { + margin: 0 0 5px 0; +} +.product-item{ + display: flex; + flex-direction: column; + width: 200px; + border-radius: 5px; + overflow: hidden; + margin: 20px 0; +} +img { + max-width: 100%; + height: auto +} +.desc { + border: 1px solid #c0c0c040; + padding: 15px +} +.cart{ + position: relative; +} +.cart-block{ + box-shadow: 0 0 5px rgba(0, 0, 0, 0.62); + border-radius: 5px; + box-sizing: border-box; + right: 0; + top: 130%; + position: absolute; + background-color: white; + padding: 20px; + color: black; + width: 300px; +} + +.invisible{ + display: none; +} +.cart-block:before{ + content: ''; + width: 0; + height: 0; + position: absolute; + top: -10px; + right: 35px; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-bottom: 10px solid white; +} + +.buy-btn, .del-btn{ + margin-top: 5px; + background-color: #2f2a2d; + padding: 5px 15px; + border: 1px solid transparent; + color: #fafafa; + border-radius: 5px; + transition: all ease-in-out .4s; + cursor: pointer; +} +.buy-btn:hover, .del-btn:hover{ + background-color: #fafafa; + color: #2f2a2d; + border: 1px solid #2f2a2d; +} +.cart-item { + display: flex; + justify-content: space-between; +} +.cart-item:not(:last-child){ + margin-bottom: 20px; +} +.product-bio{ + display: flex; +} +.cart-item img{ + align-self: flex-start; + margin-right: 15px; +} +.product-single-price{ + color: #474747; + font-size: 0.5em; +} +.product-price{ + margin-left: 30px; +} +.product-desc{ + max-width: 150px; +} +.product-quantity { + margin-top: 15px; + font-size: 0.75em; +} +.right-block{ + text-align: right; +} +.btn-search { + background-color: transparent; + border: none; + color: #fafafa; + font-size: 1.2em; + position: absolute; + bottom: 5px; + right: 0; +} +.search-form{ + position: relative; + margin-right: 50px; + display: inline-block; +} +.search-field:focus{ + outline: none; +} +.search-field { + box-sizing: border-box; + width: 200px; + color: #fafafa; + padding: 10px; + background-color: transparent; + border: none; + border-bottom: 2px solid #fafafa; +} diff --git a/02,07/getProducts.json b/02,07/getProducts.json new file mode 100644 index 0000000..f541d81 --- /dev/null +++ b/02,07/getProducts.json @@ -0,0 +1,12 @@ +[ + { + "id_product": 124, + "product_name": "Keyboard", + "price": 1600 + }, + { + "id_product": 457, + "product_name": "Gamepad", + "price": 3000 + } +] \ No newline at end of file diff --git a/02,07/index.html b/02,07/index.html new file mode 100644 index 0000000..39ec924 --- /dev/null +++ b/02,07/index.html @@ -0,0 +1,40 @@ + + + + + + Интернет-магазин + + + + + +
+
+ +
+ + + + + + + +
+ +
+
+ +
+
+ + + + + + + + + \ No newline at end of file diff --git a/02,07/js/CartComponent.js b/02,07/js/CartComponent.js new file mode 100644 index 0000000..8288004 --- /dev/null +++ b/02,07/js/CartComponent.js @@ -0,0 +1,29 @@ +Vue.component('cart', { + props: ['cartItems', 'img', 'visibility'], + template: ` +
+ + +
+ ` +}); + +Vue.component('cart-item', { + props: ['img', 'cartItem'], + template: ` +
+
+ Some img +
+
{{ cartItem.product_name }}
+
Quantity: {{ cartItem.quantity }}
+
$ {{ cartItem.price }} each
+
+
+
+
{{cartItem.quantity*cartItem.price}}
+ +
+
+ ` +}) \ No newline at end of file diff --git a/02,07/js/FilterComponent.js b/02,07/js/FilterComponent.js new file mode 100644 index 0000000..ca6df5b --- /dev/null +++ b/02,07/js/FilterComponent.js @@ -0,0 +1,9 @@ +Vue.component('filt', { + template: `
+ + +
` + +}); diff --git a/02,07/js/ProductComponent.js b/02,07/js/ProductComponent.js new file mode 100644 index 0000000..88b8f4f --- /dev/null +++ b/02,07/js/ProductComponent.js @@ -0,0 +1,22 @@ +Vue.component('products', { + props: ['products', 'img'], + template: `
+ +
` +}); +Vue.component('product', { + props: ['product', 'img'], + template: ` +
+ Some img +
+

{{product.product_name}}

+

{{product.price}}

+ +
+
+ ` +}) \ No newline at end of file diff --git a/02,07/js/main.js b/02,07/js/main.js new file mode 100644 index 0000000..65815be --- /dev/null +++ b/02,07/js/main.js @@ -0,0 +1,281 @@ +//Отправляйте свои данные с помощью $emit в верхний компонент, а вниз с помощью props +const API = 'https://raw.githubusercontent.com/GeekBrainsTutorial/online-store-api/master/responses'; + +const app = new Vue({ + el: '#app', + data: { + userSearch: '', + showCart: false, + catalogUrl: '/catalogData.json', + cartUrl: '/getBasket.json', + cartItems: [], + filtered: [], + imgCart: 'https://via.placeholder.com/50x100', + products: [], + imgProduct: 'https://via.placeholder.com/200x150', + error: false + }, + methods: { + getJson(url) { + return fetch(url) + .then(result => result.json()) + .catch(error => console.log(error)) + this.error = true; + }, + addProduct(item) { + this.getJson(`${API}/addToBasket.json`) + .then(data => { + if (data.result === 1) { + let find = this.cartItems.find(el => el.id_product === item.id_product); + if (find) { + find.quantity++; + } else { + const prod = Object.assign({ quantity: 1 }, item); + this.cartItems.push(prod) + } + } + }) + }, + remove(item) { + this.getJson(`${API}/addToBasket.json`) + .then(data => { + if (data.result === 1) { + if (item.quantity > 1) { + item.quantity--; + } else { + this.cartItems.splice(this.cartItems.indexOf(item), 1); + } + } + + }) + }, + filter() { + let regexp = new RegExp(this.userSearch, 'i'); + this.filtered = this.products.filter(el => regexp.test(el.product_name)); + } + }, + mounted() { + this.getJson(`${API + this.cartUrl}`) + .then(data => { + for (let item of data.contents) { + this.$data.cartItems.push(item); + } + }); + this.getJson(`${API + this.catalogUrl}`) + .then(data => { + for (let item of data) { + this.$data.products.push(item); + this.$data.filtered.push(item); + } + }); + /*this.getJson(`getProducts.json`) + .then(data => { + for(let item of data){ + this.$data.products.push(item); + this.$data.filtered.push(item); + } + })*/ + } + +}); + + +// class List { +// constructor(url, container){ +// this.container = container; +// this.url = url; +// this.goods = []; +// this.allProducts = []; +// this.filtered = []; +// this._init(); +// } +// getJson(url){ +// return fetch(url ? url : `${API + this.url}`) +// .then(result => result.json()) +// .catch(error => console.log(error)) +// } +// calcSum(){ +// return this.allProducts.reduce((accum, item) => accum += item.price, 0); +// } +// handleData(data){ +// this.goods = data; +// this.render(); +// } +// render(){ +// const block = document.querySelector(this.container); +// for (let product of this.goods){ +// const productObj = new list[this.constructor.name](product); +// this.allProducts.push(productObj); +// block.insertAdjacentHTML('beforeend', productObj.render()); +// } +// } +// filter(value){ +// const regexp = new RegExp(value, 'i'); +// this.filtered = this.allProducts.filter(product => regexp.test(product.product_name)); +// this.allProducts.forEach(el => { +// const block = document.querySelector(`.product-item[data-id="${el.id_product}"]`); +// if(!this.filtered.includes(el)){ +// block.classList.add('invisible'); +// } else { +// block.classList.remove('invisible'); +// } +// }) +// } +// _init(){ +// return false +// } +// } +// class Item { +// constructor(el, img = 'https://placehold.it/200x150'){ +// this.product_name = el.product_name; +// this.price = el.price; +// this.img = img; +// this.id_product = el.id_product +// } +// +// render(){ +// return `
+// Some img +//
+//

${this.product_name}

+//

${this.price} $

+// +//
+//
`; +// +// } +// } +// +// +// class ProductsList extends List { +// constructor(cart, url = '/catalogData.json',container = '.products'){ +// super(url, container); +// this.cart = cart; +// this.getJson() +// .then(data => this.handleData(data)); +// } +// _init(){ +// document.querySelector(this.container).addEventListener('click', e => { +// if(e.target.classList.contains('buy-btn')){ +// this.cart.addProduct(e.target); +// } +// }); +// document.querySelector('.search-form').addEventListener('submit', e => { +// e.preventDefault(); +// this.filter(document.querySelector('.search-field').value); +// }) +// } +// } +// +// class Product extends Item{} +// class Cart extends List{ +// constructor(url = '/getBasket.json', container = '.cart-block'){ +// super(url, container); +// this.getJson() +// .then(data => this.handleData(data.contents)); +// } +// addProduct(element){ +// this.getJson(`${API}/addToBasket.json`) +// .then(data => { +// if(data.result === 1){ +// let productId = +element.dataset['id']; +// let find = this.allProducts.find(product => product.id_product === productId); +// if(find){ +// find.quantity++; +// this._updateCart(find); +// } else { +// let product = { +// id_product: productId, +// price: +element.dataset['price'], +// product_name: element.dataset['name'], +// quantity: 1 +// }; +// this.goods = [product]; +// this.render(); +// } +// } else { +// alert('Error') +// } +// }) +// } +// removeProduct(element){ +// this.getJson(`${API}/deleteFromBasket.json`) +// .then(data => { +// if(data.result === 1){ +// let productId = +element.dataset['id']; +// let find = this.allProducts.find(product => product.id_product === productId); +// if(find.quantity > 1){ +// find.quantity--; +// this._updateCart(find); +// } else { +// this.allProducts.splice(this.allProducts.indexOf(find), 1); +// document.querySelector(`.cart-item[data-id="${productId}"]`).remove(); +// } +// } else { +// alert('Error') +// } +// }) +// } +// _updateCart(product){ +// const block = document.querySelector(`.cart-item[data-id="${product.id_product}"]`); +// block.querySelector(`.product-quantity`).textContent = `Quantity: ${product.quantity}`; +// block.querySelector(`.product-price`).textContent = `$${product.quantity*product.price}`; +// } +// _init(){ +// document.querySelector(this.container).addEventListener('click', e => { +// if(e.target.classList.contains('del-btn')){ +// this.removeProduct(e.target); +// } +// }); +// document.querySelector('.btn-cart').addEventListener('click', () => { +// document.querySelector(this.container).classList.toggle('invisible') +// }) +// } +// } +// +// class CartItem extends Item{ +// constructor(el, img = 'https://placehold.it/50x100'){ +// super(el, img); +// this.quantity = el.quantity; +// } +// render(){ +// return `
+//
+// Some image +//
+//

${this.product_name}

+//

Quantity: ${this.quantity}

+//

$${this.price} each

+//
+//
+//
+//

${this.quantity*this.price}

+// +//
+//
` +// } +// } +// +// const list = { +// ProductsList: Product, +// Cart: CartItem +// }; +// +// +// const cart = new Cart(); +// const products = new ProductsList(cart); +// setTimeout(() => { +// products.getJson(`getProducts.json`).then(data => products.handleData(data)); +// }, 300); + +// list.getProducts(() => { +// list.render(); +// }); + + diff --git a/11.06/index.html b/11.06/index.html new file mode 100644 index 0000000..a6b399a --- /dev/null +++ b/11.06/index.html @@ -0,0 +1,20 @@ + + + + + Змейка + + + +
Счет игры: 0
+
+
+
+ + + + + \ No newline at end of file diff --git a/11.06/js.js b/11.06/js.js new file mode 100644 index 0000000..1be9450 --- /dev/null +++ b/11.06/js.js @@ -0,0 +1,742 @@ +"use strict"; + +/** + * Объект DTO с настройками игры по умолчанию, которые можно будет поменять при инициализации игры. + * @property {int} rowsCount Количество строк. + * @property {int} colsCount Количество колонок. + * @property {int} speed Скорость змейки. + * @property {int} winLength Длина змейки для победы. + */ +const settings = { + rowsCount: 21, + colsCount: 21, + speed: 2, + winFoodCount: 50, +}; + +/** + * Объект конфига игры, содержащий методы получения настроек и проверки этих настроек. + * @property {settings} settings Настройки игры. + */ +const config = { + settings, + + /** + * Инициализация настроек игры. + * @param {Object} userSettings Объект с пользовательскими настройками игры. + */ + init(userSettings) { + // Записываем переданные настройки в те, которые будут использоваться. + Object.assign(this.settings, userSettings); + }, + + /** + * @returns {int} Отдает количество строк в игре. + */ + getRowsCount() { + return this.settings.rowsCount; + }, + + /** + * @returns {int} Отдает количество колонок в игре. + */ + getColsCount() { + return this.settings.colsCount; + }, + + /** + * @returns {int} Отдает скорость змейки в игре. + */ + getSpeed() { + return this.settings.speed; + }, + + /** + * @returns {int} Отдает количество еды, которое надо съесть для победы. + */ + getWinFoodCount() { + return this.settings.winFoodCount; + }, + + /** + * Проверка значений настроек игры. + * @returns {{isValid: boolean, errors: Array}} Результат валидации в виде объекта с ошибками. + */ + validate() { + /** + * Объект DTO с результатами валидации. + * @property {boolean} isValid true, если настройки валидны, иначе false. + * @property {string[]} errors массив со всеми ошибками настроек. + */ + const result = { + isValid: true, + errors: [], + }; + + if (this.settings.rowsCount < 10 || this.settings.rowsCount > 30) { + result.isValid = false; + result.errors.push('Неверные настройки, значение rowsCount должно быть в диапазоне [10, 30].'); + } + + if (this.settings.colsCount < 10 || this.settings.colsCount > 30) { + result.isValid = false; + result.errors.push('Неверные настройки, значение colsCount должно быть в диапазоне [10, 30].'); + } + + if (this.settings.speed < 1 || this.settings.speed > 10) { + result.isValid = false; + result.errors.push('Неверные настройки, значение speed должно быть в диапазоне [1, 10].'); + } + + if (this.settings.winFoodCount < 5 || this.settings.winFoodCount > 50) { + result.isValid = false; + result.errors.push('Неверные настройки, значение winLength должно быть в диапазоне [5, 50].'); + } + + return result; + }, +}; + +/** + * Объект карты с методами отображения и создания игрового поля. + * @property {Object} cells Объект содержащий все ячейки игры. + * @property {Array} usedCells Массив содержащий все занятые ячейки игры. + */ +const map = { + cells: null, + usedCells: null, + + /** + * Метод инициализирует и выводит карту для игры. + * @param {int} rowsCount Количество строк в карте. + * @param {int} colsCount Количество колонок в карте. + */ + init(rowsCount, colsCount) { + // Контейнер, где будут наши ячейки, первоначально его очистим. + const table = document.getElementById('game'); + table.innerHTML = ""; + // Объект-хранилище всех клеток пока пустой. + this.cells = {}; + // Массив со всеми занятыми элементами на карте пока тоже пустой. + this.usedCells = []; + // Цикл запустится столько раз, сколько у нас количество строк. + for (let row = 0; row < rowsCount; row++) { + // Создаем строку, добавляем ей класс, после добавляем ее в таблицу. + const tr = document.createElement('tr'); + tr.classList.add('row'); + table.appendChild(tr); + // Цикл запустится столько раз, сколько у нас количество колонок. + for (let col = 0; col < colsCount; col++) { + // Создаем ячейку, добавляем ячейке класс cell. + const td = document.createElement('td'); + td.classList.add('cell'); + // Записываем в объект всех ячеек новую ячейку. + this.cells[`x${col.toString()}_y${row.toString()}`] = td; + // Добавляем ячейку в строку. + tr.appendChild(td); + } + } + }, + + /** + * Отображает все объекты на карте. + * @param {{x: int, y: int}[]} snakePointsArray Массив с точками змейки. + * @param {{x: int, y: int}} foodPoint Точка еды. + * @param {{x: int, y: int}} blockPoint Точка препятствия + * @see {@link https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach|Array.prototype.forEach()} + */ + render(snakePointsArray, foodPoint, blockPoint) { + // Чистим карту от предыдущего рендера, всем занятым ячейкам оставляем только класс cell. + for (const cell of this.usedCells) { + cell.className = 'cell'; + } + // Очищаем массив с занятыми ячейками, при отображении сейчас его соберем заново. + this.usedCells = []; + // Отображаем змейку. + snakePointsArray.forEach((point, idx) => { + // Получаем элемент ячейки змейки по точке point. + const snakeCell = this.cells[`x${point.x}_y${point.y}`]; + // Если первый элемент массива, значит это голова, иначе тело. + snakeCell.classList.add(idx === 0 ? 'snakeHead' : 'snakeBody'); + // Добавляем элемент ячейки змейки в массив занятых точек на карте. + this.usedCells.push(snakeCell); + }); + // Получаем элемент ячейки с едой по точке foodPoint. + const foodCell = this.cells[`x${foodPoint.x}_y${foodPoint.y}`]; + // Отображаем еду. + foodCell.classList.add('food'); + // Добавляем элемент ячейки еды в массив занятых точек на карте. + this.usedCells.push(foodCell); + // Получаем элемент ячейки препятствия по точке blockPoint. + const blockCell = this.cells[`x${blockPoint.x}_y${blockPoint.y}`]; + // Отображаем препятствие. + blockCell.classList.add('block'); + // Добавляем элемент ячейки препятствия в массив занятых точек на карте. + this.usedCells.push(blockCell); + }, +}; + +/** + * Объект змейки. + * @property {{x: int, y: int}[]} body Массив с точками тела змейки. + * @property {string} direction Направление, куда пользователь направил змейку. + * @property {string} lastStepDirection Направление, куда сходила змейка прошлый раз. + * @property {int} maxX Максимальная позиция змейки на карте по оси X. + * @property {int} maxY Максимальная позиция змейки на карте по оси Y. + */ +const snake = { + body: null, + direction: null, + lastStepDirection: null, + maxX: null, + maxY: null, + + /** + * Инициализирует змейку, откуда она будет начинать и ее направление. + * @param {{x: int, y: int}[]} startBody Начальная позиция змейки. + * @param {string} direction Начальное направление игрока. + * @param {int} maxX Максимальная позиция змейки по оси X. + * @param {int} maxY Максимальная позиция змейки по оси Y. + */ + init(startBody, direction, maxX, maxY) { + this.body = startBody; + this.direction = direction; + this.lastStepDirection = direction; + this.maxX = maxX; + this.maxY = maxY; + }, + + /** + * Отдает массив со всеми точками змейки. + * @return {{x: int, y: int}[]}; + */ + getBody() { + return this.body; + }, + + /** + * Отдает прошлое направление змейки. + */ + getLastStepDirection() { + return this.lastStepDirection; + }, + + /** + * Проверяет содержит ли змейка переданную точку. + * @see {@link https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/some|Array.prototype.some()} + * @param {{x: int, y: int}} point Точка, которую проверяем. + * @returns {boolean} true, если змейка содержит переданную точку, иначе false. + */ + isOnPoint(point) { + return this.body.some(snakePoint => snakePoint.x === point.x && snakePoint.y === point.y); + }, + + /** + * Двигает змейку на один шаг. + */ + makeStep() { + // Записываем направление движения, которое сейчас произойдет как направление прошлого шага. + this.lastStepDirection = this.direction; + // Вставляем следующую точку в начало массива. + this.body.unshift(this.getNextStepHeadPoint()); + // Удаляем последний лишний элемент. + this.body.pop(); + }, + + /** + * Добавляет в конец тела змейки копию последнего элемента змейки. + */ + growUp() { + // Получаем индекс последней точки в массиве точек змейки (последний элемент this.body). + const lastBodyIdx = this.body.length - 1; + // Получаем последнюю точку змейки. + const lastBodyPoint = this.body[lastBodyIdx]; + // Клонируем последнюю точку змейки (делаем копию). + const lastBodyPointClone = Object.assign({}, lastBodyPoint); + // Добавляем копию в наш массив this.body. + this.body.push(lastBodyPointClone); + }, + + /** + * Отдает точку, где будет голова змейки если она сделает шаг. + * @returns {{x: int, y: int}} Следующая точка куда придет змейка сделав шаг. + */ + getNextStepHeadPoint() { + // Получаем в отдельную переменную голову змейки. + const firstPoint = this.body[0]; + // Возвращаем точку, где окажется голова змейки в зависимости от направления. + switch (this.direction) { + case 'up': + return { x: firstPoint.x, y: firstPoint.y !== 0 ? firstPoint.y - 1 : this.maxY }; + case 'right': + return { x: firstPoint.x !== this.maxX ? firstPoint.x + 1 : 0, y: firstPoint.y }; + case 'down': + return { x: firstPoint.x, y: firstPoint.y !== this.maxY ? firstPoint.y + 1 : 0 }; + case 'left': + return { x: firstPoint.x !== 0 ? firstPoint.x - 1 : this.maxX, y: firstPoint.y }; + } + }, + + /** + * Устанавливает направление змейки. + * @param {string} direction Направление змейки. + */ + setDirection(direction) { + this.direction = direction; + }, +}; + +/** + * Объект еды. + * @property {int} x Координата X еды. + * @property {int} y Координата Y еды. + */ +const food = { + x: null, + y: null, + + /** + * Отдает координаты еды. + * @returns {{x: int, y: int}} Координаты еды. + */ + getCoordinates() { + return { + x: this.x, + y: this.y, + } + }, + + /** + * Устанавливает координаты для еды. + * @param {{x: int, y: int}} point Новая точка с координатами еды. + */ + setCoordinates(point) { + this.x = point.x; + this.y = point.y; + }, + + /** + * Определяет соответствует ли точка на которой находится еда той точке что была передана. + * @param {{x: int, y: int}} point Точка, для проверки соответствия точке еды. + * @returns {boolean} true, если точки совпали, иначе false. + */ + isOnPoint(point) { + return this.x === point.x && this.y === point.y; + }, +}; + +/** + * Объект препятствия. + * @property {int} x Координата X препятствия. + * @property {int} y Координата Y препятствия. + */ +const block = { + x: null, + y: null, + + /** + * Отдает координаты препятствия. + * @returns {{x: int, y: int}} Координаты препятствия. + */ + getCoordinates() { + return { + x: this.x, + y: this.y, + } + }, + + /** + * Устанавливает координаты для препятствия. + * @param {{x: int, y: int}} point Новая точка с координатами препятствия. + */ + setCoordinates(point) { + this.x = point.x; + this.y = point.y; + }, + + /** + * Определяет соответствует ли точка на которой находится препятствие той точке что была передана. + * @param {{x: int, y: int}} point Точка, для проверки соответствия точке препятствия. + * @returns {boolean} true, если точки совпали, иначе false. + */ + isOnPoint(point) { + return this.x === point.x && this.y === point.y; + }, +}; + + +/** + * Статус игры. + * @property {string} condition Статус игры. + */ +const status = { + condition: null, + + /** + * Устанавливает статус в "playing". + */ + setPlaying() { + this.condition = 'playing'; + }, + + /** + * Устанавливает статус в "stopped". + */ + setStopped() { + this.condition = 'stopped'; + }, + + /** + * Устанавливает статус в "finished". + */ + setFinished() { + this.condition = 'finished'; + }, + + /** + * Проверяет является ли статус "playing". + * @returns {boolean} true, если статус "playing", иначе false. + */ + isPlaying() { + return this.condition === 'playing'; + }, + + /** + * Проверяет является ли статус "stopped". + * @returns {boolean} true, если статус "stopped", иначе false. + */ + isStopped() { + return this.condition === 'stopped'; + }, +}; + +/** + * Объект счетчика. Подсчитывает очки пользователя. + * @property {int} count Очки пользователя. + * @property {HTMLElement} countEl DOM-элемент для вставки числа отображающего количество очков пользователя. + */ +const score = { + count: null, + countEl: null, + + /** + * Инициализацирует счетчик. + */ + init() { + this.countEl = document.getElementById('score-count'); + this.drop(); + }, + + /** + * Инкрементирует счетчик. + */ + increment() { + this.count++; + this.render(); + }, + + /** + * Сбрасывает счетчик. + */ + drop() { + this.count = 0; + this.render(); + }, + + /** + * Отображает количество очков пользователю. + */ + render() { + this.countEl.textContent = this.count; + } +}; + +/** + * Объект игры. + * @property {settings} settings Настройки игры. + * @property {map} map Объект отображения. + * @property {snake} snake Объект змейки. + * @property {food} food Объект еды. + * @property {block} block Объект препятствия. + * @property {status} status Статус игры. + * @property {score} score Счетчик игры. + * @property {int} tickInterval Номер интервала игры. + */ +const game = { + config, + map, + snake, + food, + block, + status, + score, + tickInterval: null, + + /** + * Инициализация игры. + * @param {object} userSettings Настройки игры, которые можно изменить. + */ + init(userSettings) { + // Инициализируем настройки игры. + this.config.init(userSettings); + // Получаем результат валидации настроек. + const validation = this.config.validate(); + // Если настройки игры неверные - сообщаем об ошибках и выходим из метода, игру не запускаем. + if (!validation.isValid) { + for (const err of validation.errors) { + console.error(err); + } + return; + } + // Инициализируем карту. + this.map.init(this.config.getRowsCount(), this.config.getColsCount()); + // Инициализируем счетчик. + this.score.init(); + // Устанавливаем обработчики событий. + this.setEventHandlers(); + // Ставим игру в начальное положение. + this.reset(); + }, + + /** + * Ставит игру в начальное положение. + */ + reset() { + // Ставим статус игры в "остановлена". + this.stop(); + // Сбрасываем счетчик. + this.score.drop(); + // Инициализируем змейку. + this.snake.init(this.getStartSnakeBody(), 'up', this.config.getColsCount() - 1, this.config.getRowsCount() - 1); + // Ставим еду на карту в случайную пустую ячейку. + this.food.setCoordinates(this.getRandomFreeCoordinates()); + // Ставим епрепятствие на карту в случайную пустую ячейку. + this.block.setCoordinates(this.getRandomFreeCoordinates()); + // Отображаем все что нужно для игры. + this.render(); + }, + + /** + * Ставим статус игры в "играем". + */ + play() { + // Ставим статус в 'playing'. + this.status.setPlaying(); + // Ставим интервал шагов змейки. + this.tickInterval = setInterval(() => this.tickHandler(), 1000 / this.config.getSpeed()); + // Меняем название кнопки в меню на "Стоп" и делаем ее активной. + this.setPlayButton('Стоп'); + }, + + /** + * Ставим статус игры в "стоп". + */ + stop() { + // Ставим статус в 'stopped'. + this.status.setStopped(); + // Убираем интервал шагов змейки. + clearInterval(this.tickInterval); + // Меняем название кнопки в меню на "Старт" и делаем ее активной. + this.setPlayButton('Старт'); + }, + + /** + * Ставим статус игры в "финиш". + */ + finish() { + // Ставим статус в 'finished'. + this.status.setFinished(); + // Убираем интервал шагов змейки. + clearInterval(this.tickInterval); + // Меняем название кнопки в меню на "Игра закончена" и делаем ее неактивной. + this.setPlayButton('Игра закончена', true); + }, + + /** + * Обработчик события тика игры, когда змейка должна перемещаться. + */ + tickHandler() { + // Если следующий шаг невозможен, то ставим игру в статус завершенный. + if (!this.canMakeStep()) { + return this.finish(); + } + // Если следующий шаг будет на еду, то заходим в if. + if (this.food.isOnPoint(this.snake.getNextStepHeadPoint())) { + // Прибавляем к змейке ячейку. + this.snake.growUp(); + // Инкрементируем счетчик. + this.score.increment(); + // Ставим еду в свободную ячейку. + this.food.setCoordinates(this.getRandomFreeCoordinates()); + // Если выиграли, завершаем игру. + if (this.isGameWon()) { + this.finish(); + } + } + // Перемещаем змейку. + this.snake.makeStep(); + // Отрисовываем что получилось после шага. + this.render(); + }, + + /** + * Меняем кнопку с классом playButton. + * @param {string} textContent Текст кнопки. + * @param {boolean} [isDisabled = false] Необходимо ли заблокировать кнопку. + */ + setPlayButton(textContent, isDisabled = false) { + // Находим кнопку. + const playButton = document.getElementById('playButton'); + // Меняем текст внутри кнопки на переданный. + playButton.textContent = textContent; + // Если необходимо запретить нажатие кнопку - ставим класс disabled, иначе убираем класс disabled. + isDisabled ? playButton.classList.add('disabled') : playButton.classList.remove('disabled'); + }, + + /** + * Возвращает начальную позицию змейки в центре карты. + * @returns {{x: int, y: int}[]} Точка начальной позиции змейки. + */ + getStartSnakeBody() { + return [{ + x: Math.floor(this.config.getColsCount() / 2), + y: Math.floor(this.config.getRowsCount() / 2) + }]; + }, + + /** + * Ставит обработчики события. + */ + setEventHandlers() { + // При клике на кнопку с классом playButton вызвать функцию this.playClickHandler. + document.getElementById('playButton').addEventListener('click', () => this.playClickHandler()); + // При клике на кнопку с классом newGameButton вызвать функцию this.newGameClickHandler. + document.getElementById('newGameButton').addEventListener('click', event => this.newGameClickHandler(event)); + // При нажатии кнопки, если статус игры "играем", то вызываем функцию смены направления у змейки. + document.addEventListener('keydown', event => this.keyDownHandler(event)); + }, + + /** + * Отображает все для игры, карту, еду и змейку. + */ + render() { + this.map.render(this.snake.getBody(), this.food.getCoordinates(), this.block.getCoordinates()); + }, + + /** + * Отдает случайную не занятую точку на карте. + * @return {{x: int, y: int}} Точку с координатами. + */ + getRandomFreeCoordinates() { + // Занятые точки на карте. + const exclude = [this.food.getCoordinates(), this.block.getCoordinates(), ...this.snake.getBody()]; + // Пытаемся получить точку ничем не занятую на карте. + while (true) { + // Случайно сгенерированная точка. + const rndPoint = { + x: Math.floor(Math.random() * this.config.getColsCount()), + y: Math.floor(Math.random() * this.config.getRowsCount()), + }; + // Если точка ничем не занята, то возвращаем ее из функции. + if (!exclude.some(exPoint => rndPoint.x === exPoint.x && rndPoint.y === exPoint.y)) { + return rndPoint; + } + } + }, + + /** + * Обработчик события нажатия на кнопку playButton. + */ + playClickHandler() { + // Если сейчас статус игры "играем", то игру останавливаем, если игра остановлена, то запускаем. + if (this.status.isPlaying()) { + this.stop(); + } else if (this.status.isStopped()) { + this.play(); + } + }, + + /** + * Обработчик события нажатия на кнопку "Новая игра". + */ + newGameClickHandler() { + // Ставим игру в начальное положение. + this.reset(); + }, + + /** + * Обработчик события нажатия кнопки клавиатуры. + * @param {KeyboardEvent} event + */ + keyDownHandler(event) { + // Если статус игры не "играем", значит обрабатывать ничего не нужно. + if (!this.status.isPlaying()) { + return; + } + // Получаем направление змейки, больше мы не обрабатываем других нажатий. + const direction = this.getDirectionByCode(event.code); + // Змейка не может повернуть на 180 градусов, поэтому делаем проверку, можем ли мы назначить направление. + if (this.canSetDirection(direction)) { + this.snake.setDirection(direction); + } + }, + + /** + * Отдает направление змейки в зависимости от переданного кода нажатой клавиши. + * @param {string} code Код нажатой клавиши. + * @returns {string} Направление змейки. + */ + getDirectionByCode(code) { + switch (code) { + case 'KeyW': + case 'ArrowUp': + return 'up'; + case 'KeyD': + case 'ArrowRight': + return 'right'; + case 'KeyS': + case 'ArrowDown': + return 'down'; + case 'KeyA': + case 'ArrowLeft': + return 'left'; + default: + return ''; + } + }, + + /** + * Определяет можно ли назначить переданное направление змейке. + * @param {string} direction Направление, которое проверяем. + * @returns {boolean} true, если направление можно назначить змейке, иначе false. + */ + canSetDirection(direction) { + // Получаем прошлое направление змейки. + const lastStepDirection = this.snake.getLastStepDirection(); + // Если прошлое направление змейки не противоположное от direction, возвращаем true. + return direction === 'up' && lastStepDirection !== 'down' || + direction === 'right' && lastStepDirection !== 'left' || + direction === 'down' && lastStepDirection !== 'up' || + direction === 'left' && lastStepDirection !== 'right'; + }, + + /** + * Проверяем произошла ли победа, судим по очкам игрока (длине змейки). + * @returns {boolean} true, если игрок выиграл игру, иначе false. + */ + isGameWon() { + return this.snake.getBody().length > this.config.getWinFoodCount(); + }, + + /** + * Проверяет возможен ли следующий шаг. + * @returns {boolean} true если следующий шаг змейки возможен, false если шаг не может быть совершен. + */ + canMakeStep() { + // Змейка может сделать шаг если следующая точка не на теле змейки. + return !this.snake.isOnPoint(this.snake.getNextStepHeadPoint()) && !this.block.isOnPoint(this.snake.getNextStepHeadPoint()); + }, +}; + +// При загрузке страницы инициализируем игру. +window.onload = game.init({ speed: 5 }); diff --git a/11.06/style.css b/11.06/style.css new file mode 100644 index 0000000..89c78c1 --- /dev/null +++ b/11.06/style.css @@ -0,0 +1,56 @@ +*{ + margin: 0; + padding: 0; + background: #2e353d; +} +.menuButton{ + width: 150px; + padding: 15px; + margin: 10px; + color: white; + background: #007b32; + text-align: center; + cursor: pointer; +} +.menuButton:hover{ + background: #009641; +} +#game{ + border-collapse: collapse; + margin: 2% auto 0; +} +#menu{ + display: flex; + justify-content: center; +} +td.cell{ + width: 20px; + height: 20px; + border: 1px dotted #eee; + border-collapse: collapse; +} +.food{ + background: #00d021; +} +.block{ + background: hsl(207, 69%, 47%); +} +.snakeBody{ + background: #e5e5e5; +} +.snakeHead{ + background: #9b0013; +} +.disabled { + background: #7c7c7c; +} +.disabled:hover{ + background: #7c7c7c; +} +#game-score { + margin-top: 1%; + text-align: center; + font-family: "Lobster", serif; + font-size: 2em; + color: #d40019; +} \ No newline at end of file diff --git a/25.06/index.html b/25.06/index.html new file mode 100644 index 0000000..cda4d28 --- /dev/null +++ b/25.06/index.html @@ -0,0 +1,46 @@ + + + + + + + + Document + + + +
+

+ One: 'Hi Mary.' Two: 'Oh, hi.' + One: 'How are you doing?' + Two: 'I'm doing alright. How about you?' + One: 'Not too bad. The weather is great isn't it?' + Two: 'Yes. It's absolutely beautiful today.' + One: 'I wish it was like this more frequently.' + Two: 'Me too.' + One: 'So where are you going now?' + Two: 'I'm going to meet a friend of mine at the department store.' + One: 'Going to do a little shopping?' + Two: 'Yeah, I have to buy some presents for my parents.' + One: 'What's the occasion?' + Two: 'It's their anniversary.' + One: 'That's great. Well, you better get going. You don't want to be late.' + Two: 'I'll see you next time.' + One: 'Sure. Bye.' +

+ +
+ + + + + + \ No newline at end of file diff --git a/28.05/index.html b/28.05/index.html new file mode 100644 index 0000000..9ecc640 --- /dev/null +++ b/28.05/index.html @@ -0,0 +1,76 @@ + + + + + + + + Document + + + + + + + \ No newline at end of file diff --git a/8.06/index.html b/8.06/index.html new file mode 100644 index 0000000..8c4671b --- /dev/null +++ b/8.06/index.html @@ -0,0 +1,152 @@ + + + + + + + + Catalog + + + + +
+

Каталог

+
+
+

Корзина

+
+ + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..0e5b580 --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + + + + Document + + + + + + + + \ No newline at end of file