С чего начать написании игры 2048 на JavaScript?
Решился написать игру 2048, и все бы хорошо, но не знаю с чего начать.
Я напишу с чего бы начал я, а вы напишите с чего начали бы вы:
var data = []; //масив для хранения данных data = [. ] //поле 4x4
Хм, нужны будут методы, тогда:
var twentyFortyeight = <>; //создаем объект twentyFortyeight.data = [. ]; //поле 4x4 twentyFortyeight.ready = function() < //twentyFortyeight.data[0] = 2; //twentyFortyeight.data[1] = 2; //for()>; twentyFortyeight.ready(); // 2 2 0 0 // 0 0 0 0 // 0 0 0 0 // 0 0 0 0 twentyFortyeight.up = function() < // >twentyFortyeight.left = function() < if(twentyFortyeight.data[3]==undefined || twentyFortyeight.data[3]===twentyFortyeight.data[3-1])<>//data[1] равен data[0], значит складываем, помещаем сумму в data[0], удаляем data[1] //проверка на равенство каждого 4 элемента массива на равенство с предедущим >
Напишите что по вашему мнению действительно ускорит разработку игры. К примеру использования DDT или любых других проффесиональных приспособлений. Как видете я начал со статичной разбора алгоритма игры, но в дальнейшем нужно все прийти к динамической адресации в массивах.
- Вопрос задан более трёх лет назад
- 6826 просмотров
1 комментарий
Средний 1 комментарий
gabrielecirulli.github.io/2048 попробуйте исходники посмотреть.
Решения вопроса 0
Ответы на вопрос 5
ManWithBear @ManWithBear
Swift Adept, Prague
Всю игру оборачиваете в отдельную сущность.
Геттеры: кол-во размерностей игрового поля, размер соответствующей размерности, текущее состояние поля.
Методы: создание новой игры(массив размерностей), применить вектор движения(вектор)
Это всё что вам нужно. В итоге вы получаете не унылую игру в поле 4на4, а абстракцию над ней с возможностью игры в любом кол-ве размерностей.
UPD Ещё наверняка понадобится геттер состояния игры (игра продолжается или игра закончена)
UPD2 Получился банальный пример ответа на вопрос «Зачем линейка/математика программисту» 😀
Ответ написан более трёх лет назад
Комментировать
Нравится 4 Комментировать
Веб-разработчик
Можно глянуть как сам автор сделал, код довольно понятный, да и комментарии присутствуют.
Конкретно по вопросу: создать объект игрового поля (сетки), описать события для управления в игре, сделать игровые состояния (победа, проигрыш, в процессе). Потом можно уже колдовать над функцией добавления новой ячейки в поле, которая размещается в любую свободную клетку (например, на подходах к 2048 увеличить вероятность появления новой ячейки к самым крупным и т.п.).
Ответ написан более трёх лет назад
Комментировать
Нравится 3 Комментировать
Можно начать с прохождения курса на Udacity, который посвящен разработке 2048 на JavaScript
Ответ написан более трёх лет назад
Комментировать
Нравится 1 Комментировать
Нужно не страдать фигней в ide, а взять лист бумаги.
Шаблон приложения уже есть, отлично. Дальше нарисовать диаграмму, как это все между собой взаимодействует (flow diagram, например).
Составить план (диаграмма Ганта вполне подойдет) и двигаться по нему.
На первый взгляд эти действия покажутся лишними. Поверьте, когда вы завершите, всплывет много подводных камней.
Ответ написан более трёх лет назад
Нравится 1 1 комментарий
ManWithBear @ManWithBear
korotkin за диаграмму Ганта спасибо.
Как то ради интересна на яваскрипте сделал 2048, тонкости не помню, но принцип был такой:
data — двумерный массив 4х4, где хранились все «кубики»
blocked — такой же массив 4х4 для пометки секторов, где в этот момент уже было сложение кубиков
Далее при нажатии пробегаюсь по всем кубикам начиная от края ближнего к той стороне, куда сдвигаем (т.е. если нажали «вниз», то просмотриваю все столбцы по кубикам снизу вверх).
При этом
1.1) Если есть пустота — сдвигаю,
1.2) Если нет пустоты — проверка какой именно кубик
1.2.1) Если кубик != нашему — оставляем все на месте
1.2.2) Если это такой же кубик, то проверяем массив blocked на предмет того, что этот кубик мы получили путем сложения, или он и был до этого таким
1.2.2.1) Если кубик и был таким — наш удваиваем, перемещаем (анимация), а тот удаляем, меняем данные в data + ставим блок в массив blocked
1.2.2.2) Если кубик уже умноженный на этом ходе — оставляем все как есть
Собственно вся логика, ну и проверка на проигрыш по принципу «если некуда ходить — лузер» и на выигрыш при получении блока 2048.
Если интересно самому покопаться, добиться — дерзайте, ничего сложного нету, главное — построить логику и она не обязательна будет схожа с тем, что я описал. Если интересно посмотреть готовый вариант — могу скинуть линк на свой вариант, но за чистоту кода не отвечаю, написан за один вечер)
Оптимизировать метод обсчета поля игры 2048
Логически имеется игровое поле 4×4 тайла. Задача: при выполнении игроком хода нужно пройти по всему полю и двинуть тайлы в нужном направлении попутно смержив те, что имеют одинаковые значения. Этот код работает, но выглядит ужасно. Он реализует только логику перемещения и мержа тайлов используя только массив тайлов. Технически DOM обновляется в другом методе, фактический мерж тайлов тоже происходит позднее, здесь ставится только метка на тайл, что он будет смержен. Удаляемому тайлу просто задается значение 0.
const size = 4; function move(direction) < let moved = false; if (direction === 'left') < for (let col = 1; col < size; col++) < for (let row = 0; row < size; row++) < const cell = getCell(row, col); if (cell && !cell.merged) < let targetCol; for (targetCol = col - 1; targetCol >= 0; targetCol--) < const targetCell = getCell(row, targetCol); if (!targetCell) continue; if (targetCell.value == cell.value && !targetCell.merged) < cell.value = 0; targetCell.merged = true; >break; > if (cell.value != 0) targetCol++; if (col != targetCol) < moveCol(cell, targetCol); moved = true; >> > > > if (direction === 'right') < for (let col = size - 2; col >= 0; col--) < for (let row = 0; row < size; row++) < const cell = getCell(row, col); if (cell && !cell.merged) < let targetCol; for (targetCol = col + 1; targetCol < size; targetCol++) < const targetCell = getCell(row, targetCol); if (!targetCell) continue; if (targetCell.value == cell.value && !targetCell.merged) < cell.value = 0; targetCell.merged = true; >break; > if (cell.value != 0) targetCol--; if (col != targetCol) < moveCol(cell, targetCol); moved = true; >> > > > if (direction === 'up') < for (let row = 1; row < size; row++) < for (let col = 0; col < size; col++) < const cell = getCell(row, col); if (cell && !cell.merged) < let targetRow; for (targetRow = row - 1; targetRow >= 0; targetRow--) < const targetCell = getCell(targetRow, col); if (!targetCell) continue; if (targetCell.value == cell.value && !targetCell.merged) < cell.value = 0; targetCell.merged = true; >break; > if (cell.value != 0) targetRow++; if (row != targetRow) < moveRow(cell, targetRow); moved = true; >> > > > if (direction === 'down') < for (let row = size - 2; row >= 0; row--) < for (let col = 0; col < size; col++) < const cell = getCell(row, col); if (cell && !cell.merged) < let targetRow; for (targetRow = row + 1; targetRow < size; targetRow++) < const targetCell = getCell(targetRow, col); if (!targetCell) continue; if (targetCell.value == cell.value && !targetCell.merged) < cell.value = 0; targetCell.merged = true; >break; > if (cell.value != 0) targetRow--; if (row != targetRow) < moveRow(cell, targetRow); moved = true; >> > > > return moved; >
- просматриваю строку по колонкам от 0, то есть слева направо
- если нахожу тайл, проверяю, есть ли тайл левее
- если есть, проверяю его значение
- если совпадает и тайл не был замержен ранее, то мержу
- двигаю тайл в соответствии с выше выполненным условием, то есть либо в мерж, либо в дальнюю свободную клетку слева, если она есть
- перехожу к следующей строке
Вызываемые методы из кода выше, для референса
const cells = []; const cellSize = 100 / size; function moveRow(cell, row) < cell.row = row; cell.node.style.top = (row * cellSize) + '%'; >function moveCol(cell, col) < cell.col = col; cell.node.style.left = (col * cellSize) + '%'; >function getCell(row, col) < for (let i = 0; i < cells.length; i++) < const cell = cells[i]; if (cell.row == row && cell.col == col && cell.value != 0) return cell; >>
Отслеживать
задан 28 сен 2021 в 8:15
50k 6 6 золотых знаков 25 25 серебряных знаков 60 60 бронзовых знаков
добавь описание алгоритма.
28 сен 2021 в 8:29
@Grundy исправлено
28 сен 2021 в 8:35
просматриваю строку по колонкам от 0 до 3 — а всего сколько колонок?
28 сен 2021 в 8:36
@Grundy механика точь в точь как в оригинале, ссылка на оригинал выше, потыкать кнопки минуту и все понятно станет. Сложно описать это с нуля именно текстом.
28 сен 2021 в 8:43
просматриваю строку по колонкам от 0, то есть слева направо Почему не от 1? нулевой по дефолту не с чем мержить и некуда двигать — край доски. если нахожу тайл, проверяю, есть ли тайл левее На самом деле тебе надо обработать три взаимоисключающие ситуации. 1) IF слева пусто, действие node(i-1)=node(i);node(i)=0; 2) ELSEIF слева непусто, и node(i-1)=node(i) , действие node(i-1)=2*node(i);node(i)=0; 3) ELSE — ничего не делать, перейти к следующему node.
8 дек 2021 в 11:06
5 ответов 5
Сортировка: Сброс на вариант по умолчанию
let cvalue = [[0,2,16,2],[0,0,8,0],[4,0,2,2],[4,8,16,0]]; let placeHolder = document.getElementById("main"); for(let i =0; i function update() < for(let i =0; i> document.getElementById("left").addEventListener("click",()=>)>); document.getElementById("right").addEventListener("click",()=>)>); document.getElementById("up").addEventListener("click",()=>)>); document.getElementById("down").addEventListener("click",()=>)>); function move(direction) < //в зависимости от нажатой кнопки передаем направление движения //по осям и какой ряд вертикально или горизонтально не важно их всегда 4 for(let r=0; rupdate(); // ну и обновим > function row(row, direction)< let lastCell = 0; // тут будем хранить ячейку в которую что то положим let x,y; // пройдём по элементам ряда или столбца их у нас всегда четыре for( let pos = 0; pos < 4; pos++ )< if(direction.x == 0 )< // если направление по х нулевое значит перемещаемя горизонтально по ряду row x = row; y = (direction.y>=0 ? direction.y*pos + 0 : direction.y*pos + 3); // позиция по y будет в зависимости от направления direction.y // при положительном значении будет от 0 вверх, // а при отрицательном от 3 вниз >else< // если направление по y нулевое значит перемещаемя вертикально по столбцу row y = row; x = (direction.x>=0 ? direction.x*pos : direction.x*pos + 3); > if(lastCell == 0)< // если это первая ячейка в нашем ряду или столбце lastCell равно нулю //пока мы не определили туда ничего lastCell = ; continue; //переходим к следующей > if(cvalue[x][y] == 0) < //пустая пропустим continue; >else if(cvalue[x][y] == lastCell.val)< //одинаковые значения перекидываем отсюда в lastCell cvalue[lastCell.row][lastCell.col] += cvalue[x][y]; // эту обнуляем она стала пустая cvalue[x][y] = 0; //в lastCell уже ложить не может сдвинемся на следующую она пустая в любом случае lastCell.row += direction.x; lastCell.col += direction.y; lastCell.val = 0; >else< // нельзя мержить значит ложим в соседнюю с lastCell if(lastCell.val == 0)< //ячейка пуста просто переносим значение //и lastCell остается мы же можем в нёё мержить ? cvalue[lastCell.row][lastCell.col] = cvalue[x][y]; lastCell.val = cvalue[x][y]; cvalue[x][y] = 0; >else < // ячейка заполнена и не подходит нужно перейти к следубщей lastCell.row += direction.x; lastCell.col += direction.y; if(lastCell.row == x && lastCell.col == y ) < // это случай когда между ячейками нет свободных // мы ничего не перемещаем и теперь в lastCell у нас значение ячейки X:Y lastCell.val = cvalue[x][y]; continue; //тогда просто идём дальше >cvalue[lastCell.row][lastCell.col] = cvalue[x][y]; lastCell.val = cvalue[x][y]; cvalue[x][y] = 0; > > > >
.buttons < position: absolute; left: 100px; top: 50px; display: flex; flex-direction: column; >#main < width: 200px; border: solid 1px red; display: flex; flex-wrap: wrap; margin: auto; >.cell
Отслеживать
51.6k 204 204 золотых знака 67 67 серебряных знаков 251 251 бронзовый знак
ответ дан 6 дек 2021 в 17:08
1,466 6 6 серебряных знаков 17 17 бронзовых знаков
Выглядит как полностью альтернативное решение. Спасибо, буду разбираться. Пока только вижу странность cvalue[lastCell.row][lastCell.col] += cvalue[x][y]; Мне всегда казалось, что x это колонка, а y — это строка. Здесь точно ничего не перепутано?
6 дек 2021 в 17:59
да так и есть точно, случайно так вышло что ‘х’ это ряд
6 дек 2021 в 18:16
Если не заморачиваться с анимацией, то проще всего сделать через таблицы. Хранить длинные числа большого смысла тоже не имеет. Можно хранить только степени двоек.
// Вообще, задача не требует ООП, но пусть будет класс игры class Game < // Свойство содержит количество колонок colsNum; // Свойство содержит количество строк rowsNum; // Свойство содержит степени двоек всех ячеек raw; // Свойство содержит ссылку на таблицу actor; // Конструктор constructor(props) < // Применить свойства к this Object.assign(this, props); >// Метод генерирует таблицу init() < // Заполняем массив степеней двоек нолями this.raw = new Array(this.colsNum * this.rowsNum).fill(0); // На всякий отчистим таблицу this.actor.innerHTML = ''; // Перебор строк // ri - индекс строки // rn - количество строк for (let ri = 0, rn = this.rowsNum; ri < rn; ri++) < // Создаем let tr = document.createElement('tr'); // Перебор колонок // ci - индекс колонки // cn - количество колонок for (let ci = 0, cn = this.colsNum; ci < cn; ci++) < // Создаем и помещаем в tr.appendChild(document.createElement('td')); > // Помещаем в this.actor.appendChild(tr); > // Добавляем две рандомные ячейки для начала игры this.addRand(); this.addRand(); return this; > // Метод добавляет рандомное число в рандомную свободную ячейку addRand() < // Массив свободных ячеек let free = []; for (let i = 0, l = this.raw.length; i < l; i++) < if (!this.raw[i]) < free.push(i); >> // Выбираем рандомную ячейку const cell = free[~~(Math.random() * free.length)]; // Выбираем степень новой двойки 1 или 2 const value = ~~(Math.random() * 2) + 1; // Запишем ко всем остальным ячейкам this.raw[cell] = value; // Обновляем виды this.update(); > // Свойство возвращает массив колонок get cols() < let cols = []; for (let ci = 0, cn = this.colsNum; ci < cn; ci++) < let col = []; for (let ri = 0, rn = this.rowsNum; ri < rn; ri++) < col.push(this.raw[ri * cn + ci]); >cols.push(col); > return cols; > // Свойство принимает массив колонок и записывает ко всем степеням двоек set cols(v) < for (let ci = 0, cn = this.colsNum; ci < cn; ci++) < for (let ri = 0, rn = this.rowsNum; ri < rn; ri++) < this.raw[ri * cn + ci] = v[ci][ri]; >> > // Свойство возвращает массив строк get rows() < let rows = []; for (let ri = 0, rn = this.rowsNum; ri < rn; ri++) < let row = []; for (let ci = 0, cn = this.colsNum; ci < cn; ci++) < row.push(this.raw[ri * cn + ci]); >rows.push(row); > return rows; > // Свойство принимает массив строк и записывает ко всем степеням двоек set rows(v) < for (let ri = 0, rn = this.rowsNum; ri < rn; ri++) < for (let ci = 0, cn = this.colsNum; ci < cn; ci++) < this.raw[ri * cn + ci] = v[ri][ci]; >> > // Метод управления игрой // dir - направление // u - вверх // d - вниз // l - влево // r - вправо swap(dir) < let property, method; // Можно было бы использовать карту наподобие keyMap как по коду внизу, // но для академичности можно воткнуть switch switch (dir) < case 'u': [property, method] = ['cols', 'reverse']; break; case 'd': [property, method] = ['cols', 'forward']; break; case 'r': [property, method] = ['rows', 'forward']; break; case 'l': [property, method] = ['rows', 'reverse']; break; >let lines = this[property]; // Вызываем метод сдвига if (this.sum(lines.map(line => this[method](line)))) < // Если что-то сдвинулось, то записываем результат this[property] = lines; // Добавляем еще 2 или 4 в рандомную ячейку this.addRand(); >> // Метод делает обновление видов update() < let tds = this.actor.querySelectorAll('td'); for (let i = 0, l = this.raw.length; i < l; i++) < tds[i].innerHTML = this.raw[i] ? 2 ** this.raw[i] : ''; >> // Метод считает сумму аргументов sum(. as) < return as.flat(Infinity).reduce((a, v) =>a + v, 0); > // Метод сдвигает одну линию к началу // raw - одномерный массив степеней двоек reverse(raw) < // Количество сдвинутых ячеек let swapped = 0; for (let i = 0; i < raw.length - 1;) < if (!raw[i] && this.sum(raw.slice(i + 1, raw.length))) < raw.splice(i, 1); swapped++; >else i++; > // Заполняем конец массива недостающими нулями raw.splice(raw.length, 0, . new Array(swapped).fill(0)); // Количество совмещенных ячеек let mixed = 0; for (let i = 1; i < raw.length; i++) < if (raw[i - 1] && raw[i - 1] == raw[i]) < raw.splice(i - 1, 2, raw[i] + 1); mixed++; >> // Заполняем конец массива недостающими нулями raw.splice(raw.length, 0, . new Array(mixed).fill(0)); return swapped + mixed; > // Метод сдвигает одну линию к концу // raw - одномерный массив степеней двоек forward(raw) < // Количество сдвинутых ячеек let swapped = 0; for (let i = raw.length - 1; i >= 0; i--) < if (!raw[i] && this.sum(raw.slice(0, i))) < raw.splice(i, 1); swapped++; >> // Заполняем начало массива недостающими нулями raw.splice(0, 0, . new Array(swapped).fill(0)); // Количество совмещенных ячеек let mixed = 0; for (let i = raw.length - 2; i >= 0; i--) < if (raw[i] && raw[i] == raw[i + 1]) < raw.splice(i, 2, raw[i] + 1); mixed++; i--; >> // Заполняем начало массива недостающими нулями raw.splice(0, 0, . new Array(mixed).fill(0)); return swapped + mixed; > > // Инициализация игры const game = new Game( < // Ссылка на готовый элемент уже присутствующий на странице actor: document.querySelector('table'), // Количество колонок colsNum: 4, // Количество строк rowsNum: 4, >).init(); // Клавишная карта. let keyMap = new Map([ ['ArrowUp', 'u'], ['ArrowDown', 'd'], ['ArrowLeft', 'l'], ['ArrowRight', 'r'], ['KeyW', 'u'], ['KeyS', 'd'], ['KeyA', 'l'], ['KeyD', 'r'], ['KeyK', 'u'], ['KeyJ', 'd'], ['KeyH', 'l'], ['KeyL', 'r'], ]); // Обработчик нажатий клавиш window.onkeydown = e => < // Специально для SO, чтобы страница не дергалась если жать на стрелочки e.preventDefault(); e.stopPropagation(); // Код нажатой клавиши const = e; // Если клавиша есть в списке if (keyMap.has(code)) < // То свопаем игру в правильном направлении game.swap(keyMap.get(code)); >>;
/* Это для сопроводительной надписи */ body < font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; >/* Выравнивание и небольшой отступ */ table < float: left; margin-right: 4rem;>/* Стили ячейки */ td < width: calc(25vmin - 10px); height: calc(25vmin - 10px); border: 1px solid #ccc; text-align: center; font-size: calc(10vmin); font-family: Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif; >/* Это для визуального выделения названий клавиш */ span
Проверку на выигрыш или проигрыш не делал.
Чуть позже мне пришла идея, хранить состояние в одной строке, (один символ — одна фишка), если кодировать в шестнадцатеричной системе. Или если делать большие поля (5х5 или 10х10), то большие числа также можно хранить вплоть до 36-ричной системы.
Переводы между строками и числами можно делать так:
let num, str; num = 30; str = num.toString(36); console.log(`str = $;`); num = parseInt(str, 36); console.log(`num = $;`);
Чуть позже напишу комментариев к коду. Но если будут вопросы — спрашивайте, отвечу как увижу.
Отслеживать
ответ дан 7 дек 2021 в 11:52
7,606 1 1 золотой знак 6 6 серебряных знаков 28 28 бронзовых знаков
Спасибо за решение, этот ответ содержит для меня много нового, но я освоил. От дублирования кода избавиться удалось, да и кода стало значительно меньше, очень полезный ответ.
11 дек 2021 в 15:08
Стоит посмотреть код из другого моего ответа
11 дек 2021 в 19:33
я смотрел, спасибо, тоже полезно.
12 дек 2021 в 1:12
Можно поворачивать игровое поле так, чтобы сдвиг выполнялся всегда слева направо, затем сдвигать ячейки и поворачивать поле обратно.
//преобразовываем координаты чтобы сдвиг выполнялся слева направо if (direction === 'left') < cells.forEach(cell =>< cell.oldRow = cell.row cell.oldCol = cell.col cell.col = size - 1 - cell.col >) > if (direction === 'right') < //ничего >if (direction === 'up') < cells.forEach(cell =>< cell.oldRow = cell.row cell.oldCol = cell.col cell.col = cell.oldRow cell.row = size - 1 - cell.oldCol >) > if (direction === 'down') < cells.forEach(cell =>< cell.oldRow = cell.row cell.oldCol = cell.col cell.col = cell.oldRow cell.row = cell.oldCol >) > //сдвигаем вправо move('right') //Нужно почистить функцию и оставить только часть кода внутри direction === 'right' //восстанавливаем координаты обратно cells.forEach(cell => < cell.row = cell.oldRow cell.col = cell.oldCol >)
Можно этот алгоритм преобразования координат перенести в функцию getCell :
function getCell(row, col, direction) < if (direction === 'left') < col = size - 1 - cell.col >if (direction === 'up') < let oldRow = row row = size - 1 - col col = oldRow >if (direction === 'down') < let oldRow = row row = col col = oldRow >for (let i = 0; i < cells.length; i++) < const cell = cells[i]; if (cell.row === row && cell.col === col && cell.value !== 0) < return cell; >> >
Отслеживать
ответ дан 6 дек 2021 в 12:43
2,468 9 9 серебряных знаков 22 22 бронзовых знака
Несложно заметить, что при хранении элементов в плоском массиве разные варианты перебора столбцов и строк отличаются только стартовой позицией и смещениями при переходе к следующим элементам. Соответственно имеет смысл не хардкодить эти величины, а вынести их в отдельную С++style табличку и обращаться к ней при входе в функцию. Также для практической реализации, если надо делать анимации перескакивания и схлопывания ячеек, то имеет смыл добавить в функцию третий аргумент, принимающий функцию обратного вызова для соотв. оповещений.
type Field = Array; type Direction = 'left' | 'right' | 'up' | 'down'; type Navigation = < initial: number; next_col: number; next_row: number; >; type Lookup = < [direction in Direction]: Navigation; >; const dim = 4; const cells_count = dim * dim; const lookup: Lookup = < left : < initial: 0 , next_col: 1 , next_row: dim >, right: < initial: (dim - 1) , next_col: -1 , next_row: dim >, up : < initial: 0 , next_col: dim, next_row: 1 >, down : < initial: (dim - 1) * dim, next_col: -dim, next_row: 1 >>; function Fold(input: Field, direction: Direction): Field < const output = new Array(cells_count).fill(0); const navigation = lookup[direction]; let remaining = cells_count; let pos = navigation.initial; for (;;) < let read_pos = pos; let write_pos = pos; for (;;) < if (0 !== input[read_pos]) < if (output[write_pos] !== input[read_pos]) < if (0 !== output[write_pos]) < write_pos += navigation.next_col; >output[write_pos] = input[read_pos]; > else < output[write_pos] *= 2; write_pos += navigation.next_col; >> --remaining; if (0 === (remaining % dim)) < break; // inner loop >read_pos += navigation.next_col; continue; // inner loop > if (0 === remaining) < break; // outer loop >pos += navigation.next_row; continue; // outer loop > return output; >
Отслеживать
ответ дан 7 дек 2021 в 15:18
user7860670 user7860670
29.9k 3 3 золотых знака 17 17 серебряных знаков 36 36 бронзовых знаков
Спасибо. TS мне не катит, но идею понял.
7 дек 2021 в 15:26
@aepot ¿Почему не катит? А вообще он легким движением превращается в js.
7 дек 2021 в 15:29
Оно понятно, спасибо еще раз.
7 дек 2021 в 15:30
Решил немного расширить ответ. А новый код вместе со старым ответом в один ответ не вместился. Потому выложу отдельным ответом.
Прикрутил к демке выбор размера поля, две кнопки перехода по истории вперед-назад, индикатор набранных очков, обработчики touch-событий для управления с мобильных гаджетов и еще несколько неважных улучшений.
В целом, демка получилась неплохая. Над анимацией еще поработать надо.
Будут вопросы — задавайте. Код может работать некорректно. Если заметили баг, напишите, исправим.
// Порог срабатывания приковнования const TOUCH_THRESHOLD = 50; // Цветовая схнма COLOR_SCHEMA = [ 'E57373', 'F06292', 'BA68C8', '9575CD', '7986CB', '64B5F6', '4FC3F7', '4DD0E1', '4DB6AC', '81C784', 'AED581', 'DCE775', 'FFF176', 'FFD54F', 'FFB74D', 'FF8A65', 'A1887F', '90A4AE', 'E0E0E0', ]; // Математические функции и константа const < abs, round, random, max, min, sin, cos, hypot, atan2, PI: π, E: >= Math; // Функции Object const < assign, entries, fromEntries >= Object; // Хэлпер querySelector const sel = (. q) => !q.length ? null : Array.isArray(q[0]) ? sel(q[0].reduce((r, s, i) => ((r += (i ? q[i] : '') + s), r), '')) : q.reduce((r, s) => (!r ? null : 'object' == typeof s ? s : r.querySelector(s)), document); // Хэлпер querySelectorAll const selAll = (. q) => !q.length ? null : Array.isArray(q[0]) ? selAll(q[0].reduce((r, s, i) => ((r += (i ? q[i] : '') + s), r), '')) : q.reduce( (r, s, i, q) => !r ? null : 'object' == typeof s ? s : r['querySelector' + (i == q.length - 1 ? 'All' : '')](s), document ); // Хэлперы классов const addCls = (el, . cls) => (el.classList.add(. cls), el); const remCls = (el, . cls) => (el.classList.remove(. cls), el); // Хэлпер аттрибутов const attr = (el, atr, val) => (typeof val == 'undefined' ? el.getAttribute(atr) : (el.setAttribute(atr, val), el)); // Хэлпер createElement const create = def => < const el = document.createElement(def.tag || 'div'); if (def.cls) el.className = def.cls; if (def.css) assign(el.style, def.css); if (def.evt) entries(def.evt).forEach(([evt, cb]) =>el.addEventListener(evt, cb, false)); if (def.tgt) def.tgt.appendChild(el); if (def.atr) entries(def.atr).forEach(([atr, val]) => attr(el, atr, val)); if (def.txt) el.innerHTML = def.txt; if (def.chd) < if (Array.isArray(def.chd)) def.chd.forEach(subdef =>create(< tgt: el, . subdef >)); else create(< tgt: el, . def.chd >); > return el; >; // Останавливает событие const stop = e => < e.preventDefault(); e.stopPropagation(); >; // Преобразователь массива в строку const arr2str = arr => arr.map(val => (+val).toString(36)).join``; // Преобразователь строки в массив const str2arr = str => [. str].map(chr => parseInt(chr, 36)); // Схемы клавиатуры [ ['left', 'down', 'up', 'right', 'undo', 'redo'], ['⬅', '⬇', '⬆', '⮕', '⌫', '⏎'], ['H', 'J', 'K', 'L', 'U', '.'], ['A', 'S', 'W', 'D', 'Z', 'R'], ].forEach((scheme, idx) => create( < tag: 'tr', chd: scheme.map(key =>( < tag: idx ? 'td' : 'th', chd: < tag: 'span', cls: 'key', txt: key >>)), tgt: sel('table'), >) ); //Уровни сложности [ [3, 8], [4, 11], [5, 14], [6, 16], [7, 19], ].forEach(([size, adv]) => create(< tag: 'button', atr: < size, adv >, chd: [ < tag: 'label', cls: 'size', txt: `$ ×$ ` >, < tag: 'hr' >, < tag: 'label', cls: 'advance', txt: `to $ ` >, ], evt: < click: evt =>< let target = evt.target.closest('button'); createGame(< size: +attr(target, 'size'), adv: +attr(target, 'adv') >); >, >, tgt: sel('.new-game'), >) ); // Стили для фрейма выигрыша create( < tag: 'style', txt: `.frame.success h3, .frame.success .score < animation: css-fizzy 12s linear 0s infinite; >@keyframes css-fizzy < $`$<(100 / 12) * frameIdx>% `$px $< (-1.6) ** shdowPartIdx * cos((frameIdx / 6) * π) >px $px` ), ] .map((shadow, shadowIdx) => `$ hsl($deg,99%,$%)`) .join()>;>` ).join``> >`, tgt: document.body, >); // текущая игра var game; // текущее косание let cur = < pageX: null, pageY: null >; // Обработчик кнопок закрытия selAll('button.exit').forEach( btn => (btn.onclick = e => attr(e.target.closest('.frame'), 'mode') == 'export' ? (setFrame('game'), activateListeners()) : closeGame()) ); // Обработчик кнопкт Экспорт sel('button.export').onclick = e => < deactivateListeners(); const txtAr = sel('textarea'); txtAr.value = JSON.stringify(game.serialize()); attr(setFrame('memo'), 'mode', 'export'); txtAr.select(); >; // Обработчик кнопки import sel('button.import').onclick = e => < deactivateListeners(); const txtAr = sel('textarea'); txtAr.value = ''; attr(setFrame('memo'), 'mode', 'import'); txtAr.select(); >; // Обработчик кнопки OK sel('button.ok').onclick = e => < const txtAr = sel('textarea'); if (attr(txtAr.closest('.frame'), 'mode') == 'import') < try < let obj = JSON.parse(txtAr.value); // TODO: Check JSON-data createGame(obj); >catch (e) < alert(e.message); >> else < setFrame('game'); activateListeners(); >>; // Обработчик кнопки Undo sel('button.undo').onclick = e => < game.undo(); >; // Обработчик кнопки Redo sel('button.redo').onclick = e => < game.redo(); >; // отчищает текущее косание function clearCur() < cur.pageX = cur.pageY = null; >// устанавливает начало текущего косания function setCur(touch) < cur.pageX = touch.pageX; cur.pageY = touch.pageY; >// определяет направление косания function dirCur(touch) < const dX = touch.pageX - cur.pageX; const dY = touch.pageY - cur.pageY; const d = Math.hypot(dX, dY); if (d >TOUCH_THRESHOLD && (abs(dY) > 2 * abs(dX) || 2 * abs(dY) < abs(dX))) < let side = ((π + atan2(dX, dY)) / π) * 2; side = round(side); side = side < 4 ? side : side - 4; let dir = ['u', 'l', 'd', 'r'][side]; setCur(touch); return dir; >else < return null; >> //Функция создает экземпляр игры function createGame(props) < setFrame('game'); game = new Game(props).init(); activateListeners(); >// Функция закрывает текующий экземпляр игры function closeGame() < game = null; setFrame('lobby'); deactivateListeners(); >// Функция активирует заданный аргументом фрейм function setFrame(frame) < selAll('.frame').forEach(frame =>remCls(frame, 'show')); return addCls(sel(`.$`), 'show'); > // Функция включает обработчики function activateListeners() < window.ontouchstart = e => < if (e.touches.length != 1) < clearCur(); >else < setCur(e.touches[0]); >>; window.ontouchmove = e => < if (e.touches.length != 1) < return clearCur(); >let dir = dirCur(e.touches[0]); if (dir) < game.swap(dir); >>; window.ontouchend = e => clearCur(); window.ontouchcancel = e => clearCur(); window.onkeydown = e => < [ ['up', ['ArrowUp', 'KeyW', 'KeyK']], ['down', ['ArrowDown', 'KeyS', 'KeyJ']], ['left', ['ArrowLeft', 'KeyA', 'KeyH']], ['right', ['ArrowRight', 'KeyD', 'KeyL']], ['undo', ['Backspace', 'KeyU', 'KeyZ']], ['redo', ['Enter', 'KeyR', 'Period']], ].map(([fn, keys]) => < if (keys.includes(e.code) && !e.ctrlKey && !e.altKey && !e.metaKey) < game[fn](); stop(e); >>); >; window.onresize = e => < game.reposition(); >; window.onwheel = e => < stop(e); >; > // Функция выключет обработчики function deactivateListeners() < // удаляем обработчики ['keydown', 'touchend', 'touchcancel', 'touchmove', 'touchstart', 'resize', 'wheel'].map( evt =>(window[`on$`] = null) ); > // Функция пересоздает grid function regrid(size) < // Удаляем старые ячейки selAll(grid, 'div').forEach(cell =>cell.remove()); // Удаляем старые тайлы selAll(layer, 'div').forEach(tile => tile.remove()); // Добавляем новые ячейки for (let i = 0; i < size ** 2; i++) create(< tag: 'div', tgt: grid >); // Шаблоны grid assign(grid.style, < gridTemplateColumns: `repeat($,1fr)`, gridTemplateRows: `repeat($ ,1fr)`, >); // Динамические стили для тайлов dynamic.innerHTML = Object.entries(COLOR_SCHEMA) .map( ([dib, color]) => `#layer .tile[val="$"];> #layer .tile[val="$"]::before< content:"$"; >` ) .join('\n') + `#layer .tile < font-size: calc(80vmin / $); line-height: calc(80vim / $); >`; > class Game < // размер size; // сложность adv; // массив значений raw; // стек отмен stackUndo = []; // стек повторов stackRedo = []; // очки _score = 0; // флаг начала started = false; constructor(props) < assign(this, props); >// Инициализация init() < if (!this.size) this.size = 4; if (!this.adv) this.adv = round(this.size * ); if (!this.raw) this.raw = new Array(this.size ** 2).fill(0); regrid(this.size); if (!this.started) < this.started = true; this.addition().addition(); >else < this.recreate(); >this.score = this.score; return this; > // Дополнение addition() < // считаем пыстые ячейки let freeCells = []; for (let i = 0; i < this.size ** 2; i++) < if (!this.raw[i]) < freeCells.push(i); >> // выбираем рандломную let pos = freeCells[~~(freeCells.length * random())]; // рандомное значение let value = 1 + ~~((this.size - 2) * random()); // let [x, y] = [pos % this.size, ~~(pos / this.size)]; this.raw[pos] = value; this.createTile(x, y, value, 'addition'); return this; > // Сделать ход swap(dir) < const [property, method] = < u: ['cols', 'backward'], d: ['cols', 'forward'], r: ['rows', 'forward'], l: ['rows', 'backward'], >[dir]; let lines = this[property]; let newLines = []; let changes = 0; // Вызываем метод сдвига for (let i = 0; i < this.size; i++) < let line = lines[i]; let result = this[method](line); let < moves, mixes, newLine >= result; for (let j = 0; j < moves.length; j++) < let [from, to] = moves[j]; let [xFrom, yFrom, xTo, yTo] = property == 'cols' ? [i, from, i, to] : [from, i, to, i]; let tile = this.getTile(xFrom, yFrom); assign(tile.style, this.getRect(xTo, yTo)); attr(tile, 'x', xTo); attr(tile, 'y', yTo); >// вызываем метод мутирования for (let j = 0; j < mixes.length; j++) < let [x, y] = property == 'cols' ? [i, mixes[j]] : [mixes[j], i]; let value = newLine[mixes[j]]; this.score += value; // Удаляем старые тайлы this.getTile(x, y).remove(); this.getTile(x, y).remove(); // создаем новые тайлы this.createTile(x, y, value, 'mix'); >changes += mixes.length + moves.length; newLines.push(newLine); > // Если что-то поменялось if (changes) < // Сохраняем старые значения this.stackUndo.push([arr2str(this.raw), this._score]); this.stackRedo.splice(0, this.stackRedo.length); // Применяем значения this[property] = newLines; // Добавляем рандомный тайл this.addition(); // Чекнем на вымгрыш или проигрыш this.check(); // На всякий перепозиционируем тайлы requestAnimationFrame(() =>< this.reposition(); >); > > // биндинги сторон up() < this.swap('u'); >down() < this.swap('d'); >left() < this.swap('l'); >right() < this.swap('r'); >// отменить undo() < // если стек пуст if (!this.stackUndo.length) < return; >this.stackRedo.push([arr2str(this.raw), this.score]); let [raw, score] = this.stackUndo.pop(); this.score = score; this.raw = str2arr(raw); this.recreate(); > // повторить redo() < // если стек пуст if (!this.stackRedo.length) < return; >this.stackUndo.push([arr2str(this.raw), this.score]); let [raw, score] = this.stackRedo.pop(); this.score = score; this.raw = str2arr(raw); this.recreate(); > // Проверка на выигрыш или проигрыш check() < if (this.raw.find(item =>item == this.adv)) < return setFrame('success'); >if (!this.raw.includes(0)) < for (let y = 0; y < this.size; y++) < for (let x = 0; x < this.size - 1; x++) < if (this.raw[y * this.size + x] == this.raw[y * this.size + x + 1]) < return; >> > for (let x = 0; x < this.size; x++) < for (let y = 0; y < this.size - 1; y++) < if (this.raw[y * this.size + x] == this.raw[(y + 1) * this.size + x]) < return; >> > return setFrame('failure'); > > // сдвигаем назад backward(line) < let moves = []; let mixes = []; let skip = []; let pos = -1; let lastValue = null; let lastIndex = null; let newLine = []; for (let i = 0, l = line.length; i < l; i++) < if (line[i] == 0) < skip.push(i); >else if (lastIndex != null && lastValue != null && line[i] == lastValue) < moves.push([i, lastIndex]); mixes.push(lastIndex); newLine[lastIndex]++; lastIndex = null; lastValue = null; >else < lastIndex = newLine.length; newLine.push(line[i]); lastValue = line[i]; // >if (++pos != i) < moves.push([i, pos]); >> > let endian = this.size - newLine.length; newLine.splice(newLine.length, 0, . new Array(endian).fill(0)); return < moves, mixes, newLine >; > // сдвигаем линию вперед forward(line) < let moves = []; let mixes = []; let skip = []; let pos = this.size; let lastValue = null; let lastIndex = null; let newLine = new Array(this.size).fill(0); for (let i = line.length - 1; i >= 0; i--) < if (line[i] == 0) < skip.push(i); >else if (lastIndex != null && lastValue != null && line[i] == lastValue) < moves.push([i, lastIndex]); mixes.push(lastIndex); newLine[lastIndex]++; lastIndex = null; lastValue = null; >else < lastIndex = --pos; newLine[pos] = line[i]; lastValue = line[i]; if (pos != i) < moves.push([i, pos]); >> > return < moves, mixes, newLine >; > // перепозиционирование тайлов reposition() < // получаем все тайлы let tiles = selAll(layer, '.tile'); for (let tile of tiles) < let [x, y] = [+attr(tile, 'x'), +attr(tile, 'y')]; // циклично каждому устанавливаем позицию в соответствии с коордтнатами assign(tile.style, this.getRect(x, y)); >> // пересоздать тайлы recreate() < // удаляем все тайлы selAll(layer, 'div').forEach(tile =>tile.remove()); // создаем тайлы for (let [pos, dib] of this.raw.entries()) < if (!dib) continue; let [x, y] = [pos % this.size, ~~(pos / this.size)]; this.createTile(x, y, dib); >> // создать тайл createTile(x, y, val, anim) < let rect = this.getRect(x, y); let tile = create(< cls: 'tile', atr: < x, y, val >, css: this.getRect(x, y), tgt: layer, >); if (anim) < requestAnimationFrame(() => < if (anim) < tile.classList.add(anim); >requestAnimationFrame(() => < tile.classList.add('transition'); if (anim) < requestAnimationFrame(() =>< tile.classList.remove(anim); >); > >); >); > return tile; > // Найти тайл getTile(x, y) < return sel(layer, `div[x="$"][y="$"]`); > // Найти область ячейки getRect(x, y) < const rect = sel(grid, `div:nth-child($)`).getBoundingClientRect(); return fromEntries(['left', 'top', 'width', 'height'].map(prop => [prop, rect[prop] + 'px'])); > // Свойство возвращает массив столбцов get cols() < const cols = []; for (let ci = 0, cn = this.size; ci < cn; ci++) < let col = []; for (let ri = 0, rn = this.size; ri < rn; ri++) < col.push(this.raw[ri * cn + ci]); >cols.push(col); > return cols; > // Свойство записывает массив столбцов set cols(v) < for (let ci = 0, cn = this.size; ci < cn; ci++) < for (let ri = 0, rn = this.size; ri < rn; ri++) < this.raw[ri * cn + ci] = v[ci][ri]; >> > // Свойство возвращает массив строк get rows() < const rows = []; for (let ri = 0, rn = this.size; ri < rn; ri++) < let row = []; for (let ci = 0, cn = this.size; ci < cn; ci++) < row.push(this.raw[ri * cn + ci]); >rows.push(row); > return rows; > // Свойство записывает массив строк set rows(v) < for (let ri = 0, rn = this.size; ri < rn; ri++) < for (let ci = 0, cn = this.size; ci < cn; ci++) < this.raw[ri * cn + ci] = v[ri][ci]; >> > // Свойство возвращает количество очков get score() < return this._score; >// Свойство устанавливает количество очков set score(v) < selAll('.score').forEach(el =>(attr(el, 'text', v).innerHTML = v)); this._score = v; > // Сериализация игры serialize() < return fromEntries( ['size', 'adv', 'raw', 'score', 'started', 'stackUndo', 'stackRedo'].map(prop =>[prop, this[prop]]) ); > >
*, *::before, *::after < box-sizing: border-box; >body < margin: 0; min-height: 100vw; overflow: hidden; >.frame < width: 100vw; height: 100vh; >.frame < display: none; >.show, .lobby.show, .new-game, .panel < display: flex; >.anim < transition: all 100ms; >.lobby, .memo, .game < flex-flow: column nowrap; justify-content: flex-start; align-items: center; >.meta, .new-game < justify-content: center; align-items: center; flex-flow: row wrap; gap: 1rem; >.note, .kbd < width: 25rem; >.note::before, .kbd::before < margin-right: 1rem; display: block; >.note::before < content: '⚠'; font-size: 3rem; >.kbd::before < content: '⌨'; font-size: 5rem; line-height: 3rem; >.meta, .note, .kbd < display: flex; flex-flow: row nowrap; >table tr th < font-size: 0.8rem; >table tr td span < width: 2rem; height: 1.6rem; border: 1px solid #3339; text-align: center; background: #8080802b; border-collapse: separate; border-bottom: 1px solid #4449; border-radius: 3px; box-shadow: #000 0px -1px 0px 0px inset; color: #000; cursor: default; display: block; font-family: -apple-system, system-ui, sans-serif; font-size: 1.2rem; forced-color-adjust: none; line-height: 1rem; margin: 0 2px 0 0; padding: 3px 5px; user-select: none; white-space: nowrap; -webkit-border-horizontal-spacing: 0px; -webkit-border-vertical-spacing: 0px; -webkit-font-smoothing: antialiased; >h3, .score, #layer div < font-family: Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif; >h3, .score < font-size: 4rem; >#grid < background: #888; display: grid; >#layer, #grid < border-radius: 1vmin; width: calc(100vw - 2rem); height: calc(100vh - 2rem); max-width: calc(100vh - 2rem); max-height: calc(100vw - 2rem); position: fixed; top: 1rem; left: 1rem; padding: 1vmin; gap: 1vmin; >#layer div < z-index: 10; position: fixed; text-align: center; >#layer div, #grid div < border-radius: 0.5vmin; >#grid div < background: #fff; >h3, .failure .score, .success .score < position: fixed; left: 50vw; transform: translate(-50%, -150%); text-align: center; color: #fffa; >h3 < top: 45vh; >.failed .score, .success .score < top: 55vh; >.layer div < position: fixed; text-align: center; color: #555; text-shadow: 0 0 0.2rem #ff0, 0 0 0.4rem #fff; >.layer > .tile < transform: scale(1); opacity: 1; >.layer > .tile.addition < transform: scale(0); >.layer > .tile.mixed < transform: scale(1.5); opacity: 0; >.transition < transition: all 100ms ease-out; >.exit < margin: 1rem 1rem 0 1rem; width: 2rem; height: 2rem; border-radius: 1rem; background: #f00; color: #fff; border: 2px solid #000; /* box-shadow: 0 0 1rem #f00; */ display: inline-block; text-align: center; padding: 0; line-height: 1.5rem; cursor: pointer; >.exit:hover < position: relative; border: 2px solid #fff; box-shadow: 0 0 1rem #f00; top: -2px; >.exit:active < border: 2px solid #fffc; box-shadow: 0 0 0.5rem #f00; top: 2px; >.exit::before < /* ✖× */ content: '×'; font-size: 2rem; line-height: 1.5rem; text-align: center; >button.ok < width: 6rem; height: 3rem; font-size: 2rem; display: inline-block; >textarea < height: calc(100vh - 3rem - 3rem - 3rem); margin: 1rem; width: calc(100vw - 2rem); >header < display: flex; flex-flow: row; justify-content: space-between; align-items: top; width: 100vw; >.game .score < position: fixed; top: 3rem; right: 1rem; color: #0008; cursor: default; user-select: none; >button < display: inline-block; vertical-align: center; >button p < float: right; /* clear: right; */ margin: 0.8rem 0 0 0.3rem; >.undo::before, .redo::before, .import::before, .export::before < font-size: 2rem; display: block; float: left; >.undo::before < content: '◀'; >.redo::before < content: '▶'; >.export::before < content: ''; >.import::before < content: ''; >.memo[mode='import'] header p::before < content: 'Вставте содержимое файла в это поле и нажмите ОК:'; >.memo[mode='export'] header p::before < content: 'Сохраните содержимое файла из этого поля'; >.memo p < margin: 2rem 0 0 1rem; >.panel < position: fixed; bottom: 1rem; right: 1rem; >:root < --f-size: 15; --f-unit: 1vmin; --f: calc(var(--f-size) * var(--f-unit)); --bg: #222; >.failure.frame, .success.frame < background: var(--bg); >.failure.frame .score, .failure.frame h3 < flex: 1; line-height: 0.75; margin: auto; color: #1af0dc; text-align: center; transform: scaleX(var(--scale, 1)); animation: glitch-p 11s infinite alternate; font-size: 6vmin; position: fixed; left: 50vw; transform: translate(-50%, -50%); >.failure.frame .loose < top: 45vh; >.failure.frame .score < top: 55vh; >.failure.frame > *::before, .failure.frame > *::after < --top: 0; --left: 0; --v-height: 30%; --n-tenth: calc(var(--f-size) * 0.1 * var(--top)); --t-cut: calc(var(--n-tenth) / var(--f-size) * 100%); --b-cut: calc(var(--t-cut) + var(--v-height)); content: attr(text); position: absolute; width: 100%; left: 0; text-align: center; transform: translateX(calc(var(--left) * 100%)); filter: drop-shadow(0 0 transparent); text-shadow: calc(var(--left) * -3em) 0 0.02em lime, calc(var(--left) * -6em) 0 0.02em #ff00e1; background-color: var(--bg); clip-path: polygon(0% var(--t-cut), 100% var(--t-cut), 100% var(--b-cut), 0% var(--b-cut)); >.failure.frame > *::before < animation: glitch-b 1.7s infinite alternate-reverse; >.failure.frame > *::after < animation: glitch-a 3.1s infinite alternate; >@keyframes glitch-p < 17% < --scale: 0.87; >31% < --scale: 1.1; >37% < --scale: 1.3; >47% < --scale: 0.91; >87% < --scale: 1; >> @keyframes glitch-a < 10%, 30%, 50%, 70%, 90% < --top: 0; --left: 0; >0% < --v-height: 15%; >20% < --left: 0.005; >40% < --left: 0.01; --v-height: 20%; --top: 3; >60% < --left: 0.03; --v-height: 25%; --top: 6; >80% < --left: 0.07; --v-height: 5%; --top: 8; >100% < --left: 0.083; --v-height: 30%; --top: 1; >> @keyframes glitch-b < 10%, 30%, 50%, 70%, 90% < --top: 0; --left: 0; >0% < --v-height: 15%; --top: 10; >20% < --left: -0.005; >40% < --left: -0.01; --v-height: 17%; --top: 3; >60% < --left: -0.03; --v-height: 35%; --top: 6; >80% < --left: -0.07; --v-height: 5%; --top: 8; >100% < --left: -0.083; --v-height: 30%; --top: 1; >> @keyframes mixed < 0% < transform: scale(1); >50% < transform: scale(1.5); >100% < transform: scale(1); >>
Игра отличается от оригинала!
Длинные числа заменены на степени двоек.
Например, 2 + 2 => 3 и 3 + 3 => 4.
Управление с тачскрина не требует
отрыва пальца от экрана.
Чувствительность сенсора завышена.
0 YOU WIN
GAME OVER
Saved searches
Use saved searches to filter your results more quickly
Cancel Create saved search
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
kubowania / 2048 Public
A game of 2048 in vanilla javaScript, HTML and CSS
kubowania/2048
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Go to file
Folders and files
Last commit message
Last commit date
Latest commit
History
View all files
Repository files navigation
2048
A game of 2048 in vanilla JavaScript, HTML and CSS
In this walkthrough, we make the very popular game 2048 in JavaScript, HTML and CSS! No canvas required! (Now with more accurate subtitles for programmers)
Full video walkthrough here
Rules of 2048:
2048 is played on a 4×4 grid, with numbered tiles that slide smoothly when a player moves them using the four arrow keys. Each time you slide, a new tile will randomly appear in an empty spot on the board. Tiles slide as far as possible in the chosen direction until they are stopped by either another tile or the edge of the grid. If two tiles of the same number collide while moving, they will merge into a tile with the total value of the two tiles that collided. The resulting tile cannot merge with another tile again in the same move.
Tools and Software I used in this video:
- TabNine By Codota: https://bit.ly/Codota
- VSCode: https://bit.ly/VSCode-Editor
By creating this popular game we will learn the following javaScript Methods:
- querySelector()
- getElementById()
- createElement()
- appendChild()
- push()
- Math.floor()
- Math.random()
- length
- innerHTML
- parseInt()
- filter()
- Array()
- fill()
- concat()
- keyCode
- addEventListener()
- removeEventListener()
- setTimeout()
- clearInterval()
- setInterval()
If you did like this video, please do Like and Subscribe so I know to make others like this!
I would love to see what you have made so please do share your finished games with me on twitter! My handle is @ania_kubow.
MIT Licence
Copyright (c) 2020 Ania Kubow
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the «Software»), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*Translation: Ofcourse you can use this for you project! Just make sure to say where you got this from 🙂
THE SOFTWARE IS PROVIDED «AS IS», WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Курсы javascript
Привет, я играл в одну мобильную игру и там была мини-игра по типу 2048. Там правда вместо плиток были клинки с нумерацией от 0 до 9(9й клинок максимальный = плитке 2048). Сложность заключалась в том что 9й клинок не соединялся с таким же. В итоге они просто копились на поле тем самым уменьшая количество рабочих клеток. решил попробовать(!) сделать также своими ручками, но не разбираюсь в js(от слова совсем). думал просто в коде некоторые значения поменяю на false и та-да. скачал код который выложил создатель игры и попытался разобраться в том что нужно сделать, но так и не нашел тех самых заветных строк которые можно было бы изменить. быть может вообще на js нельзя такое провернуть но буду очень благодарен если вы поможете хотя бы ответом «это невозможно». но раз в игре такое есть значит где-то все таки возможно!!
Изображения:
Screenshot_20200603-234128.jpg (7.0 Кб, 9 просмотров) |