Как сделать игру 2048 в ворде
А можешь сделать версию для Windows xp(если это возможно)?
в сентебре у тебя было 100 к подписчиков сейчас же 1 лям . Откуда и как?🥳
хочется скачать 2048 в ворде.
Пов это с тик тока и я это не искал
А можно ссылку на скачиване 2048 в ворде
скиньте пж программу для програмирования ПЖ
Я тоже пытался в поинте сделать, но психанул и выключил
Эдак и змейка с тетрисом соберутся. И хоночки. А то и тамагочка на ворде.
А насколько эти vba удобны для гита? Там в коммитах что-нибудь будет видно, или в ворде крякозябры сохраняются? Их можно вынести в отдельный файл?
Ех,помню у тебе 16к било на канале
Я в детстве делал там лабиринты)
я уже делал игру в повер поинте)
Поможете сделать анимацию фигуры в повер плинт
Спасибо большое автору .
Я в паверпоинт делаю куча игр по приколу
А что делать если у тебя не работает pp?
Я вот вообще не далёк , но мне кажется игра в любом случае будет давать тебе собрать эти 2048 , ты же не прописал как проигрывается , я правильно понимаю ?
Я единственный походу сделал игру по этому туториу?
Щас бы играть в дум пока начальник пьет кофе
А какая у него версия Microsoft PowerPoint и Microsoft Word?
вот чему должни учить училки на информатике
Я на примере поверпоинта целую игру сделал, спасибо :3
Очень интересно, не навязчиво и информативно.
Вау Я сегодня на повер повенте и не знал Круто
2:20: я который отвлёкся обосрался
братан так это же чистый html css)только может не на языке javascripta а так все то же самое)
Давай ещё что не будь. Молодец все чётко коротко и понятно. Видос достоин лайка
Я в начале видео:шо такое повар поент?
Неделя после просмотра видео:
Я сделал первую игру в класухе ееееее😎😎😎😎😎💩😎
Ооооо, какая ностальгия). Помню, как в 2006-м делал в Power Point игры про соседа-алкаша, суть которых всегда заключалась просто тупо в отстреле пьяниц ночью этажом выше квартиры главного героя, который был по большей части списан с моего деда). Проходились за минут 10 конечно, но делал их с огромной скурпулёзностью). Частей под 15 наверное, наклепал в общей сложности.
Автор этого туториала сосредоточился на анимации. Он использовал хуки библиотеки React, её Context API, а также TypeScript и LESS. В конце вы найдёте ссылки на игру, её код и демо анимаций. Подробности рассказываем под катом, пока у нас начинается курс по Frontend-разработке.
Правила игры 2048
Числа на плитках — только степени двойки, начиная с самой 2. Игрок объединяет плитки с одинаковыми числами. Числа суммируются, пока дело не дойдёт до 2048. Игрок должен добраться до плитки с числом 2048 за наименьшее количество шагов.
Если доска заполнена и нет возможности сделать ход, например объединить плитки вместе, — игра окончена.
Для целей статьи я сосредоточился на игровой механике и анимации и пренебрёг деталями:
Число на новой плитке всегда 2, а в полной версии игры оно случайно.
Играть можно и после 2048, а если ходов на доске не осталось, то не произойдёт ничего. Чтобы начать сначала, нажмите кнопку сброса.
И последнее: очки не подсчитываются.
Структура проекта
Приложение состоит из этих компонентов React:
Board отвечает за рендеринг плиток. Использует один хук под названием useBoard .
Grid рендерит сетку 4x4.
Tile отвечает за все связанные с плиткой анимации и рендеринг самой плитки.
Game объединяет все элементы выше и включает хук useGame , отвечающий за выполнение правил и ограничений игры.
Как сделать компонент плитки
В этом проекте больше времени хочется уделить анимации, поэтому я начинаю рассказ с компонента Tile. Именно он отвечает за все анимации. В 2048 есть две простых анимации — выделение плитки и её перемещение по доске. Написать их мы можем при помощи CSS-переходов:
Я определил только один переход, выделяющий плитку, когда она создаётся или объединяется с другой плиткой. Пока оставим его таким.
Посмотрим, как должны выглядеть метаданные Tile, чтобы легко с ними работать. Я решил назвать тип метаданных TileMeta : не хочется, чтобы его имя конфликтовало с другими, например Tile:
id — уникальный идентификатор плитки. Он нужен, чтобы DOM React при каждом изменении не перерисовывал все плитки с самого начала. Иначе мы увидим подсвечивание плиток на каждом действии игрока.
position — положение плитки на доске. Это массив с двумя элементами, то есть координатами x и y и значениями от 0 до 3.
value — число на плитке.
mergeWith — необязательный идентификатор плитки, которая поглотит текущую. Если он существует, то плитка должна слиться с другой плиткой и исчезнуть.
Как создавать и объединять плитки
Как-то нужно отметить, что плитка изменилась после действия игрока. Думаю, лучший способ — изменить масштаб плитки. Изменение масштаба покажет, что была создана новая плитка или изменена другая:
Чтобы запустить анимацию, нужно рассмотреть два случая:
создаётся новая плитка — предыдущее значение будет равно null ;
плитка изменяет значение — предыдущее значение будет отличаться от текущего.
И вот результат:
Вы могли заметить, что я работаю с пользовательским хуком usePrevProps . Он помогает отслеживать предыдущие значения свойств компонента (props).
Я мог бы использовать ссылки, но они громоздкие, поэтому решил выделить код в отдельный хук ради читабельности и для того, чтобы использовать хук повторно. Если вы хотите задействовать его в своём проекте, просто скопируйте фрагмент ниже:
Как двигать плитки по доске
Без анимированного движения плиток по доске игра будет смотреться неаккуратно. Такую анимацию легко создать при помощи CSS-переходов. И удобнее всего будет воспользоваться свойствами позиционирования, например left и top . Изменим CSS таким образом:
Объявив стили, можно написать логику изменения положения плитки:
Как видите, выражение в positionToPixels должно знать положение плитки, общее количество плиток в строке и столбце, а ещё общую длину доски в пикселях. Вычисленное значение передаётся в элемент HTML как встроенный стиль. Но как же хук useBoard и свойство zIndex ?
Свойство useBoard позволяет получить доступ к свойствам доски внутри дочерних компонентов, не передавая их ниже. Чтобы найти нужное место на доске, компоненту Tile нужно знать ширину и общее количество плиток. Благодаря React Context API мы можем обмениваться свойствами между несколькими слоями компонентов, не загрязняя их свойства (props).
zIndex — это свойство CSS, которое определяет порядок расположения плиток. В нашем случае это id плитки. На рисунке ниже видно, что плитки могут укладываться друг на друга. Свойство zIndex позволяет указать, какая плитка находится наверху.
Как сделать доску
Другой важной частью игры является доска. За рендеринг сетки и плиток отвечает компонент Board. Кажется, что Board дублирует логику компонента Tile, но есть небольшая разница. В Board хранится информация о его размере (ширине и высоте), а также о количестве столбцов и строк. Это противоположно плитке, которая знает только собственную позицию:
Board использует BoardProvider для распределения ширины контейнера плитки и количества плиток в строке и столбце между всеми плитками и компонентом сетки:
Чтобы передать свойства всем дочерним компонентам, BoardProvider использует React Context API. Если какому-либо компоненту необходимо использовать некоторое доступное в провайдере значение, он может сделать это, вызвав хук useBoard .
Эту тему я пропушу: более подробно я рассказал о ней в своём видео о Feature Toggles в React. Если вы хотите узнать о них больше, вы можете посмотреть его:
Компонент Game
Теперь можно задать правила игры и раскрыть интерфейс для игры. Я собираюсь начать с навигации: это поможет вам понять, почему логика игры реализована именно так:
Как видите, логика игры будет обрабатываться хуком useGame , который представляет следующие свойства и методы:
tiles — это массив доступных на доске тайлов. Здесь используется TileMeta , речь о котором шла выше.
moveLeft перемещает все плитки на левую сторону доски.
moveRight сдвигает все плитки на правую сторону доски.
moveUp перемещает все плитки в верхнюю часть доски.
moveDown перемещает все плитки в нижнюю часть доски.
Мы работаем с колбеком throttledHandleKeyDown , чтобы предотвратить выполнение игроком множества движений одновременно.
Прежде чем игрок сможет вызвать другое движение, ему нужно дождаться завершения анимации. Этот механизм называется тормозящим (throttling) декоратором. Для него я решил использовать хук useThrottledCallback пакета use-debounce .
Как работать с useGame
Выше я упоминал, что компонент Game обрабатывает правила игры. Не хочется загромождать код, поэтому не будем записывать логиек непосредственно в компонент, а извлечём её в хук useGame . Этот хук основан на встроенном в React хуке useReducer . Начнём с определения формы состояния редюсера:
Состояние useReducer содержит следующие поля:
tiles — это хэш-таблица, отвечающая за хранение плиток. Она позволяет легко найти записи по их ключам, поэтому подходит идеально: находить плитки мы хотим по их идентификаторам.
byIds — это массив, содержащий все идентификаторы по возрастанию. Мы должны сохранить правильный порядок плиток, чтобы React не перерисовывал всю доску при каждом изменении состояния.
hasChange отслеживает изменения плиток. Если ничего не изменилось, новая плитка не создаётся.
inMotion указывает на то, движутся ли плитки. Если это так, то новая плитка не создаётся вплоть до завершения движения.
Экшены
useReducer требуется указать экшены, которые поддерживаются этим хуком:
За что отвечают эти экшены?
CREATE_TILE создаёт новую плитку и добавляет её в хэш-таблицу плиток. Флаг hasChange меняется на false : это действие всегда срабатывает при добавлении новой плитки на доску.
UPDATE_TILE обновляет существующую плитку; не изменяет её id , что важно для работы анимации. Воспользуемся этим экшеном, чтобы изменить положение плитки и её значение (во время слияния). Также UPDATE_TILE изменяет флаг hasChange на true .
MERGE_TILE объединяет исходную плитку и плитку назначения. После этой операции плитка назначения изменит своё значение, то есть к нему будет добавлено значение исходной плитки. Исходная плитка удаляется из таблицы плиток и массива byIds .
START_MOVE сообщает редюсеру, что он должен ожидать нескольких экшенов, а значит, должен дождаться завершения всех анимаций, прежде чем сможет сгенерировать новую плитку.
END_MOVE сообщает редюсеру, что все действия завершены, и он может создать новую плитку.
Логику этого редюсера вы можете написать самостоятельно или скопировать мою:
Если вы не понимаете, для чего мы определили эти экшены, не беспокойтесь — сейчас мы реализуем хук, который, я надеюсь, всё объяснит.
Как внедрить хук
Посмотрим на функцию, которая отвечает за ходы игрока. Сосредоточимся только на ходе влево: остальные ходы практически одинаковы.
Видно, что к функции хода я решил привязать два колбека. Эта техника называется инверсией управления — таким образом потребитель функции сможет подставлять в выполняемую функцию собственные значения.
Если вы не знаете, как работает bind , вам стоит узнать об этом. Вопрос об этом часто задают на собеседованиях.
Колбек retrieveTileIdsByRow отвечает за поиск всех доступных в ряду непустых плиток (для перемещений влево или вправо). Если игрок делает движения вверх или вниз, будем искать все плитки в столбце.
Колбек calculateFirstFreeIndex , находит ближайшую к границе доски позицию на основе заданных параметров, таких как индекс плитки, индекс плитки в ряду или в столбце, количество объединённых плиток и максимально возможный индекс.
Посмотрим на логику функции перемещения. Её код я объяснил в комментариях. Алгоритм может быть немного сложным, поэтому я решил, что построчные комментарии помогут его понять:
Код колбека RetrieveTileIdsByRowColumnCallback
Полный код useGame содержит более 400 строк.
Продолжить изучение современной веб-разработки вы сможете на наших курсах:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
package com.javarush.games.game2048 ; |
import com.javarush.engine.cell.Color ; |
import com.javarush.engine.cell.Game ; |
import com.javarush.engine.cell.Key ; |
public class Game2048 extends Game |
private static final int SIDE = 4 ; |
private int [][] gameField = new int [ SIDE ][ SIDE ]; |
private boolean isGameStopped; |
private int score = 0 ; |
// отрисовка |
private void win () |
isGameStopped = true ; |
showMessageDialog( Color . LAVENDER , " Ура, победа! Макисимум очков! " , Color . GOLD , 36 ); |
> |
private void gameOver () |
isGameStopped = true ; |
showMessageDialog( Color . LAVENDER , " всё, GAME OVER! гамовер! " , Color . RED , 36 ); |
> |
@Override |
public void initialize () |
// Создаем игровое поле 4x4 клетки |
setScreenSize( SIDE , SIDE ); |
createGame(); |
drawScene (); |
> |
private void createGame () |
// isGameStopped=false; |
gameField = new int [ SIDE ][ SIDE ]; |
createNewNumber(); |
createNewNumber(); |
score = 0 ; |
setScore( 0 ); |
// gameField[0][3] = 2; |
> |
private void createNewNumber () |
if (getMaxTileValue() >= 2048 ) |
win(); |
int x = 0 ; |
int y = 0 ; |
do |
x = getRandomNumber( SIDE ); |
y = getRandomNumber( SIDE ); |
> |
while ( gameField[x][y] != 0 ); |
int znach = getRandomNumber( 10 ); |
if (znach > 8 ) |
gameField[x][y] = 4 ; |
> |
else |
gameField[x][y] = 2 ; |
> |
> |
private Color getColorByValue ( int value ) |
// 0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048. |
switch (value) |
case 2 : |
return Color . PLUM ; |
case 4 : |
return Color . AQUA ; |
case 8 : |
return Color . GREEN ; |
case 16 : |
return Color . YELLOW ; |
case 32 : |
return Color . RED ; |
case 64 : |
return Color . PINK ; |
case 128 : |
return Color . PERU ; |
case 256 : |
return Color . PURPLE ; |
case 512 : |
return Color . GRAY ; |
case 1024 : |
return Color . BEIGE ; |
case 2048 : |
return Color . BLUE ; |
> |
return Color . WHITESMOKE ; |
> |
private void setCellColoredNumber ( int x , int y , int value ) |
Color color = getColorByValue(value); |
if (value == 0 ) |
setCellValueEx(x, y, color, " " ); |
else |
setCellValueEx(x, y, color, String . valueOf(value)); |
> |
private void drawScene () |
for ( int i = 0 ; i < SIDE ; i ++ ) |
for ( int j = 0 ; j < SIDE ; j ++ ) |
setCellColoredNumber(j, i, gameField[i][j] ); |
> |
> |
> |
// Движение |
private boolean compressRow ( int [] row ) |
boolean result = false ; |
for ( int j = 0 ; j < SIDE ;j ++ ) |
for ( int i = 1 ; i < SIDE ; i ++ ) |
// int temp = gameField[i-1]; |
if (row[i - 1 ] == 0 && row[i] != 0 ) |
int temp = row[i - 1 ]; |
row[i - 1 ] = row[i]; |
row[i] = temp; |
result = true ; |
> |
> |
> |
return result; |
> |
private boolean mergeRow ( int [] row ) |
boolean result = false ; |
for ( int i = 1 ; i < SIDE ; i ++ ) |
if (row[i - 1 ] == row[i] && row[i] != 0 ) |
row[i - 1 ] = row[i] * 2 ; |
score += row[i - 1 ]; |
setScore(score); |
row[i] = 0 ; |
result = true ; |
> |
> |
return result; |
> |
@Override |
public void onKeyPress ( Key key ) |
if (canUserMove() == false ) |
gameOver(); |
return ; |
> |
if (isGameStopped == true && key != Key . SPACE ) |
return ; |
switch (key) |
case LEFT : |
moveLeft(); |
drawScene(); |
break ; |
case UP : |
moveUp(); |
drawScene(); |
break ; |
case DOWN : |
moveDown(); |
drawScene(); |
break ; |
case RIGHT : |
moveRight(); |
drawScene(); |
break ; |
case SPACE : |
isGameStopped = false ; |
createGame(); |
drawScene (); |
break ; |
> |
> |
private void moveLeft () |
boolean change = false ; |
for ( int i = 0 ; i < SIDE ; i ++ ) |
if (compressRow(gameField[i])) change = true ; |
if (mergeRow(gameField[i])) change = true ; |
if (compressRow(gameField[i])) change = true ; |
> |
if (change) createNewNumber(); |
> |
private void moveRight () |
rotateClockwise(); |
rotateClockwise(); |
moveLeft(); |
rotateClockwise(); |
rotateClockwise(); |
> |
private void moveUp () |
rotateClockwise(); |
rotateClockwise(); |
rotateClockwise(); |
moveLeft(); |
rotateClockwise(); |
> |
private void moveDown () |
rotateClockwise(); |
moveLeft(); |
rotateClockwise(); |
rotateClockwise(); |
rotateClockwise(); |
> |
private void rotateClockwise () |
int [][] result = new int [ SIDE ][ SIDE ]; |
for ( int i = 0 ; i < SIDE ; i ++ ) |
for ( int j = 0 ; j < SIDE ; j ++ ) |
result[i][j] = gameField[ SIDE - j - 1 ][i]; |
> |
> |
gameField = result; |
> |
// вычисления |
private int getMaxTileValue () |
int maxValue = 0 ; |
for ( int i = 0 ; i < SIDE ; i ++ ) |
for ( int j = 0 ; j < SIDE ; j ++ ) |
if ( gameField[i][j] > maxValue) |
maxValue = gameField[i][j]; |
> |
> |
return maxValue; |
> |
private boolean canUserMove () |
boolean resault = false ; |
// Проверка что есть 0 |
for ( int i = 0 ; i < SIDE ; i ++ ) |
for ( int j = 0 ; j < SIDE ; j ++ ) |
if ( gameField[i][j] == 0 ) |
return true ; |
> |
> |
> |
// Проверка 2-х соседних |
for ( int i = 0 ; i < SIDE ; i ++ ) |
for ( int j = 1 ; j < SIDE ; j ++ ) |
if ( gameField[i][j] == gameField[i][j - 1 ]) |
return true ; |
> |
> |
> |
rotateClockwise(); |
for ( int i = 0 ; i < SIDE ; i ++ ) |
for ( int j = 1 ; j < SIDE ; j ++ ) |
if ( gameField[i][j] == gameField[i][j - 1 ]) |
return true ; |
> |
> |
> |
rotateClockwise(); |
rotateClockwise(); |
rotateClockwise(); |
return resault; |
> |
// удалить |
private void showRow ( int [] row ) |
for ( int i = 0 ; i < SIDE ; i ++ ) |
System . out . print(row[i]); |
System . out . println(); |
> |
> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to commentYou can’t perform that action at this time.
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.
Каждое логическое значение ИСТИНА / ЛОЖЬ в строках 2, 4, 6 и 8 используется для определения, что данные каждой ячейки в строках 1, 3, 5 и 7 равны 0 или нет.
Экспериментальная реализация
Все предложения приветствуются.
Переменные
Хотя это и не требуется явно, переменные в VBA могут иметь типы, как и в строго типизированных языках, таких как c++ . Включение типов в код дает два преимущества:
- Повышает производительность, поскольку типизированные переменные работают быстрее и занимают меньше памяти
- Возможно, что более важно, помогает документировать код; если мы знаем loop_num целое число или rand_num является плавающей точкой, то мы можем предположить некоторые вещи о том, для чего они могут быть использованы. Это значительно упрощает чтение, поддержку и улучшение кода.
Так что предпочитаю:
Пока мы это делаем, эти имена переменных не очень информативны, не так ли? Конечно loop_num говорит мне, что это, вероятно, увеличенная переменная в цикле for, но я уже вижу это, просто посмотрев! Лучше использовать описательные имена, которые сделают код самодокументированным и легким для понимания. Я действительно не знаю, для чего нужны все эти переменные, так как сейчас я сосредотачиваюсь на общих проблемах, но, возможно, что-то вроде:
Также в нескольких местах вы не объявляете переменные; помимо того, что вы не можете объявить тип, отказ от принудительного объявления переменных может привести к пропуску опечаток, loop_counter против loop_cuonter . Добавлять Option Explicit в верхней части модуля (модулей), чтобы принудительно объявить переменную и помочь вам обнаружить опечатки.
Магические числа
Добавление комментариев, объясняющих, почему ваш код делает то, что он делает, или, что еще лучше, переименование этих чисел в константы:
СУХОЙ
Не повторяйся; MergeUp/Down и MergeLeft/Right содержат много повторений, но с немного разными комбинациями A,B,C,D . Было бы лучше использовать их как аргументы для одного Sub, чтобы вы могли повторно использовать код для выполнения нескольких задач.
может стать примерно таким:
вы поняли (ps the Call ключевое слово как в Call rand_num устарело, оно вам больше не нужно, и это хорошо, чтобы удалить IMO, потому что это лишний беспорядок для вашего мозга)
Повторное использование кода ценно, поскольку это означает, что если вы измените логику, вы измените ее только в одном месте, что сделает ее менее подверженной ошибкам. Кроме того, меньшее количество кода для обработки, вероятно, упрощает интерпретацию кода для рецензентов и сопровождающих (через 6 месяцев), если сокращение не снижает читаемость (что в данном случае я не думаю)
В любом случае, это начальный первый проход, если вы, возможно, хотите получить больше отзывов о своей технике и алгоритме, я бы порекомендовал максимально привести ваш код в порядок с помощью некоторых из вышеперечисленных методов и опубликовать следующий вопрос.
Надеюсь, что это поможет, дайте мне знать, если вам нужны разъяснения (PS, я не уверен, сколько из этого будет для вас новым, поскольку я вижу, что вы уже задали много вопросов по CR, и это довольно общий / базовый совет, но я понимаю, что если вы просто балуетесь новым языком, вы можете потратить большую часть своего времени, пытаясь заставить его работать, и забыть некоторые важные стилистические основы!)
Читайте также: