Share Wars - Have fun bankrupting your friends!

Hello fellow Godot developers!

My name is Thomas, and I run a small games studio together with two friends. Our studio is called Rubarb. I’ve been a Unity developer since it was in version 3, and have recently switch over to Godot. After a year of experimentation and playing around, I have become a true believer. :smile: Godot has brought fun back into game development for me. :partying_face:

I am currently working on a game called Share Wars. A game about being a ruthless capitalist in a battle for total market domination. It plays as a board game, and it will support single player and hot seat local multiplayer. I am also researching LAN multiplayer, but that will have to come as an update later.

The game is inspired by an old Windows 3.11 game called Oil Baron, which in turn is inspired by an old board game called Acquire. Share Wars will feature new challenges and options. It won’t be the most innovative game out there, but that’s not the point either. I’m making this to get experience with Godot and to build our portfolio.

Share Wars is not playable yet, but it’s getting there. The system architecture has been refactored a couple of times, as I learnt about the engine and how the game should work. All of the code is written in GDScript, except for a couple of GDExtensions I wrote in C++ (for nine slice scaling of sprites, and adding support for parsing toml-files).

The images you see here are all made by me, so consider this programmer art. My artist colleague, Simon, will sprinkle some of his magic pixel dust over the game once it’s ready for it. And Dan, the audio colleague will add music and sound effects later.

I’d like to use this thread as a dev diary, to document the process for myself, and show you what I’m working on.

The tasks for next work session, are:

  • Establish a new corporation. Show it in the corporations list, add company logo to the tiles.
  • Design and implement a popup for choosing which corporation to establish.
8 Likes

Good luck and looking forward to seeing your progress!

1 Like

Thank you!

While I work on the mentioned tasks for the next update, I thought since there are other developers here, I would like to document some of the underlying architecture and data flow.

For context, I started programming in 1989, when I was 10 years old. My father taught me BASIC, and we started to make games together. Since then I have worked on about 30 games, that are released, close to 80 if we include all the prototypes and abandoned projects. Even though every game is different, I still think deeply about architecture and data flow in hopes of finding an architecture that I can bring over to new projects. I might be a dreamer. :slight_smile:

Architecture
With Share Wars, I have so far, landed on a quite simple architecture.

The Main node is responsible for holding a reference to the UI and the game board (which is the playing field). It also loads two config files, one for system settings such as volume control and screen resolutions, and one for altering the behaviour and rules of the game. They are both saved in the TOML format, which are loaded via a custom C++ GDExtension I made. Once the extension gets mature enough, I’ll release it for everyone to use.

Main is also responsible for the startup sequence. Preloading data and showing the splash screen. When all is ready, it can instantiate a new Game.

The Game class does not need to live in the scene, and is therefore of type Object. The class is only responsible for instantiation of the core game classes, in addition to saving (serializing the current game to files on disk) and loading.

State is a pure data class that holds every piece of info that the game needs in order to function. The reason for sentralizing all game state in one class, is that it makes it very easy to find the data (locality is no longer a challenge), and it’s a lot easier to serialize and deserialize the data.

The Rules class is responsible for maintaining the game’s rules. It consists of functions that do and decides things.

Game mode is responsible for handling a specific way of playing the game. Currently I only have one mode, the single player mode. Later a mode for hot-seat multiplayer will be added, and lastly one for LAN party.

With a setup like this, I can combine game modes with different rule sets and configs, to give the player many different ways of playing the game. For myself, it’s an architecture that is simple enough to keep it all in my head at once. I can easily navigate the code and know where to put new code, and find old code.

Data flow
I’ve been through many projects with variying degrees of control over data flow. For Share Wars I wanted to enforce a more strict flow, in hopes of staying in control over the code for as long as possible.

The main guidelines behind the setup are:

  • Never use signals for decoupling modules that are critical for the game to work
  • Use direct function calls to push data
  • Listen for signals to react to data

The reason for sticking to direct function calls between critical modules, is to clearly see where data flows, and where responsibilty is delegated to. Many years ago I used Unity to make a game called Deflector (GitHub - mandarinx/Deflector), in which I extensively used ScriptableObjects for handling game events. It was a trend back then. It was such a horrible experience that I vowed to never decouple critical game modules using assets. But, it also led me to become more strict and explicit in my game code.

Mode calls functions directly on board and rules. It dictates, based on user input, what is supposed to happen to the game. The board never writes to state, only reads it. Rules can both read and write, and is the only one who can update the state. Rules can also use the UI to ask for input from the player. State emits signals when parts of the state has changed. The UI listens to the signals and updates when needed. The UI also picks up user input, such as button presses, and calls functions directly on mode.

