📜 ⬆️ ⬇️

35-kilobyte post-nuclear caravan

35 kilobytes of minified code in regular JavaScript, seven cities, wastelands, radioactive geckos, edible cacti, oncoming caravans and gangsters. What is it? This is a small game that runs in a browser. Its principles are quite simple to repeat and in the most primitive version it can be recreated, probably, on any device, if there is an output device and a random number generator. But today I want to tell you how I implemented it for modern browsers.

In the current version, the player controls a caravan that travels between cities after a nuclear war. Krotokrysy steal food stocks, and radioactive geckos kill the Brahmin. Thieves can track down the caravan and steal some of the money, and in the desert, radioactive ghouls, raiders and other bandits can attack.

The whole setting, the world and the logic can be modified as you like - the source code and graphics are distributed as a public domain, that is, without restrictions on copying and use.
')


Game features


Your trackers can find edible cactus or pre-war canned food. In the desert you can find the corpses of other travelers, from which you can raise some money. You can buy food or brahmins from oncoming caravans, as well as hire people. And you can be joined by oncoming tramps, and sometimes - even gangsters, if you hit them with your charisma. Joke. In fact, the charisma in the game yet, it is emulated randomly.

This game is, in fact, the next step in the evolution of text games, to which we added navigation around the world map, random encounters, and the ability to add any of our modules in JavaScript.

The functionality can be easily expanded, charisma and other real role factors add themselves. Even if you do not want to program - you can simply open files with a set of events in Notepad and add new events, change the balance or even completely rewrite the world and ENT, turning the journey through the post-nuclear desert into the adventures of Buttercup caravan in the land of elves. Or in the wanderings of a spaceship between different star systems (although it is necessary to throw away edible cacti that come across on the way).

The mechanics of this game are quite simple, and its graphic capabilities are dialogues, indicators and movement around the world map. Her strength lies in the magic of text-based games, which are capable of using words to describe the most incredible adventures.

The main idea and logic of the program


The basic idea of ​​the game is very simple - we run an infinite loop, inside which four basic operations are performed:

  1. Movement to a given point
  2. Countdown days
  3. Food consumption
  4. Check for probability for the event that awaits us in the wasteland

The last operation is the same snuff box from which gangsters, ghouls, meetings with dead travelers, all other adventures that can be invented, jump out like devils.

How does this happen? Very simple - each event simply changes the numerical parameters of a caravan or the world, and then reports this to the log. And here magic arises - the parameters can be the same, but you can report completely different reasons. Minus a few units of food? These could be rat attacks, fallout or hungry vagabonds. Plus a few units of food? So, your people found an edible cactus, excavated pre-war canned goods in roadside ruins, or found wonderful leather boots with soft soles on the road.

The first version with which I started experimenting looked like a pure illustration of the algorithm described:



I made this prototype as a remake of a game about the Oregon caravan from this tutorial .

One-dimensional prototype - caravan for js13kGames


The game from the tutorial that I found was one-dimensional - the caravan left from one point and came to another. On the way there were random events, meetings with gangsters and merchants.



The player in this version was very limited. Basically, he had to admire the log with events. Interactive came only from time to time, when a store or gangsters appeared. At the same time, all events had an equal probability and were even stored in one array.

When I looked at the result, I realized that the game can be expanded to a real sandbox. It was necessary only to add freedom, make a modular code and post events on different modules. I had to rework the code almost completely.

List of changes
  1. Reusable journey - the game does not stop with the goal
  2. Two-dimensional map of the world and city
  3. Another setting of the world and no dysentery
  4. Gangsters can negotiate and engage
  5. Added products that are automatically sold and bought when reaching the city
  6. Modular system - plugin-based logic and event sets in separate files


Caravan engine and architecture


The game, as already mentioned, is made in pure JavaScript without the use of third-party libraries (you can add them yourself if you see fit). To display a map of the world and the interface using plain HTML and CSS. To modify them, basic DOM operations and the classic document.getElementById operation are used.

An example of displaying the number of players in the caravan
this.view = {}; //     DOM this.view.crew = document.getElementById('game-stat-crew'); //      // ... this.view.crew.innerHTML = world.crew; //        html 


WorldState - a model of the world


The world in the game is a WorldState class. It stores all the important parameters and does not contain any logic. Logic we will tie later, at the expense of plug-ins.

 function WorldState(stats) { this.day = 0; //  ,    this.crew = stats.crew; //   this.oxen = stats.oxen; //   this.food = stats.food; //   this.firepower = stats.firepower; //   this.cargo = stats.cargo; //    this.money = stats.money; // //  ,  ,    // { day: 1, message: " ", goodness: Goodness.positive} this.log = []; //  ,     this.caravan = { x: 0, y: 0}; this.from = {x: 0, y: 0}; this.to = {x: 0, y: 0}; this.distance = 0; //    this.gameover = false; // gameover this.stop = false; //    ,    this.uiLock = false; //     } 

Game - the creation of the world and the game cycle


The game loop is started and controlled by the Game object. The same object creates the world. Notice the plugins field — by default, this is an empty array. Game knows nothing about plugins, except for two things - they must have the init (world) initialization function and the update update function.

 Game = { plugins: [], //  , }; Game.init = function () { //           //   StartWorldState   data this.world = new WorldState(StartWorldState); var i; for (i = 0; i < this.plugins.length; i++) { this.plugins[i].init(this.world); } }; //   Game.addPlugin = function (plugin) { this.plugins.push(plugin); }; //   Game.update = function () { if (this.world.gameover) return; //   var i; for (i = 0; i < this.plugins.length; i++) { this.plugins[i].update(); } }; Game.resume = function () { this.interval = setInterval(this.update.bind(this), GameConstants.STEP_IN_MS); }; Game.stop = function () { clearInterval(this.interval); }; Game.restart = function () { this.init(); this.resume(); }; 

To start a new game, you need to call the Game.restart function. But before this happens, you need to add some plugin - otherwise nothing will happen, we just have a game cycle to idle.

Eat, live, move - CorePlugin


The most basic actions of the caravan - moving, counting down time and food consumption - are implemented in the CorePlugin object:

CorePlugin source code
 CorePlugin = {}; CorePlugin.init = function (world) { this.world = world; //  world this.time = 0; //     ,   this.dayDelta = GameConstants.STEP_IN_MS / GameConstants.DAY_IN_MS; //       this.lastDay = -1; //     this.speedDelta = Caravan.FULL_SPEED - Caravan.SLOW_SPEED; //       }; CorePlugin.update = function () { if (this.world.stop) return; //   -   this.time += GameConstants.STEP_IN_MS; //   this.world.day = Math.ceil(this.time / GameConstants.DAY_IN_MS); //  ,  //      ,    this.updateDistance(this.dayDelta, this.world); //       if (this.lastDay < this.world.day) { this.consumeFood(this.world); this.lastDay = this.world.day; } }; //       CorePlugin.consumeFood = function (world) { world.food -= world.crew * Caravan.FOOD_PER_PERSON; if (world.food < 0) { world.food = 0; } }; //           CorePlugin.updateDistance = function (dayDelta, world) { var maxWeight = getCaravanMaxWeight(world); var weight = getCaravanWeight(world); //   - Caravan.SLOW_SPEED //  0  - Caravan.FULL_SPEED var speed = Caravan.SLOW_SPEED + (this.speedDelta) * Math.max(0, 1 - weight/maxWeight); // ,        var distanceDelta = speed * dayDelta; //     var dx = world.to.x - world.caravan.x; var dy = world.to.y - world.caravan.y; //      -  if(areNearPoints(world.caravan, world.to, Caravan.TOUCH_DISTANCE)){ world.stop = true; return; } //     -    //      var angle = Math.atan2(dy, dx); world.caravan.x += Math.cos(angle) * distanceDelta; world.caravan.y += Math.sin(angle) * distanceDelta; world.distance += distanceDelta; }; //     Game.addPlugin(CorePlugin); 


It's all elementary. At first, when we start the game, init is called for us, which will allow us to save the link to the model of the world. Then, in the game cycle, we will be called update, which will change the world for the better, as the characters from the series “Silicon Valley” like to say. Joke - the world will change in all directions.

Our base plugin counts time in milliseconds, translates them into days, and then updates the distance and food reserves. In principle, the plug-in object simply has to contain the functions init (world) and update (), and it can do anything. You can even simply call up some other HTML5 game or create a dialog box.

To connect a plugin, you need to add its code between the definition of the Game object and the first call to Game.restart (). Approximately the way it is done now in index.html:

 <script src="js/Game.js"></script> <!--  --> <script src="js/plugins/CorePlugin.js"></script> <!--   --> <script> Game.restart(); </script> 

So, how to make a game about a caravan


If you are an experienced programmer, you can certainly implement such a toy in any language and graphical level, simply starting from the very idea of ​​“a cycle in which movement and random selection from an array of events takes place”. I think it will turn out well even a game for bash, especially since recent publications show that there are some very interesting graphical features.

If you are familiar with JavaScript at least at a basic level - you can download the source code for the game, which I did in this language, and freely modify and improve them. You can use any architecture if you are not satisfied with the current one. But the easiest option is to simply add a plugin, implemented according to the principle described above.

Existing plugins can be disabled (by removing their source code from index.html or by commenting out the line from Game.addPlugin (SomePlugin) at the end of their code). They know nothing about each other and simply change the model of the world or the interface of the game.

And the last option, for writers, just open files in the data directory and edit event descriptions and constants. Although these are the same javascript sources, they are content easy to change. Especially the lyrics. To prove this, I will briefly describe how the other plugins in the current version are arranged.

Random events


All primitive random events are in the data / RandomEvents.js file in the RandomEvents variable in the following format:

 var RandomEvents = [ { goodness: Goodness.negative, stat: 'crew', value: -4, text: '   ! : -$1' }, { goodness: Goodness.negative, stat: 'food', value: -10, text: '     .  : -$1' }, { goodness: Goodness.positive, stat: 'money', value: 15, text: '    .    . : +$1' }, { goodness: Goodness.positive, stat: 'crew', value: 2, text: '   ,       . : +$1' }, 

Objects of random events are contained in curly braces. In this example, I cited only four objects, but in general their number can be anything. Just keep in mind that with an increase in the number of events, the probability of a particular individual falling out of them decreases.

The first field, goodness, means positive, negative and neutral coloring of the message in the log. The second field, stat, contains the name of the WorldState parameter, which should be changed. Value is the average value for changing this parameter. The last field should contain any arbitrary text describing what happened. Instead of the symbol $ 1 in the text will be substituted for the actual change of the parameter, which will fall in the game.

Random events are checked for a drop in the RandomEventPlugin object and delight the player in the log:



One last note: in RandomEvents.js, you will find a variable with a probability constant for a random event. It is set as the average number of events per game day of the caravan. When I experimented with different values, I found out that too many random events, which cannot be influenced in any way, begin to annoy wildly. The lack of interactive is the main disadvantage of these simple events. That's why I scratched my head and decided to make a universal dialog module that can be called from other plugins.

How to write or edit a dialogue


The DialogWindow object is responsible for the dialog system. If you look at its source code, you will see a mutant that finds the desired div element in the HTML code and binds to it the general click handler with the mouse. The idea is that when we ask this object to show us a new dialogue, we give it an array of our dialogs. And the handler for clicking on a particular choice is described in a specific dialog in this format:

 var DeathDialogs = { "start": { icon: "images/pic_death.jpg", //   url  title: "  ", //   desc: "", //    desc_action: function (world, rule) { //       var desc = "  : "+rule.text+".    "+Math.floor(world.distance) + "    "+Math.floor(world.money) + " "; desc += " ,    ?" return desc; }, choices:[ //   { text: '  ', //    action: function () { return "stop"; } // ,     } ] }, }; 

In the dialogues of death there is only one option, described as the "start" field. But such options can be infinitely many. For example, in the dialogues of bandits, I realized 12 forks. How does the transition between them? Our universal DialogWindow object, when calling the show function, keeps a list of the passed dialogs and shows the one defined in the "start" field.

When the next dialog is displayed, its choices array is displayed as a set of buttons, in the attributes of which the selection number is written. And all the action functions from choices are written to the internal array dialogActions. When you click on the selection button with a mouse, the universal handler determines the function number in the dialogActions and calls it, passing in passing the two arguments that we decided to use in this dialog. Thus, in dialogues with gangsters, the action function in a particular choice can assume the state of the world (world) and the description of current gangsters (bandits). And in the dialogs to other plugins - other options. Yes, you can and without them at all, especially if the point of choice is to simply end the dialogue, as with a game gem.



In order for the dialog to end and the player return to the world map, it is necessary that the action function in the choice object returns one of the reserved tags “finish”, “exit”, “stop”. In general, the meaning of this function is to return the name of the next fork. But before this cherished return-a, you can and sometimes you need to insert any logic, any calculations that will allow you to choose the next fork - “run”, “fight” or, perhaps, even “love”.

How to call a dialog from the plugin


At any time in the update of any running plug-in, you can call the dialog as follows:

  // ... -   update  - //  ,   world.stop = true; //        DeathDialogs //      world  rule DialogWindow.show(DeathDialogs, world, rule, this); 

Also, the onDialogClose function must be implemented in the plugin - this callback will be called after the dialog is closed. An example from a plugin defining death:

 DeathCheck.onDialogClose = function () { Game.restart(); }; 

Brief description of existing plugins


The current version of the game uses the following plugins:

Map2DPlugin - move the caravan around the map. Search for cities that are specified in index.html as regular divs with parameters top and left. It also determines the arrival in the city and automatic trading takes place.

ShopPlugin - generation of random oncoming caravans or other merchants. Allows you to buy food, brahmins and hire mercenaries. Or buy nothing and go on.

BanditPlugin - meetings and dialogues with bandits. Around the middle of this plugin, I realized that with the idea of ​​making a simple tutorial I got excited. Simple did not work, sorry. Gangsters can hire you, and if they die of hunger, they will be asked to accept them for free if you refuse to hire them for money.

DropPlugin - plugin overload. In the prototype from the tutorial about the Oregon caravan, the game itself automatically dropped things - first a weapon, and then a meal. It was not very comfortable and puzzling - how is that? After all, you can get food with weapons, but “you cannot kill the enemy with fried meat” (c) one famous stream in Fallout 4. So I decided to make a dialogue in which you simply choose what to get rid of.

DeathCheck - death check plugin. This is still a very simple plugin, and for sure there may be questions for it. For example, why do people die immediately when food ends? Well, I propose to answer this question yourself - or wait for the next version if everything goes well.

WorldViewPlugin is an interface plugin. It simply updates the interface, showing the current parameters of the world. Perhaps this idea will seem strange to someone - an independent interface object that tracks changes in the update cycle. But then, thanks to this strange idea, we got rid of numerous updateUi and gained independence between blocks of different logic.

Small tips on the current game and balance


The vital parameter now is the amount of food. It can be replenished when meeting with other caravans - if they occur, it should be purchased to the maximum. Of course, it would be reasonable to add a purchase in the cities, but we will assume that for the time being they all suffer from a lack of food.

The second vital parameter is the number of people in the caravan. They should be. If all people are knocked out - the game ends.

All changes in the balance sheet that increase the damage to food and the number of people dramatically increase the difficulty of the game.

Links and distributions


I used the Creative Common 0 license from pixabay.org and opengameart.org, so both the graphics and the code are distributed under these conditions - free to copy and use, without any obligations.

You can get the source code from GitHub or download the zip archive from here . The first option is preferable, as it is often updated.

To test, even on the local computer, it is enough to open index.html in the browser - there are no functions that necessarily require a server.

Live build games can be tested here . Layout is designed for regular monitors, not for mobile screens.

Source: https://habr.com/ru/post/336724/


All Articles