Футбол. Для многих людей, живущих на земле, эта игра - больше, чем просто спорт. И эти люди всегда мечтают стать футболистом или менеджером. Но, к сожалению, большинство из них не могут подняться по лестнице успеха, и эти идеи застревают на стадии мечты. В этот момент на помощь приходят футбольные игры, которые помогают им удовлетворить свои желания.
Итак, что самое главное в футбольной игре? Графика? Может быть. Лицензированные команды и игроки? Безусловно, но есть еще одна вещь, которая важнее этого: движок игры. Да, как вы уже догадались, без самой игры все остальные вещи не имеют смысла. И сегодня я покажу, как сделать движок для футбольного матча.
Однако, прежде чем мы начнем, я хочу отметить, что это не самая лучшая идея - строить игровую механику с нуля. С другой стороны, для простенького проекта этого будет вполне достаточно.
И последнее, я хочу сообщить вам, что во время объяснения я буду использовать Java.
В процессе создания движка я создал и использовал два пакета и четыре класса. Эти пакеты - Action и General.
Пакет Action состоит из классов Scorers и Scoring. Класс Scoring используется для получения точного результата матча. Класс Scorers используется для определения того, какие игроки и в какое время забили эти голы.
Пакет General состоит из классов MatchRunner и TeamData. Класс TeamData используется для сбора в одном объекте информации, относящейся как к домашней, так и к гостевой командам. Класс MatchRunner используется для моделирования матча. Это одна из основных функций движка.
TeamData - это неизменяемый класс. Он имеет пять приватных переменных. Это игроки, атака, защита, avgDefence и avgAttack.
private ArrayList<String> players; private ArrayList<Integer> attack, defence; private double avgDefence, avgAttack;
Конструктор принимает три параметра. Это игроки, атака, защита.
public TeamData(ArrayList<String> players, ArrayList<Integer> attack, ArrayList<Integer> defence) { this.players = players; this.attack = attack; this.defence = defence; average(); }
Переменная "players" содержит имена игроков команды, которые играют в матче. Переменные "атака" и "защита" содержат данные о способностях игроков атаковать и защищаться.
Кроме того, переменные "avgAttack" и "avgDefence" содержат средние показатели атаки и защиты команды. При инициализации им присваивается значение null, но после получения необходимой информации для их расчета внутри конструктора вызывается функция average.
private void average() { double total = 0; for (Integer p: defence) total += p; avgDefence = total / defence.size(); total = 0; for (Integer p: attack) total += p; avgAttack = total / attack.size(); }
Внутри функции среднего значения поочередно вычисляются avgAttack и avgDefence. Чтобы найти среднее значение, необходимо получить сумму способностей. Затем она делится на количество игроков.
Кроме того, класс TeamData включает методы getter, позволяющие получать данные извне класса.
public double getAvgDefence() {return avgDefence;} public double getAvgAttack() {return avgAttack;} public ArrayList<String> getPlayers() {return players;} public ArrayList<Integer> getAttack() {return attack;}
Класс Scoring имеет две частные переменные. Это home и away и их тип данных - TeamData. Они присваиваются внутри конструктора.
public Scoring(TeamData home, TeamData away) { this.home = home; this.away = away; }
Метод score является ядром симуляции. Потому что он вычисляет, сколько голов забила команда за матч.
public int score(char team) { int diff = 1; if (team == 'h') { //home advantage diff++; if (home.getAvgAttack() > away.getAvgDefence()) diff += (int) (home.getAvgAttack() - away.getAvgDefence()); } else if (team == 'a') { if (away.getAvgAttack() > home.getAvgDefence()) diff += (int) (away.getAvgAttack() - home.getAvgDefence()); } return testChance(diff); }
Чтобы сделать это возможным, сначала счет определяет, сколько шансов у команды сделать гол. Эта переменная шанса названа в методе diff, и у каждой команды есть хотя бы один шанс.
int diff = 1;
Кроме того, если команда является домашней, она имеет один дополнительный шанс на гол благодаря преимуществу стадиона.
if (team == 'h') { //home advantage diff++;
Окончательное значение diff вычисляется путем прибавления к diff результата вычитания из средней атаки команды соперника по очкам к средней защите команды соперника (конечно, если средняя атака больше средней защиты).
if (team == 'h') { //home advantage diff++; if (home.getAvgAttack() > away.getAvgDefence()) diff += (int) (home.getAvgAttack() - away.getAvgDefence()); } else if (team == 'a') { if (away.getAvgAttack() > home.getAvgDefence()) diff += (int) (away.getAvgAttack() - home.getAvgDefence()); }
Наконец, вызывается функция testChances и возвращается окончательный результат.
return testChance(diff);
Функция testChances утверждает, что шанс стать голом составляет 40%.
private int testChance(int chances) { int goals = 0; for (int i = 0; i < chances; i++) { int faith = new Random().nextInt(5); if (faith >= 3) goals++; } return goals; }
Класс Scorer состоит из четырех частных переменных. Это home, away, scorerPlayers и minutes.
private TeamData home, away; private ArrayList<ArrayList<String>> scorerPlayers; private ArrayList<ArrayList<Integer>> minutes;
Эти переменные присваиваются внутри конструктора.
public Scorers(int[] results, TeamData home, TeamData away) { this.home = home; this.away = away; scorerPlayers = new ArrayList<>(); minutes = new ArrayList<>(); scorerPlayers.add(new ArrayList<>()); scorerPlayers.add(new ArrayList<>()); minutes.add(new ArrayList<>()); minutes.add(new ArrayList<>());
Переменные scorerPlayers и minutes хранят внутри себя две коллекции ArrayList. Коллекция с индексом 0 предназначена для домашней команды, а следующая коллекция - для информации о гостевой команде.
Затем, если результаты не равны 0, внутри конструктора запускается процесс уточнения.
if (results[0] != 0) { getGoalers(false, results[0]); setMinutes(false, results[0]); } if (results[1] != 0) { getGoalers(true, results[1]); setMinutes(true, results[1]); } }
Метод getGoalers используется для определения игроков, которым были забиты голы. Он принимает параметр в виде количества голов и возвращает функцию who внутри цикла for это количество раз.
private void getGoalers(boolean isAway, int score) { for (int i = 0; i < score; i++) { who(isAway); } }
В начале метод who получает сумму всех возможностей.
int total = getTotalPossibility(isAway);
Метод getTotalPossibility получает данные об атаке домашней или гостевой команды в соответствии с переменной isAway. Затем он возвращает общее значение атакующих возможностей команды.
private int getTotalPossibility(boolean isAway) { int total = 0; ArrayList<Integer> valueList; if (!isAway) valueList = home.getAttack(); else valueList = away.getAttack(); for (Integer p: valueList) total += p; return total; }
После получения суммарной возможности метод who получает списки игроков и атакующих способностей в соответствии с переменной isAway.
private void who(boolean isAway) { int total = getTotalPossibility(isAway); ArrayList<String> playerList; ArrayList<Integer> attack; if (!isAway) { playerList = home.getPlayers(); attack = home.getAttack(); } else { playerList = away.getPlayers(); attack = away.getAttack(); }
Затем он выбирает случайное значение от 1 до total.
int point = new Random().nextInt(total) + 1;
В этой точке продумывается линейная линия и на ней размещаются узлы, которые разделяют идентификационные диапазоны игроков. Эти диапазоны определяются с учетом их атакующих способностей. В результате, в каком бы диапазоне не находилась точка, игрок, имеющий этот диапазон, считается забившим.
int ceil = 0; int floor = 0; for (int i = 0; i < attack.size(); i++) { ceil += attack.get(i); if (i != 0) floor += attack.get(i-1); if (point > floor && point <= ceil) { int loc = 0; if (isAway) loc = 1; scorerPlayers.get(loc).add(playerList.get(i)); break; } }
Допустим, есть три игрока - игрок1, игрок2 и игрок3. Их способности к атаке равны 40, 50 и 60.
Если 0 < point ≤ 40, то бомбардиром является игрок1.
Если 40 < очков ≤ 90, то бомбардиром является игрок2.
Если 90 < point ≤ 150, то бомбардиром является игрок3.
После определения бомбардиров вычисляется время гола. Для этого вызывается метод setMinutes.
private void setMinutes(boolean isAway, int score) { ArrayList<Integer> goals = getOrderedMinutes(getRawMinutes(score)); if (!isAway) for (Integer g: goals) minutes.get(0).add(g); else for (Integer g: goals) minutes.get(1).add(g); }
Сначала функция setMinutes вызывает метод getRawMinutes, чтобы получить время гола от 0 до 90.
private ArrayList<Integer> getRawMinutes(int score) { ArrayList<Integer> rawMinutes = new ArrayList<>(); for (int i = 0; i < score; i++) rawMinutes.add(new Random().nextInt(90) + 1); return rawMinutes; }
Затем вызывается функция getOrderedMinutes, чтобы отсортировать их от наименьшего к наибольшему.
private ArrayList<Integer> getOrderedMinutes(ArrayList<Integer> raw) { ArrayList<Integer> ordered = new ArrayList<>(); ArrayList<Integer> rawEdited = raw; while (rawEdited.size() > 0) { int min = getMin(raw); ordered.add(min); int id = getMinID(min, rawEdited); rawEdited.remove(id); } return ordered; }
Функция getOrderedMinutes использует метод getMin, чтобы найти текущий наименьший элемент в списке.
private int getMin(ArrayList<Integer> raw) { int min = Integer.MAX_VALUE; for (Integer r: raw) if (r < min) min = r; return min; }
Функция getOrderedMinutes также использует метод getMinID для удаления текущего наименьшего элемента в списке.
private Integer getMinID(int number, ArrayList<Integer> raw) { Integer id = null; for (int i = 0; i < raw.size(); i++) { if (number == raw.get(i)) { id = i; break; } } return id; }
Класс MatchRunner имеет пять приватных переменных. Это счет, бомбардиры, минуты, дома и в гостях.
private int[] score; private ArrayList<ArrayList<String>> scorers; private ArrayList<ArrayList<Integer>> minutes; private TeamData home, away;
Конструктор используется для присвоения переменных score (как null), home и away.
public MatchRunner(TeamData home, TeamData away) { score = new int[2]; this.home = home; this.away = away; }
Метод run внутри класса MatchRunner управляет всеми операциями матча. Классы Scoring и Scorers вызываются внутри функции run.
public void run() { score[0] = new Scoring(home, away).score('h'); score[1] = new Scoring(home, away).score('a'); Scorers scorer = new Scorers(score, home, away); scorers = scorer.getScorers(); minutes = scorer.setMinutes(); }
Кроме того, класс MatchRunner включает методы getter и setter для манипулирования переменными и получения результатов извне класса.
public void setHome(TeamData home) {this.home = home;} public void setAway(TeamData away) {this.away = away;} public int[] getScore() {return score;} public ArrayList<String> getScorers(boolean isAway) { if (!isAway) return scorers.get(0); else return scorers.get(1); } public ArrayList<Integer> getMinutes(boolean isAway) { if (!isAway) return minutes.get(0); else return minutes.get(1); }
Я выложил исходный код на GitHub. Вы можете посмотреть или развить его по своему усмотрению. Вот ссылка .
Также, вы можете скачать jar файл здесь .
Используется для создания объекта команды дома/в гостях.
Параметры конструктора
Внутри него находится функция run.
Параметры конструктора
Public void run
Это начальная функция для симуляции матча.
Public int[ ] getScore
Он возвращает счет матча. Длина массива равна двум. int[0] хранит голы хозяев, а int[1] - голы гостей.
Public ArrayList<String> getScorers (boolean isAway)
Возвращает голы, забитые домашней / гостевой командой в соответствии с переменной isAway.
Public ArrayList<Integer> getMinutes (boolean isAway)
Он возвращает минуты голов для команды дома/в гостях в соответствии с переменной isAway.
Public void setHome (TeamData home)
Изменяет переменную home.
Public void setAway (TeamData away)
Изменяет переменную away.
Футбол - сложная игра, и нет сомнений, что реализовать ее в компьютерных играх - дело непростое. Но и простое применение игры не является сложным.
В этом примере фолы, красные/желтые карточки, офсайд, свободный удар и т.д. не были добавлены, чтобы не усложнять ситуацию. На данный момент любой может добавить их в движок и управлять ими по своему усмотрению. Вот код, вот возможность. Остальное зависит от вас.
20.08.2023 18:21
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в 2023-2024 годах? Или это полная лажа?".
20.08.2023 17:46
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
19.08.2023 18:39
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в частности, магию поплавков и гибкость flexbox.
19.08.2023 17:22
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для чтения благодаря своей простоте. Кроме того, мы всегда хотим проверить самые последние возможности в наших проектах!
18.08.2023 20:33
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий их языку и культуре.
14.08.2023 14:49
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип предназначен для представления неделимого значения.