Hacktoberfest!

Hacktoberfest is upon us! This month I’m hacking on small projects each week and sharing them.

Minecraft

This week my son and I hacked together an auto-deployed Docker based Minecraft server that is also automatically backed up and uses Git for managing server configurations. We haven’t finished the auto-backup to Dropbox portion yet, but that’s something we can always work on later! Here’s what we did…

Continuous Integration & Continuous Deployment

I knew I wanted this server to be a “set it and forget it” type of server. If anything went wrong, restarting it would fix it without losing world data. With this in mind, my first thought was using GitLab‘s CI/CD process and a .gitlab-ci.yml file. Looking through the Docker registry, I found a Minecraft server image that appears to stay up to date: itzg/minecraft-server. We simply used that and mounted a few volumes for version controlled configs and the world data directory. The GitLab Runner file is a lot to take in if this is the first time seeing this type of file. I’ll walk through each line and explain what it does. Here’s it is:

cache:
  key: "$CI_REGISTRY_IMAGE-$CI_COMMIT_SHA"
  untracked: true
stages:
  - deploy
deploy-prod:
  stage: deploy
  image: docker:latest
  only:
    - "master"
  variables:
    DOCKER_DRIVER: "overlay2"
  script:
    - docker pull itzg/minecraft-server:latest
    - docker stop minecraft || true
    - docker rm minecraft || true
    - docker run -d --name minecraft -p 25565:25565 -v minecraft-world:/data/world -v minecraft-config:/config -v minecraft-mods:/mods -v minecraft-plugins:/plugins -e EULA=TRUE --restart always  itzg/minecraft-server
    - docker cp ./config/* minecraft:/config/ || true
    - docker cp ./data/* minecraft:/data/ || true
    - docker cp ./mods/* minecraft:/mods/ || true
    - docker cp ./plugins/* minecraft:/plugins/ || true
    - docker restart minecraft || true
    - docker pull janeczku/dropbox:latest
    - docker stop -t 0 minecraft-backup || true
    - docker rm minecraft-backup || true
    - docker run -d --restart=always --name=minecraft-backup -v minecraft-world:/dbox/Dropbox/minecraft-server/data -v minecraft-config:/dbox/Dropbox/minecraft-server/config -v minecraft-mods:/dbox/Dropbox/minecraft-server/mods -v minecraft-plugins:/dbox/Dropbox/minecraft-server/plugins janeczku/dropbox
  tags:
    - docker

Devil is in the Details

cache:
  key: "$CI_REGISTRY_IMAGE-$CI_COMMIT_SHA"
  untracked: true

Lines 1-3 relate to GitLab and Docker Container caching. This will cache the resulting image to the GitLab Runner cache with the key on line 2. This is useful for downstream builds. I copied this from another project (Hacktoberfest!) so, I’m not sure if they are needed since this container isn’t used by any other job.

stages:
  - deploy

Line 4 defines the stages that will be used during this pipeline and line 5 is that stage. We define only the “deploy” stage as we aren’t performing any testing or other delivery stages. This is useful to define for organization within this file and allows you to introduce pipelines later. Again, this may not be necessary since it’s not involved in more than one pipeline. I did copy this from another project to reduce the amount of time spend recreating it so… Hacktoberfest!

deploy-prod:
  stage: deploy
  image: docker:latest
  only:
    - "master"
  variables:
    DOCKER_DRIVER: "overlay2"
  script:
    - docker pull itzg/minecraft-server:latest
    - docker stop minecraft || true
    - docker rm minecraft || true
    - docker run -d --name minecraft -p 25565:25565 -v minecraft-world:/data/world -v minecraft-config:/config -v minecraft-mods:/mods -v minecraft-plugins:/plugins -e EULA=TRUE --restart always  itzg/minecraft-server
    - docker cp ./config/* minecraft:/config/ || true
    - docker cp ./data/* minecraft:/data/ || true
    - docker cp ./mods/* minecraft:/mods/ || true
    - docker cp ./plugins/* minecraft:/plugins/ || true
    - docker restart minecraft || true
    - docker pull janeczku/dropbox:latest
    - docker stop -t 0 minecraft-backup || true
    - docker rm minecraft-backup || true
    - docker run -d --restart=always --name=minecraft-backup -v minecraft-world:/dbox/Dropbox/minecraft-server/data -v minecraft-config:/dbox/Dropbox/minecraft-server/config -v minecraft-mods:/dbox/Dropbox/minecraft-server/mods -v minecraft-plugins:/dbox/Dropbox/minecraft-server/plugins janeczku/dropbox

Line 6 defines a new job called deploy-prod and is only deployed during the deploy stage we just defined and only for the master branch (line 9 and 10). This will spin up a new docker container using the latest docker image (line 8) from the Docker registry. Once spun up line 11 defines environmental variables available to the container and line 12 sets the DOCKER_DRIVER. This driver is supposed to be more efficient. Again, this was copied from another project and I haven’t had any problems, so I leave it alone. Lines 13-26 are the meat and potatoes. The script section does the heavy lifting.

  script:
    - docker pull itzg/minecraft-server:latest
    - docker stop minecraft || true
    - docker rm minecraft || true
    - docker run -d --name minecraft -p 25565:25565 -v minecraft-world:/data/world -v minecraft-config:/config -v minecraft-mods:/mods -v minecraft-plugins:/plugins -e EULA=TRUE --restart always  itzg/minecraft-server
    - docker cp ./config/* minecraft:/config/ || true
    - docker cp ./data/* minecraft:/data/ || true
    - docker cp ./mods/* minecraft:/mods/ || true
    - docker cp ./plugins/* minecraft:/plugins/ || true
    - docker restart minecraft || true
    - docker pull janeczku/dropbox:latest
    - docker stop -t 0 minecraft-backup || true
    - docker rm minecraft-backup || true
    - docker run -d --restart=always --name=minecraft-backup -v minecraft-world:/dbox/Dropbox/minecraft-server/data -v minecraft-config:/dbox/Dropbox/minecraft-server/config -v minecraft-mods:/dbox/Dropbox/minecraft-server/mods -v minecraft-plugins:/dbox/Dropbox/minecraft-server/plugins janeczku/dropbox

Line 13 defines what will run on the GitLab Runner. This is the reason we use the latest docker image on line 8. The commands on lines 14 through 26 will actually leverage Docker on the GitLab Runner and manipulate docker containers. We start off on line 14 by pulling the itzg/minecraft-server image (in case Minecraft server releases an update). This updates the image that Docker can use but doesn’t update the running container. After we pull the latest container image, we stop (line 15) and remove (line 16) the current running Minecraft server container. The || true guarantees the execution will not return an error which would stop this from succeeding in the event the container isn’t running or doesn’t exist. Line 15 doesn’t have a timeout for forcing the container to shutdown so it will wait to clean up and prepare the world for the server going down. This helps prevent data corruption. Line 16 removes the running container so we can re-deploy with the latest version.

    - docker run -d --name minecraft -p 25565:25565 -v minecraft-world:/data/world -v minecraft-config:/config -v minecraft-mods:/mods -v minecraft-plugins:/plugins -e EULA=TRUE --restart always  itzg/minecraft-server

Line 17 does a lot. It runs a container named Minecraft (--name minecraft) as a daemon service (-d) and binds the host port 25565 to the container port 25565 (-p 25565:25565). Since Minecraft clients check for this specific port, I opted to just bind the public exposed port to the same container port. We then mount several Docker volumes with the -v flag. I like to think of a Docker Volume as a flash drive. It’s just a storage device you can plug into other containers. The only difference is that you can plug it into multiple containers simultaneously and you can mount it to a specific folder. Here, we mounted the minecraft-world volume to the /data/world container directory. We also mount the minecraft-config to /config, minecraft-mods to /mods and minecraft-plugins to /plugins. Once these mounts are in place, we set the container EULA environment variable to true, set the container to always restart if it goes down, and finally tell Docker what image to use for the container. This runs the actual container and the whole server spins up performing any startup tasks before the server is live and available for connection!

    - docker cp ./config/* minecraft:/config/ || true
    - docker cp ./data/* minecraft:/data/ || true
    - docker cp ./mods/* minecraft:/mods/ || true
    - docker cp ./plugins/* minecraft:/plugins/ || true
    - docker restart minecraft || true

Once this container is running, we copy version controlled files (if any) from the checked out repository into the mounted volumes on the Minecraft server container (lines 18-21). This lets us version control configurations, plugins, and mods and auto-deploy them to the server. Once these are in place, we restart the container one more time (line 22) for these to take effect. These files are copied in this way so we don’t have to worry if this is the first time the container is ran. If we mounted the files before starting the container, the container wouldn’t exist on the first run and would require 2 deployments for these changes to take effect.

    - docker pull janeczku/dropbox:latest
    - docker stop -t 0 minecraft-backup || true
    - docker rm minecraft-backup || true
    - docker run -d --restart=always --name=minecraft-backup -v minecraft-world:/dbox/Dropbox/minecraft-server/data -v minecraft-config:/dbox/Dropbox/minecraft-server/config -v minecraft-mods:/dbox/Dropbox/minecraft-server/mods -v minecraft-plugins:/dbox/Dropbox/minecraft-server/plugins janeczku/dropbox

Lines 23-26 are backup related. Line 23 updates the local version of the janeczku/dropbox:latest image. Lines 24 and 25 should look familiar. These stop and remove the existing container while guaranteeing success if the container is already stopped or doesn’t exist. Line 26 should also look familiar. Here, we start another container as a daemon (-d) that restarts whenever it stops (--restart=always) named minecraft-backup (--name=minecraft-backup) with a few volumes mounted to the container. These volume mounts should also look familiar! We mount the same volumes here as we do the Minecraft server so this container can periodically back up the contents of the volumes to Dropbox. We are still troubleshooting why this isn’t quite working and hope to have this resolved next time.

  tags:
    - docker

Finally, likes 27 and 28 tell GitLab what GitLab Runner to run this job on. Each GitLab Runner may have tags that jobs can use to run on specific runners. This particular job requires a GitLab Runner that is Docker enabled. This doesn’t actually mean there is a Docker enabled GitLab Runner available, but in my case, I have already set up a Docker enabled GitLab Runner and I can force jobs to use it by adding this tag to this file.

That’s it! Now we have a Minecraft server! Even without the Dropbox backups, this project was fun. It was an awesome moment when my son wanted to stand up a Minecraft server and actually stayed interested through the build. He got to see behind the scenes how the game is set up, although I don’t think he fully understands how it all works, but maybe one day!

We will continue tinkering with this script as we add configuration files and get the Dropbox backups working. I will be open sourcing this when we have completed working on it. Until then feel free to use this as a template for your next Minecraft Server and I will be updating this post as I update the files.

Rules

We’ve come so far since last “game dev” session! We have an actual rules book! This came about after sifting through notes we had taken in the previous sessions while play testing and meeting conflicting information. It was time to consolidate and create one rules set to rule them all! The rule book isn’t entirely complete, but it will serve as the golden standard going forward. If a rule changes, it changes in the rule book. If an idea occurs, it’s tested before being added to the rule book.

Things are a’ Changin’

We ended up changing the actions on a turn. Now, instead of placing a tile, drawing back up to 3 cards, moving, fighting, moving zombies, then discarding players still place a tile but then choose whether to move, search, trade, or use an item outside of combat. The rest of the turn is just zombie movement. We tried this new turn order and it worked! Sort of…

We had already decided that players started with only 3 heart tokens. This proved devastatingly hard and we generally didn’t survive the first zombie encounter. Adding the three bullet tokens back didn’t seem to increase the odds past 2 encounters. So, you get to draw 2 cards from the item deck to start as well. This seems to be a great balance as you are essentially meeting up with another player at the start of it all with whatever you managed to grab on the way out the door.

We did clean up the combat rules to indicate that using a Weapon requires a bullet token. Without a weapon, or bullet tokens for the weapon, you instead must make a combat roll under the original rules. We also established basic zombie movement rules: the closest zombie to a player moves towards that player. In the event of a tie, the player chooses which one moves. This prevents the players from just making zombies move away from them or the area they want to go to.

We also implemented a limited inventory and item sharing. Players can now hold up to 3 items at a time whether or not they are broken. A player may discard an item at the end of their turn or when they pick up a 4th item. We also implemented sharing. This still has some kinks to work out as the time it takes for sharing may outweigh the benefits.

When will it end?

We did find out that members of your team will eventually lose all their heart tokens. We discussed then decided to test that when this happens the player becomes a member of the zombie team which gains full control over the zombie hoard surrounding their former partners. This means that zombies move according to the zombie team discretion instead of only moving the closes zombie to a player towards that player. This lets the zombie team become strategic in isolating players from their group.

This, of course, eliminates the win condition of collecting 25 zombies per player. We’re not sure if this is a good idea or not, but I think with a few more play tests and a bit more tweaking it can be a beneficial mechanic for interesting play.

On that note…

We did come up with some new ideas like additional armor, weapons, and items. Barricades and backpacks were some specifics. Barricades would block a street or building entrance so zombies (and players) couldn’t pass. Backpacks would add 1 armor token and 3 inventory spaces as long as it wasn’t broken. When it breaks, you have to discard down to 3 items. We will most likely change the tiles to be more advantageous to the new mechanics and play styles we are introducing. Ideas like parking lots, shortcuts, and alleys all came up. A difficulty system based on starting heart tokens was also discussed. Who knows? Maybe next session will see some of these fleshed out.

Play Testing!

It’s been a short while and Connor and I have had another “game dev” session! He brought his journal with him and it had some ideas! So, we went about play testing some of the ideas from the first session and some of the ideas in his journal.

The Story… So Far…

For this particular game, we already have a platform with a story. It’s a rather simple story without all that backstory that enriches characters. The story is essentially: “Escape the city on the helicopter or capture enough zombies surviving won’t be an issue.” Pretty simple and straightforward. We could always expand this later with, perhaps an actual story? Maybe a few named characters? Who knows!

Goooooooooooooal!

So we have a story, now we need goals. Goals tell players how to win. The original is “escape” but typically comes down to “prevent your opponents from escaping” in actual play. This becomes boring and ends in a long slog through the game until someone flips a table or everybody just gives up.

We didn’t want to play a game like that. We want a fun, albeit challenging, game to play with friends that resolved in a reasonable amount of time. These are our goals. With these goals in mind, we couldn’t see any other ways to win other than “escape.” So we made the game goal “escape with your friends” to set the tone that this game is cooperative!

But escaping is hard, and the original had somewhat of a fail-safe: collect 25 zombies. That sounds reasonable to keep, maybe “25 zombies for each player playing the game”? That seemed better so we went with that!

Mechanics Schmechanics

So if a story is the who, what, when and where and the goal is the why, mechanics are the how. This is how a game is played. In Monopoly, you move around the board buying properties and building houses and hotels so you can collect money. Build and buy enough and your opponents can’t pay the bills and go bankrupt. These mechanics (dice, properties, rent, houses, hotels, mortgaging, passing go, Change, Community Chest) are all the mechanics of how you play Monopoly. They also fit in with the story of being a real estate mogul and the goal of being the last one  in the game. This synergy is hard to hit with mechanics unless the story and the goal are related and aligned.

Card Change Up

So most of the cards in the standard deck revolve around messing with other players. We knew we needed to replace those. In our previous brainstorming session we talked about getting players to work together instead of against each other so we though of how new cards could be added to help that happen. As you could probably imagine, this lead to extremely over powering cards like “Capture all Zombies.” So we talked about the different aspects of games like the story/ploy, player goals, and game mechanics and how they all worked together.

Search and Replace

We struggled with the current card mechanics and how to change them. There was definitely something off with the standard game because it focused more on sending zombies to the opposing players than surviving. What zombie apocalypse has people fighting against the world and not teaming up? So we removed all the “opposition” cards that hurt your new teammates and thought up some alternate cards. We came up with a few replacements and wrote them down on a piece of paper. I broke out the card protection sleeves slipped the original card in it and the piece of paper in front of the card. Boom low budget prototyping.

After adding some risky cards and some beneficial cards it quickly became clear that we needed to change up the deck mechanic and introduce a search mechanic. So we split the deck into “Items” and “Search” decks. We added some proxy cards into the Search deck for drawing an Item (currently the only way to do get an item). This seemed great! We got more weapons and there are risks of searching (like finding a zombie or misplacing/breaking a weapon). This mechanic helped a lot. it got more weapons and items in play and it organically made us stick together for protection. It was a great step in feel for the way we imagined this game playing out.

Weaponized

We found the standard weapon mechanic too limiting. It requires you to go to a specific building after finding a weapon to actually use the weapon. This seemed backwards to us. Once you find a weapon you should be able to use it. So, we lifted that restriction and found that they suddenly became way too powerful! We talked about balance (which is a rather difficult concept to understand). So I offered up a “durability” check when you use a weapon to balance this mechanic. This introduced some risk to using a weapon since it can break. It worked great but weapons were still too powerful. The original game has a concept of increasing your combat roll with a bullet token. We opted to keep that mechanic as more of a general representation of “effort” when you are in combat without a weapon — you may or may not need it. But, once you have a weapon, you aren’t making combat rolls. So this seemed to be a little uneven. We decided to have using a weapon cost a bullet token as ammunition in addition to the “durability” roll. This ended up working out great so far! You have the safety of a weapon but if it breaks, you’re low on effort/energy since a disheartening event just occurred and your risk just increased.

Life or Death

We only kept the basic weapons in the game. Some of them remained one-time use (grenade and Molotov) and we buffed some of them (shotgun has a range of 1). We still didn’t end up searching as much as we hoped. While the search and item decks seemed to have complementing risk and reward, opting to search seemed too much of a risk in itself. We hadn’t changed any of the main mechanics so that you were more likely to want to search. So, we took out heart tokens from building spawns and added Armor to the items deck. Other than First Aid Kits the only way to get life now is through Armor. But, there are limited armor items in the deck (need to still play test that one with more people). So, we talked about repairing items. If a weapon or armor breaks, we just tap it (turn it sideways) to mark it as broken. Broken items can be fixed with a Repair Kit.

Sharing is Caring

This is going great! We have weapons to survive, a reason to search for things, and a way to fix the things we found. What about helping others though? We talked about Pandemic and how researchers can share their research. What if players could share their inventory items? We tried it out. Players have to be on the same square and the active player can forego moving to give 1 item to a player on their square. This seems balanced. It requires communication on trades, planning for meeting up, and both players essentially sacrificing their movement (the receiving player has to use their movement to get to the trading player). This happened almost immediately with armor when Connor found 2 pieces. It sparked the talk on armor tokens remaining on their respective armor cards and how it travels with the armor.

Conclusion

This session was awesome! We have a semi-playable version of our modified Zombies!!! co-operative game. Brainstorming is one thing, but seeing your ideas in action and how it plays out is so much more informational. We almost immediately saw flaws in our ideas, discussed them, did a quick brainstorming session on how to fix it, then tried out the new way. These iterations helped Connor understand how players interact with different mechanics and how they all play together. He’s still got his journal and he’s still writing down ideas. We are both looking forward to the next “game dev” session!

Braaaaaainsssstorming…

The other day I was playing a tabletop game with my son. It was Zombies!!! If you haven’t played it, it’s a tile based tabletop game where you play as a human trying to escape a city filled with zombies. The world starts as 9×9 gridded square. Each turn a new tile is played growing the city larger and larger all the while filling it with zombies. In this game you would rather feed your friend to the zombies to get away then help him out of a jam. So, yes, it’s hyper competitive. The game has its downs though. The only way to win is by either capture 25 zombies, or escaping on the helicopter (last tile in the deck). You can slow your opponents down by dropping zombies on them. As the game progresses, the mechanics break down and the game becomes how fast can you send players back to start. The game has a ton of potential. So we decided to change up some mechanics to see if we could turn this into a multiplayer cooperative game (a kin to Pandemic). What better way to start working on a zombie game than brainstorming!

Brainstorming

So, my son just turned 10. He’s excited (really excited) about Fortnite and YouTube and Twitch! So, I brought up tabletop games. Why not make a game? So, I showed him different type of games. He already knew about card games, and I’ve been showing him other board games like Ticket to Ride First Journey, Pandemic, Life on the Farm, and Monopoly of course! He hasn’t played a tile based game so I thought Zombies!!! would be a great gateway into thinking outside the box (or board) and into a more open format game. So, as we quickly became board with this game in its current condition, we asked ourselves.. How could we make this fun? Connor was the first to answer with “Why aren’t we working together to escape?” which is a great idea! It’s the zombie apocalypse! When have we ever seen a zombie apocalypse that doesn’t have a band of humans (good or bad) working together for survival? Seems like a great idea! Added it to the list. We went down different ideas and talked about how that would affect the game and if it would make it too difficult or not fun. We added them to the list and came up with a bunch of ideas. Here’s some general ideas of where this brainstorming led:

  1. Players work together to escape or capture enough zombies
  2. Different decks to search from
  3. Change the layout of the tiles
  4. Some sort of inventory system
  5. Some better weapons system where they aren’t one-time use

Connor seems very excited to continue the next session that he’s keeping a journal of ideas to share next time! I’ll keep updating this series as we progress in our game! This activity was definitely more fun than I expected and am looking forward to regular “game dev” nights and hopefully a better game to play and share with friends!