Веб-программирование → Делаем игру с Роботом на jQuery, HTML, CSS

Предыдущую классную работу все выполнили? Мне скинули всего 3-4 человека 😏. 

Ребята, спрашивайте если что-то не понятно! На этой неделе меня не будет, так что вам придется самим поработать. Эта работа небольшая. Будем делать маленького робота. 🤖 А чем он будет заниматься, я по ходу щас придумаю.

Здесь нам обязательно пригодится jQuery, которую можно скачать здесь (compressed production). Откройте ваш любимый редактор кода. Или скачайте: Brackets, Sublime Text, PyCharm

Приступим..

Создаем три пустых файла classwork3.html, classwork3.css, classwork3.js. В classwork3.html подключаем стили и скрипты:

<html>
<head>
    <meta charset="UTF-8">
    <title>Робот!</title>
    <link rel="stylesheet" href="classwork3.css">
    <script src="jquery-3.2.1.min.js"></script>
    <script src="classwork3.js"></script>
</head>
<body>

</body>
</html>

 Все подключаемые файлы должны быть в одной папке. 

Так, теперь что у нас будет. Будет поле прямоугольное, состоящее из ячеек. Ячейка может быть пустой или заполнена чем-то: например стеной, деревом или миной. Будем случайным образом заполнять поле. Робот должен пройти невредимым до пункта назначения. Будем управлять роботом клавиатурой и кнопками на экране.

Сейчас найдем в инете несколько картинок для робота, для выхода, для стен, для деревьев и для мин. 

    

Создаем папку img и закидываем все эти картинки в эту папку. 

Игровое поле

Поле у нас будет размером 20 на 15 ячеек. Для игрового поля будем использовать class="game-field". Размер одной ячейки 40х40 пикселей. Для ячеек будем использовать class="cell". 

Если 20 ячеек по ширине и 15 ячеек по высоте и каждая по 40 пикселей, то поле у нас будет размером 800х600 пикселей.   Добавим стили для игрового поля в файле classwork3.css:

.game-field{
    width: 800px;
    height: 600px;
    margin-left: auto;
    margin-right: auto;
    margin-top: 10px;
    box-shadow: 0 0 5px grey;
}

Мы также добавили автоматические отступы слева и справа, чтобы блок был посередине экрана. Отступ сверху на 10 пикселей и тень вокруг блока. Теперь если мы внутри тега <body> добавим блок с классом game-field, то увидим на странице большой прямоугольник с тенью. 

<body>
    <div class="game-field">

    </div>
</body>

Откройте вашу страницу и посмотрите на прямоугольник с тенью. 

Нам нужно теперь расставить внутри поля ячейки. Ячейки мы будем создавать с помощью javascript. Сделаем двойной цикл и с помощью команды document.write() добавим html элементы внутри блока game-field. У каждой ячейки должен быть id соответствующий ее координатам, поэтому мы будем добавлять id в формате "cell_x_y" - где вместо x будет координата по x, а вместо y будет координата по y. 

Помните как обрабатывается javascript ? Браузер загружает страницу сверху вниз, и когда доходит до тега <script>, выполняет то, что внутри него и идет дальше. Поэтому мы положим наш скрипт внутри блока с классом game-field.

<body>
    <div class="game-field">
        <script>
            for (var y=0; y<15; y++){
                for (var x=0; x<20; x++){

                }
            }
        </script>
    </div>
</body>

Двойной цикл: внешний цикл(y) идет по строкам, внутренний(x) по столбцам. Внутри цикла мы будем добавлять в документ html код ячеек. Сначала в переменную сгенерируем id для ячейки в формате "cell_x_y":

var cell_id = "cell_"+x+"_"+y;

Потом добавим тег <div> с классом "cell" и c id равным значению переменной cell_id:

document.write("<div class='cell' id='"+cell_id+"'></div>");

И в итоге у нас получится такой код:

<body>
    <div class="game-field">
        <script>
            for (var y=0; y<15; y++){
                for (var x=0; x<20; x++){
                    var cell_id = "cell_"+x+"_"+y;
                    document.write("<div class='cell' id='"+cell_id+"'></div>");
                }
            }
        </script>
    </div>
</body>