With a setup like this, it’s easy to reason about the game. As state changes only happens in two places, any bugs related to game state are easy to find. And it’s very easy to follow the code.

We’ll see how well this architecture holds up. :smile:

3 Likes

This looks like a game I used to play many years ago.
You place tiles for your corporations and when a group of tiles meets a group of another corporations tiles the larger corporation swallows the smaller one.
I can’t remember the name of it though.
Edit: I believe it was called “Acquire” however my search skills are failing me and I can’t find an image of it.

That’s correct. I believe I wrote about my inspiration in the first post. It’s a long post, sorry about that.

In the 90’s a guy called Al Funk made a game called Oil Baron, which was very much inspired by Acquire. In the early 2000’s I wanted to make a clone of the game, for practice, so I contacted Al. I don’t think I have the email correspondence anymore, but I have memories of him writing that the rights for the game was up in the air. Could have been that he sold the rights to his game to a company that went bankrupt, or that the company he secured the rights from went bankrupt.

Anyway, I got his blessing. But I didn’t finish the game back then. Now, 22-ish years later I have decided to finally finish the project. :blush: I have some ideas to add my own touch to the concept.

Edit: what do you know! Thanks to having the same email address for 24 years, I still have the email correspondence. :smiley:

Al sold the rights to his game to a company that later got bought by another. They had plans to revamp the UI, improve the user experience and do other fixes, and then release it as version 2.0 together with a bundle of other games. However, they never finished it. Now, the rights remains with a software company in Germany.

I could try contacting them to see if they would be willing to release the rights.

1 Like

Whoops, sorry.
I went back and read your first post; all of it.

1 Like

It will be interesting following the progress and learning from experienced programmers playing with the new tool (Godot).

Looking forward for updates (especially this logic explanations).

2 Likes

Happy new year! :smiley:

How time flies! Apart from a Christmas vacation, and some other personal things that happened, I have been working quite regularly on Share Wars. What took so long, was a classic case of totally under estimating the amount of work in the tasks I set for myself. That’s one of the things I like about game development, that you can never become fully educated. Even after 25 years, I still mess up. :slight_smile:

The tasks I gave myself, were simple enough:

  • Establish a new corporation. Show it in the corporations list, add company logo to the tiles.
  • Design and implement a popup for choosing which corporation to establish.

Before I implement new features, I always make some sketches and notes to get a better overview of the work, and to lay a simple plan. I never fully plan out every detail, because in my experience, it’s actually faster to get started working and refactor when needed. During the first phase, I try to write as much of the code in one file, to save time on jumping between files. And if I get to a point where it’s a total mess, I use git to discard all changes and start over. What I learn about the feature I’m making is more important than the code.

Inspired by something John Romero once said, I always try to keep the game in a playable state, and if I find something that doesn’t work, I fix it immediately. That way of working has really helped me write better and more stable code.

As I was implementing the corporations, I found so many things that didn’t work. The things I have fixed resulted in:

  • Better handling of loading previous game session
  • A more organized state API
  • More strict data flow
  • Simplication of game architecture
  • Standardized handling of players, regardless of human or NPC
  • A more stable game mode

I have learnt a lot of lessons, but I think the most useful for others, are:

Player classes belongs to a game mode

For a while, I thought the player classes could be generalized and reused between game modes. For context, a game mode is a specific way of playing the game. It could be a time challenge mode, local multiplayer, maybe some rules are relaxed for practice, etc. But the more I have worked on this, the more I see the player classes as an extension of the game mode. As an extension of the game mode, it’s completely fine to put game mode related code in a player class.

A well defined data flow leads to more a stable game

It’s a lot easier to identify bugs and to understand why they happen, and also how to fix them, when you can internalize a mental image of the data flow. It can be hard to get the data flow right on the first try. As you keep working on the code, always be mindful of who’s allowed to read and write state data, and when. Over time, you’ll see patterns that can help you streamline the data flow.

A simple and strict architecture makes it easier to separate engine code from gameplay scripts

With the architecture that Share Wars has now, I can easily take most of the core classes out of gdscript and into a C++ extension, and use gdscript for scripting of gameplay, game modes and player scripts. The rules of the game, the state data and config files are locked inside an extension. The benefits of putting the core code in a C++ extensions are better performance, improved type safety and true private functions.

New architecture and data flow

The new architecture of Share Wars looks like this:

The rules class is gone. Game mode and Game state has taken over the responsibility of the old rules class.

Game mode is responsible for creating the player instances.