Если сейчас обновите вашу страницу, на экране ничего не изменится, но в Инспекторе во вкладке Elements вы должны увидеть много  таких элементов:

<div class="cell" id="cell_0_0"></div>
<div class="cell" id="cell_1_0"></div>
<div class="cell" id="cell_2_0"></div>
...

Давайте теперь зададим стиль для ячейки: Размер ячейки 40х40. Также добавим стиль display: inline-block чтобы ячейка вела себя как строчный элемент, заполняя всю ширину родительского элемента. Еще в ячейках будут установлены разные картинки как background-image. А картинки у нас могут быть разных размеров. Поэтому, чтобы они помещались в ячейке полностью, добавляем стиль background-size: 40px. Итак добавим это в файл classwork3.css:

.cell{
    width: 40px;
    height: 40px;
    display: inline-block;
    background-image: url("img/tree.png");
    background-size: 40px 40px;
}

Также я для примера добавил фоновое изображение дерева. Если сохранив файл, вы обновите страницу, то увидите 300 деревьев на игровом поле. 

Заполнение игрового поля

Игровое поле и ячейки у нас есть. Теперь нам нужно случайным образом заполнить эти ячейки. Для этого мы откроем файл classwork3.js наконец-то и добавим туда функцию onReady() и скажем документу, чтобы он вызывал эту функцию когда он полностью загрузится:

function onReady(){

}

$(document).ready(onReady);

Теперь внутри функции onReady() мы должны заполнить ячейки. Для хранения состояния ячеек игрового поля и функций связанных с полем, мы создадим специальный объект. И назовем его GameField. Объекты, которые не только хранят какие-то данные, но и имеют свои функции я называю CamelCase-ом. 

Пусть в этом объекте у нас будет свойства width(ширина), height(высота) и cells(ячейки). cells будет двумерным массивом, в котором мы будем хранить состояние игрового поля. Добавим перед функцией onReady() код:

var GameField = {
    width: 20,
    height: 15,
    cells: [],
}

Теперь в этот объект добавим метод init(), который будет заполнять ячейки случайным образом. Значениями ячеек будут у нас будут текстовые значения "tree" - для дерева, "exit" - для выхода, "empty" - пустая ячейка, "wall" - стена, "bomb" - бомба.

Чтобы каждый раз эти значения не писать как текст, создадим константы и добавим их в начале файла classwork3.js:

const WALL = "wall";
const EMPTY = "empty";
const TREE = "tree";
const BOMB = "bomb";
const EXIT = "exit";


var GameField = {
    width: 20,
    height: 15,
    cells: [],
}

Теперь вместо того чтобы писать "wall", мы будем использовать константу WALL. Это предотвращает ошибки при частос использовании одного и того же значения. 

Внутри метода init() мы запустим двойной цикл, чтобы заполнить все ячейки. По умолчанию все ячейки будут пустыми. (EMPTY):

var GameField = {
    width: 20,
    height: 15,
    cells: [],

    init: function () {
        for (var x = 0; x < GameField.width; x++) {
            GameField.cells[x] = [];

            for (var y = 0; y < GameField.height; y++) {
                GameField.cells[x][y] = EMPTY;
            }
        }
    }
}

сначала идет цикл по ширине поля (по х). от 0 до 20(width). И при этом в переменную cells мы добавляем внутренний массив на каждое значения х. Потом идет внутренний цикл по высоте поля(по y) от 0 до 15(height). И при этом в ячейку с с координатами x,y мы пишем значение EMPTY.

Если вы в голове можете представить двумерный массив, то получится примерно так:

    0     1     2     3     4     5   ... x
 
0  empty empty empty empty empty empty    

1  empty empty empty empty empty empty    

2  empty empty empty empty empty empty    

3  empty empty empty empty empty empty    

4  empty empty empty empty empty empty    

.

y

Теперь будем заполнять стенами, деревьями и бомбами. 

Сделаем вокруг поля стены. Для этого внутри цикла сделаем проверку. Если х или у равны 0 или крайним значеням поля, то значит это край поля, там должна быть стена:

if(x==0 || y==0 || x==GameField.width-1 || y==GameField.height-1){
    GameField.cells[x][y] = WALL;
}

А если это не край поля, то случайным образом выберем чем заполнять: Сгенерируем слуйчайное чилсло от 0 до 100. Если это число меньше 30 (30% вероятность), то пусть там будем стена. Если слуйчайно число больше 30 и меньше 50 (20% вероятность), то пусть там будет дерево. Если случайное число больше 50 и меньше 60 (10% вероятность), то пусть там будет бомба. 

if(x==0 || y==0 || x==GameField.width-1 || y==GameField.height-1){
    GameField.cells[x][y] = WALL;
} else{
    var random = Math.random()*100;
    if(random < 30){
        GameField.cells[x][y] = WALL;
    } else if(random<50) {
        GameField.cells[x][y] = TREE;
    } else if(random<60) {
        GameField.cells[x][y] = BOMB;
    }
}

так мы заполнили почти все поле, 40% поля осталось пустым. Теперь случайным образом сгерируем координаты выхода. Выход не должен быть на краю, поэтому добавляем 1+ и -2:

var exit_x = 1 + Math.round(Math.random()*(GameField.width -2));
var exit_y = 1 + Math.round(Math.random()*(GameField.height -2));

GameField.cells[exit_x][exit_y] = EXIT;

А по координатам 1,1  всегда будет робот, поэтому оставим это место гарантированно пустым:

GameField.cells[1][1] = EMPTY; // здесь будет робот

И так, в итоге у нас получился такой метод init:

var GameField = {
    width: 20,
    height: 15,
    cells: [],

    init: function () {
        for (var x = 0; x < GameField.width; x++) {
            GameField.cells[x] = [];

            for (var y = 0; y < GameField.height; y++) {
                GameField.cells[x][y] = EMPTY;

                if(x==0 || y==0 || x==GameField.width-1 || y==GameField.height-1){
                    GameField.cells[x][y] = WALL;
                } else{
                    var random = Math.random()*100;
                    if(random < 30){
                        GameField.cells[x][y] = WALL;
                    } else if(random<50) {
                        GameField.cells[x][y] = TREE;
                    } else if(random<60) {
                        GameField.cells[x][y] = BOMB;
                    }
                }
            }
        }

        var exit_x = 1 + Math.round(Math.random()*(GameField.width -2));
        var exit_y = 1 + Math.round(Math.random()*(GameField.height -2));

        GameField.cells[exit_x][exit_y] = EXIT;
        GameField.cells[1][1] = EMPTY; // здесь будет робот
    },
}

Игровое поле сгенерировано. Теперь отобразим это на странице. Для этого создадим еще одну функция в объекте GameField с названием show:

var GameField = {
    width: 20,
    height: 15,
    cells: [],

    ....

    show: function(){
        for (var x = 0; x < GameField.width; x++) {
            for (var y = 0; y < GameField.height; y++) {
                if(GameField.cells[x][y] == WALL){
                    $("#cell_"+x+"_"+y).css("background-image", "url('img/wall.png')");
                } else if(GameField.cells[x][y] == TREE){
                    $("#cell_"+x+"_"+y).css("background-image", "url('img/tree.png')");
                } else if(GameField.cells[x][y] == BOMB){
                    $("#cell_"+x+"_"+y).css("background-image", "url('img/bomb.png')");
                } else if(GameField.cells[x][y] == EXIT){
                    $("#cell_"+x+"_"+y).css("background-image", "url('img/exit.png')");
                }
            }
        }
    }
};

Как вы видите, тут тоже двойной цикл по ячейкам. Проверяем каждую ячейку: если значение ячейки равна значению константы WALL, то по этим координатам есть стена. Чтобы отобразить там стену, возьмем ячейку с id "cell_x_y" где вместо x и y координаты ячейки. У этой ячейке поменяем стиль background-image на "url('img/wall.png')".

И так со всеми типами ячеек. 

У нас все готово для отображения. Теперь чтобы все это запустить, в функции onReady(), сделаем вызов двух методов: init() и show() по порядку:

function onReady(){
    GameField.init();
    GameField.show();
}

Сохраняем файл и обновляем страницу(Ctrl+Shift+R) и видим что игровое поле красиво заполнилось. 

И кстати еще в CSS файле уберите стиль background-image: url("img/tree.png"); с класса .cell .

Робот

Давайте теперь добавим самого робота. Робот будет у нас элементом с id="robot" внутри game-field:

<body>
    <div class="game-field">
        <div id="robot"></div>
    ...

Он будет отдельным слоем, т.е. отображаться под слоем ячеек. Для этого добавим стиль position: relative; для класса .game-field и следующие стили для робота:

#robot{
    position: absolute;
    background-size: 40px 40px;
    background-image: url("img/robot.png");
    z-index: -1;
    width: 40px;
    height: 40px;
    left: 40px;
    top: 40px;
}

position: absolute означает что местоположение робота будет относительно родителя и независимыми от других элементов, а координаты будут задаваться стилями top и left. Вначале координаты робота 40px, 40px - это координаты ячейки 1, 1. Стиль z-index: -1 означает чтобы данный элемент будет находиться под другими обычными элементами. 

Обновив страницу вы увидите робота теперь.

Движение робота

Чтобы двигать роботом будем использовать клавиатуру. Для начала создадим объект Robot, в котором будем хранить координату робота и функции для движения робота. 


var Robot = {
    x: 1,
    y: 1,
    show: function(){
        var $robot = $("#robot");
        $robot.css("left", Robot.x*40);
        $robot.css("top", Robot.y*40);
    },

    moveDown: function(){
        Robot.y += 1;
        Robot.show();
    },

    moveUp: function(){
        Robot.y -= 1;
        Robot.show();
    },

    moveLeft: function(){
        Robot.x -= 1;
        Robot.show();
    },

    moveRight: function(){
        Robot.x += 1;
        Robot.show();
    }
};

Методы движения меняют одну координату робота и вызывают метод show(). Метод show() берет элемент робота и меняет его стили left и top соответственно координатам робота. Умножаем на 40, потому что размер одной ячейки 40 пикселей. 

Теперь если вы в Консоли напишете Robot.moveDown() то робот переместится вниз и т.д. 

Нам нужно добавить управление через клавиатуру. Для этого мы должны отлавливать события клика и если кликнуты нужные клавиши, двигать робота. Для этого будем использовать событие "keyup" у объекта document, то есть всей страницы. Это событие происходит когда клавиша отпускается. 

$(document).on("keyup", function(event){
    var key = event.keyCode;
});

функция-обработчик принимает один параметр(event), у которого есть свойство keyCode, в котором хранится код нажатой клавиши. Вы можете проверить коды разных клавиш, выводя их в Консоль:

$(document).on("keyup", function(event){
    var key = event.keyCode;
    console.log(event.keyCode);
});

мы будем использовать клавиши стрелки. Их коды: 37, 38, 39, 40. Создадим константы для них.


const KEY_LEFT = 37;
const KEY_UP = 38;
const KEY_RIGHT = 39;
const KEY_DOWN = 40;

Теперь внутри функции-обработчика будем проверять нажатую клавишу и вызывать соответствующий метод движения робота. Для этого создадим объект Keyboard с функцией init():

var Keyboard = {
    init: function(){
        $(document).on("keyup", function(event){
            var key = event.keyCode;
            switch(key){
                case KEY_DOWN: Robot.moveDown(); break;
                case KEY_UP: Robot.moveUp(); break;
                case KEY_LEFT: Robot.moveLeft(); break;
                case KEY_RIGHT: Robot.moveRight(); break;
            }
        })
    }
};

и теперь вызовем функцию init() внутри функции onReady():


function onReady(){
    Keyboard.init();
    GameField.init();
    GameField.show();
}

Сохраняем и обновляем страницу и двигаем роботом клавиатурой.

На этом все, если у кого не так работает. То сравните ваш код:

Файл classwork3.html:

<html>
<head>
    <meta charset="UTF-8">
    <title>Робот!</title>
    <link rel="stylesheet" href="classwork3.css">
    <script src="jquery-3.2.1.min.js"></script>
    <script src="classwork3.js"></script>
</head>
<body>
    <div class="game-field">
        <div id="robot"></div>
        <script>
            for (var y=0; y<15; y++){
                for (var x=0; x<20; x++){
                    var cell_id = "cell_"+x+"_"+y;
                    document.write("<div class='cell' id='"+cell_id+"'></div>");
                }
            }
        </script>
    </div>
</body>
</html>

Файл: classwork3.css:

.game-field{
    width: 800px;
    height: 600px;
    margin-left: auto;
    margin-right: auto;
    margin-top: 10px;
    box-shadow: 0 0 5px grey;
    position: relative;
}

.cell{
    width: 40px;
    height: 40px;
    display: inline-block;
    background-size: 40px 40px;
}

#robot{
    position: absolute;
    background-size: 40px 40px;
    background-image: url("img/robot.png");
    z-index: -1;
    width: 40px;
    height: 40px;
    left: 40px;
    top: 40px;
}

Файл classwork3.js:

const WALL = "wall";
const EMPTY = "empty";
const TREE = "tree";
const BOMB = "bomb";
const EXIT = "exit";

const KEY_LEFT = 37;
const KEY_UP = 38;
const KEY_RIGHT = 39;
const KEY_DOWN = 40;

var GameField = {
    width: 20,
    height: 15,
    cells: [],

    init: function () {
        for (var x = 0; x < GameField.width; x++) {
            GameField.cells[x] = [];

            for (var y = 0; y < GameField.height; y++) {
                GameField.cells[x][y] = EMPTY;

                if(x==0 || y==0 || x==GameField.width-1 || y==GameField.height-1){
                    GameField.cells[x][y] = WALL;
                } else{
                    var random = Math.random()*100;
                    if(random < 30){
                        GameField.cells[x][y] = WALL;
                    } else if(random<50) {
                        GameField.cells[x][y] = TREE;
                    } else if(random<60) {
                        GameField.cells[x][y] = BOMB;
                    }
                }
            }
        }

        var exit_x = 1 + Math.round(Math.random()*(GameField.width -2));
        var exit_y = 1 + Math.round(Math.random()*(GameField.height -2));

        GameField.cells[exit_x][exit_y] = EXIT;
        GameField.cells[1][1] = EMPTY; // здесь будет робот
    },

    show: function(){
        for (var x = 0; x < GameField.width; x++) {
            for (var y = 0; y < GameField.height; y++) {
                if(GameField.cells[x][y] == WALL){
                    $("#cell_"+x+"_"+y).css("background-image", "url('img/wall.png')");
                } else if(GameField.cells[x][y] == TREE){
                    $("#cell_"+x+"_"+y).css("background-image", "url('img/tree.png')");
                } else if(GameField.cells[x][y] == BOMB){
                    $("#cell_"+x+"_"+y).css("background-image", "url('img/bomb.png')");
                } else if(GameField.cells[x][y] == EXIT){
                    $("#cell_"+x+"_"+y).css("background-image", "url('img/exit.png')");
                }
            }
        }
    }
};

var Robot = {
    x: 1,
    y: 1,
    show: function(){
        var $robot = $("#robot");
        $robot.css("left", Robot.x*40);
        $robot.css("top", Robot.y*40);
    },

    moveDown: function(){
        Robot.y += 1;
        Robot.show();
    },

    moveUp: function(){
        Robot.y -= 1;
        Robot.show();
    },

    moveLeft: function(){
        Robot.x -= 1;
        Robot.show();
    },

    moveRight: function(){
        Robot.x += 1;
        Robot.show();
    }
};

var Keyboard = {
    init: function(){
        $(document).on("keyup", function(event){
            var key = event.keyCode;
            switch(key){
                case KEY_DOWN: Robot.moveDown(); break;
                case KEY_UP: Robot.moveUp(); break;
                case KEY_LEFT: Robot.moveLeft(); break;
                case KEY_RIGHT: Robot.moveRight(); break;
            }
        })
    }
};

function onReady(){
    Keyboard.init();
    GameField.init();
    GameField.show();
}

$(document).ready(onReady);

 

Задание домой:

Разберитесь хорошо в этом коде, попробуйте сделать так, чтобы робот не мог идти по стенам, и если он наступает на бомбу, то пусть игра пусть робот взрывается, а если доходит до двери, пусть игра заканчивается. 

Также добавьте кнопки на экране для управления роботом, для перегенерации игрового поля. 

Или сделайте что-нибудь другое, похожее, крутое. 

Удачи!

826 4
Alisher Alikulov