Game state is one class with all the data. For organizational purposes, the state API is divided into smaller classes, grouped by responsibility. For instance, there is a PlayerAPI class which exposes functions for looking up players, check who’s turn it is, getting the player’s info, etc.

The new data flow looks like this:

State API’s are the only ones who are allowed to write to the State.

When some of the state changes, certain signals are emitted out to the Game Board and UI. Board and UI are responsible for displaying the game to the local player.

Game Mode is in charge of the player classes and is allowed to write to them. Players emits signals back to game mode, to let mode know about when a player is done with its turn, and other events.

The human player is the only player who is allowed to write to the UI. This is because that player class is only a proxy for the human. Without the UI or the game board, the human player is not capable of making informed decisions. NPC’s only need access to the state API’s to make sense of the game.

You can think of this map as the rules for how data flows. If I wanted to read from state directly in a game mode, that would be a vioation of the rules. In that case, I might have to extend the state API with a new function.

The next steps

For the next update, I will focus on:

  • Merge corporations that grow into each other
  • Make the test NPC actually do some trading of shares

Had a really productive day today!

As I was fleshing out the AI trade routine, I stumbled across a few bugs and some temp code that I fixed immediately. The result of the work is that I have an utterly ruthless opponent. Should probably dumb him down a bit to make the game more fun.

Merging corporations next.

Merging of corporations is finally done

This took quite some time, but it was worth it. The game is in general in a much better shape now.

When a merger happens, the game has to …

  • decide who is the buyer. The corporations involved are ranked based on their worth. The worth of a corporation is the number of tiles (the market share) multiplied by the share price. The most valuable corporation is the buyer.
  • group the selling corporations by share holder.
  • ask each player what to do about their shares. The list of selling corporations are passed to each player so that they can validate each one, and return their decisions.
  • lastly, the game mode acts on the player’s decisions

I found that the player code became more easy to reason about when it’s only about reading state data, and returning a decision, not actually performing the changes. A decision is usually in the form of an enum.

For deciding what to do about the selling corporations, the player code looks something like this:

func merge(buyer: Enums.CorpType, sellers: Array[Enums.CorpType]) -> Dictionary:
    var result = {}
    for seller in sellers:
        # when player wants to sell all shares
        result[seller] = Enums.Merge.SellAll
        # when player wants to trade
        result[seller] = Enums.Merge.Trade
    return result

The calling code expects to get a Dictionary in return, with the CorpType as key, and the decision as a specific enum. I use this way of handling player decisions for all actions, placing tiles, merging corporations, trading shares and ending the game.

Aside from simplifying the player code, it also ensures that the game mode is in control over when things happen, and can ensure that nothing happens out of order.

AI vs AI

For as long as I can remember, gamedevs have called their NPC’s intelligense AI. Now that AI can be ChatGPT, Midjourney and loads of other things, I’m not quite sure what term to use. Anyway, just for fun, I swapped out my local player object with another instance of the AI (called Archie), to see what would happen when they play against each other.

sharewars_ai-vs-ai_01

This was not only fun to watch, but also a handy way of testing the game.

  • I learnt that I hadn’t implemented the game over state. The game crashed when it reached the end. New task added to the task list.
  • I can clearly see that the game needs more work on balancing the share and dividends values. Not surprising.
  • It seems like the function that returns a random chit makes a pattern. Should probably look for a different random function.

Civilization graph

Later, when I get to implement the graph for tracking the player worth (like Civilization does), I can see how different AI’s perform against each other, and use that to make more informed changes to their strategies.

The next steps

  • Implement the game over state. Make it possible to end the game, decide a winner, show it to the player.
  • Make sure the code cleans up after a session, so that a new one can be started.
2 Likes

Looks like a lot of fun and a good way to start some fights between friends, a perfect combo!

1 Like

@Cha_Cha_Slide_Leader Glad you like it :slight_smile: One of the design goals is to make it fun to crush your opponents. Once the game is ready for testing, I’ll share a playable build here for you to try.

Got to squeeze in an hour of work this morning, and made the game over screen show the wealth graph for each player. Every turn the wealth of the players are calculated and added to a timeline. The wealth is the total value of all owned shares + the cash at hand.

I think it’ll be fun to see how well you’ve played agains your opponents. In a future revision, I’d like to add more data to the graph, such highlighting certain events. At some point, that corporation went bankrupt. Over there, you took over a corporation. Things like that.

I also see this as a useful tool for visualizing how well different AI strategies perform against each other.

Next up

  • make the game over state properly clean up after itself, so that the game can safely be restarted
1 Like

This will be peak :speaking_head: