I'm hoping to make a digital board game type game - it all depends on the theme and how it inspires me.
I program python for my job, but it is all test cases and selenium. For games I haven't used python in a long time, and lately have been feeling nostalgic for all of the fun game programming I used to do.
So what have I been doing gamewise in the last couple years if I haven't been using python? Well, it's been a little bit of unity, chasing the early VR craze, which was quite fun but I feel pretty over it as the market is really challenging. Not enough people have headsets, and many who do are looking for a specific kind of thing. I usually tend to make very experimental games, and the anti-gravity 3d space pool game I made was no exception. You can see some video of it at the steam page here. Hmm. I should really do another update!
I've also been thinking about and experimenting with analogue game design - board games and card games. It's been a great experience for several reasons. First, without having to worry about programming, you get to the raw gameplay much faster and can answer questions like "is this mechanic good" or "what affect does this change have" much faster. On the other hand, I've found that without being able to rely on the whiz bang of multimedia, you really have to execute strong. I recommend any budding game designers give it a try - you'll learn things you didn't know you didn't know!
There seems to be a lot of cool things happening in this space as well, with card games like keyforge and warhammer champions shifting paradigms in the physical realm, and hearthstone and tabletop simulator bringing analog gaming into the digital space in exciting ways.
So, I'm burned out on 3d, jazzed up about card games, and nostalgic about game development with python. What better excuse than pyweek to make on a digital board game of some kind?
My plan is to build a client/server implementation of a digital card game. The game design itself will be based on the theme, the client/server I will be experimenting with before the competition starts, (but not writing any code.) I will be using kivy, to enable the client to be available on mobile and desktop platforms. And I intend for the server, written with simple http requests using hug to allow both realtime and asynchronous play.
Ambitious I know. It wouldn't be pyweek without some early big dreams that are dashed on the rocks of the reality of the last days.
The game will be an asynchronous digital card game. Players will download the client. From there they play the game, which actually takes place on a server. The state of a players game is saved until the game ends, either with a victory or with a player conceding. If both players are connected, it will seem as if the game occurs in real time - though like most card games players take discrete turns, each of which is made up of a series of ordered actions. However, if you connect to a game that you are involved with but had been disconnected, you will see a replay of what happened while you are gone, and can resume your turn.Client
The client will be written in Kivy. Currently I can build something that works well for OSX or ios. I'm still working on android support - for some reason the build I have has a terrible lag for the touch inputs that really won't fly. I also should be able to do windows pretty easy - just haven't given it a shot yet.
- Menu for how to get into a game
- Apply model to graphical interface
- Animate model state changes into interface
- Send commands to server based on player input to the interface according to model
The server must handle a series of ongoing games, user accounts, and then state transitions for each game. Since there is no actual synchronous play (it is all discrete state changes) I intend to use a lightweight REST server like hug to manage the networking. The general algorithm looks something like this:
- Client sends an action command - this is tied to a specific way to change the model
- Server checks if the player in this state is authorized to make that move
- Server updates the model
- Server returns an acknowledgement or an error along with the new model state
- Clients will also periodically poll for whether the state has updated, and get the new state
The client and server are somewhat dumb in this sense, where the model has the guts of the actual game implemented.
- Contains some information about the Players involved
- Contains a Game definition
- Contains Spaces - these are places that Things can go. Those Things have a Position defined by the Space
- Spaces contain Things. Things are elements like cards or tokens. Things contain both Static properties and Dynamic properties
- Each type of object (player, game, space, thing) includes Triggers and Actions
- Calling an Action is the only way to cause the model to transform. An action results in an updated model, or an error state
The model will exist on the server as well as the client. The client can "look ahead" by attempting to perform Actions and seeing the new state or error cases. This can help with giving feedback to players on what actions are possible in a given board state. Only highlighting cards that they have resources to play for example. In this way, the client can be made slightly more smart - but I can keep scope low by only implementing some of these things as time permits, while still enforcing the rules in a cheat-proof way on the server.
Project management day 1
So It's "flow". Cool theme! A brainstorming session with my friend on the themes earlier yielded a card game where the cards you play will flow around the board, affecting your opponent when they get on their side. We'll see if that sticks - the first few days are going to be very focused on the technical challenges.
I thought I should write down everything I hope to get done today. Until I have really ironed out my design, it wouldn't make much sense to plan further than that. You can check my last entry for the initial spec of what I hope to get done in a week. I have made an addition - I am going to implement an ai mode and a hotseat mode. This is because I'd like the game to still be easily playable if the server is gone, and to let players get a taste even if they can't find someone to play against. Also, it gives me a backup plan if I have trouble fitting in the online components. However I will develop everything so that it would work online even if I don't implement that component.
For today, roughly in order:
Create basic client - opens a window and displays game objects
Create basic game model - find the mvp and implement the model for that
Serialize model - make sure the model can be serilaized for networking
Render model in client - the display should match the model, and be able to update (rebuild itself?) when the model updates
User interface to modify model in memory - give players something (anything) they can do
Objects in model for multiple players - multiple player components, and player unable to mess with the other player's objects
Hotseat mode for basic game - switch current player when "end turn" is pressed
Define basic game mechanics - start designing the rest of the game by building on the mvp mechanics (no code for this)
That's 8 items, an hour each puts me to bed time, so hopefully they take a little less time than that. I think I will be pretty happy if I can at least get to the first user interface part though.
Day 1 status - we are displaying stuff on the screen and have some input
So I spent 3.5 hours on my first action item. Heh. Well, I have zero experience with Kivy so that's understandable. I ended up throwing out a lot of the elements that kivy includes to try and make things easier, because they just made things harder and used less code.
Day2 - We haz a model and a view!
Today I put kivy frustrations aside, and set about working on the model. I'm going for a slightly weird approach, being that I want to be able to quickly implement the multiplayer part of the game. If the model can be represented 100% as a python dictionary, then it should be feasible to just pass around json strings representing the model changes. The interface for modifying the model is also reduced to specific string arguments to a function call. Here is the object I want to manipulate, and here is what I am doing with it.
To test that the model is working, I have a small unit test, and also set up my kivy client to talk to an in memory version of the model. When you drag a card and let go, it passes the request into the model. If there is an exception - which represents some change request that the model has rejected - the view simply animates the card to return to where you dragged it from. Otherwise, we make sure our widgets are updated to the positions that match the model, adding and removing cards if necessary. The animation system makes it easy to have some pleasing player feedback even though the model change was very discrete.
For this "game", on your turn, you can play a single card into the other player's hand. And then the turn automatically passes. In the above video, you can see that I have set up this simple test in a hotseat mode, where the current player the input is targeting switches when the turn switches. The white section is the current player's hand.
I am a day or so behind my original schedule, only accomplishing the first of my listed tasks :)
So I'm already preparing myself to lower my expectations. Nothing in the game yet resembles flow, and I haven't really done any game design yet (although I have an idea) So I'm going to strive to at least have the client working how I like it. If there isn't much of a game to play, so be it! I didn't realize how frustrated I would be working with kivy. I'm so much more experienced with pygame, and pretty out of practice at game programming. But I'm not sure I like how kivy is designed outside of the main use case it was designed for (apps with standard gui).
(Also I have a new cat, and she is quite a needy one so far - always wanting attention and not letting me sleep!)
To further the primary online goal, my plan for tomorrow is to have the client connect to a server that is running a single ever present game. Api something like:
/join - returns player1 or player2
/play/card_id - sends the command to play a card (which automatically ends the turn). Also returns updated state.
/getstate - returns the current game state
If it's not our turn, the client can just call /getstate once every couple seconds. If it's our turn we don't need to call it, we'll just get the state when we play the card.
Day3 - server
I decided to write the server in flask, as it is a pretty common small framework people use. I sort of wanted to use hug, since my api should be only json and I don't need any templating or html, but I wanted something that could come with authentication that would be really easy to copy and paste or quickstart. I found the answer in flask faster than the answer for hug, which is less common. I chose flask-security which has most of the pieces, and away we went.
There is now an api with the following commands:
- You can register a new account
- You can create a game - you are randomly assigned to either player 1 or player 2
- The /home route gives you some user state, including a list of games you are a player in
- You can join a game - you are slotted into the unassigned player, and there is verification that the game is open
- You can ask for the current state of a game - this gives you all of the game state, in the same format the client understands. There is verification that you are actually a player in that specific game
- You can play a card - there is verification that it is currently your turn. The state of the game is appropriately transformed according to the game model I wrote yesterday, and as a shortcut for testing, the game model is asked to end the current player's turn.
Through api commands, you can play the game, just like you would in the client. Everything is persisted to a database, so asyncronous play is absolutely feasible. Cool! The next step is to hook up the client - I just have to figure out how to get the kivy networking to authenticate with the server, and save any session cookies that need to be kept around.
Oh... the most important thing to do now is design the game. I can do a paper prototype and take it to the board game design group on wednesday. If I finish the client on thursday, that gives me about 2 days to implement whatever game I designed into my functioning game model.
Day 4 - Multiplayer is working!
Multiplayer is working! Pretty good for four days of work eh. Right now I have the accounts hard coded. If you pass "0" at the command line, it launches with one account, and connects to a hardcoded in-progress game on the server. If you pass "1" at the command line, it launches into that same game from a 2nd account. The server is the same as it was in the last update, we just can see the state changes from the client now. There are also a few client-side niceties - it won't let you grab any pieces if it's not your turn, and it won't let you drop a piece that's not yours.
Without those, the server would still prevent those because it follows the model. But it is more sensible for the user to see what they can and can't do.
Haha I thought I could get this far on the first couple days :P Half a week to implement an actual game now, that sounds doable. It won't be a good game...
Next technical step though is a menu screen to choose your account, create games, and join/resume games. I think for registering accounts I'm just going to pop up a webbrowser.
I don't know why, but I feel like I'm moving backward
I spent a lot of time fighting the kivy layout engine. I predict I will waste the next 3 days doing the same. I did design some of the game, and my friend made some really good looking art. We'll see if it actually sees the light of day!
If all goes well:
- Tomorrow - model the game properly and generate cards players can collect
- Friday - create and tweak the game interface
- Saturday - testing and polish
Day 5 - Player hands and beginnings of the grid
I stayed up a little longer to tinker and managed to get a few key items implemented.
First, I added a grid type to the model, because the card game I'm making is going to involve cards being placed into the "flow of magic", a 4x3 grid of cards. Each turn, the cards on the outside of the grid will rotate and activate depending on where they are placed. My current model for "spaces" keeps all of the card data in a list. Rather than make the grid type a separate concept, I've added an organization field, in addition to the list of card data. This field keeps track of which position each card is in. Eventually I'll need to add helper methods to make accessing the card data according to positions a bit easier.
Then, I modified my game interface code to strip out the temporary card fields that were there previously, implementing things more like what will actually be in the game. I started with the player's hands. Now, I show your hand on the bottom of the screen, and your opponent's on the top. When the opponents hand is displayed, I also overwrite the source images with the card backs so you can't see their cards. I also improved the layout: I had to shrink the cards down to make room for the grid, which then necessitated a way to zoom in on the cards in order to read them. Finally, I made the animation code for the cards happen one by one rather than all at once. This isn't too important now, but the game is likely going to have 3 or 4 actions pop off when you end your turn, and it needs to be a little more clear what is going on.
Another minor addition I haven't posted here yet is the ability to choose your login account, as well as either creating a new game, joining an open game, or resuming a game you have in progress.
I lied - one more diary entry with magic card placeholder images
I ended up with only about 3 hours to work on the game yesterday, so not enough time to make a progress report. I was going to add a few more SPACE types (spaces are a logical grouping of cards), and then hopefully start adding some basic gameplay. I started by adding a deck that you can draw from into your hand. But I had some issues with my 100 line code that syncs the state.
Both the deck and the hand have slightly different properties whether it is YOUR space and whether it is your OPPONENTS space. Clearly, you should be able to draw from your deck into your hand. In addition, your hand should be faceup while your opponents is facedown. With the existing architecture, I would have had to add even more if statements to support the deck. And then when I got to the other and even more complicated spaces, like the play area, there would be even more if statements, to sync the state into the appropriate widget objects.
So I spent most of my time yesterday trying to generalize this process - and I'm still not quite happy with it. Essentially, we can now set up a filter when we create a widget that helps it connect to where it should sync from later. One niggle is that when you host a game, it gives you a player object which randomly assigns you to player 1 or player 2. So to determine who the "other" player might be, we just set a string based on which player you were randomly assigned. This is pretty ugly but it works OK for the time being. Refactoring was a pain with having to change code in the model, the server, and the client. Why am I doing multiplayer for pyweek...
After messing with refactoring for a while, I did manage to quickly implement a deck construct.
Tonight - you will hopefully see the real cards.
Day 7 - real internet, packaging, art
I was finally able to start adding my friend's art. (No more copyright infringement!) I've cleaned up some code, and then made sure I could at least get a distributable windows build to run and connect to my web server.
So there is a backup and no one can say I didn't create a multiplayer interface that accomplishes the goals I set out to accomplish!
I do hope to spend the next 16 hours adding some actual gameplay of course, but we'll see...
I'll definitely continue development post pyweek.
It's actually a game
There was no time for polish or even adding something as basic as a readme. But somehow I managed to pull in the basic mechanics at the last minute. In lieu of a readme:
Download the source code to run it outside of windows. Requires kivy but shouldn't need much else. Run main.py
Download the windows exe to run it on windows. Run FlowingMagic.exe after extracting.
You can use a test account (firstname.lastname@example.org/password or email@example.com/password) but you can register your own. Don't use a sensitive password I don't know how secure this server is. I'll harden it in the next few days as well.
How to Play
The goal is to deplete your opponents life force. There are two kinds of cards: instant and flow. Flow cards are placed on an empty space on your side of the board, instants will activate immediately. Drawing and playing cards costs mana: drawing is 1 mana per card, cards have a mana symbol in the corner that shows how much it costs to cast.
Flow cards: when you end your turn, you gain 1 mana, and the "FLOW OF MAGIC" rotates clockwise. If a flow card flows into a slot that matches it's color, it will activate.
When you win or lose, you can concede to close the game.
Play cards by dragging from the hand on the bottom to a spot in the play area, draw cards by dragging from your deck on the left.
If you are unable to drag cards from your hand or deck, it's probably not your turn. You may have to log off and on again at times. There will be bugs.
The Post-mortem, Post-release, post
Thanks for all of the feedback everyone! It was a really successful pyweek and everyone made cool games. Your responses will help as I continue to work on the game (and engine). 66% reported not working shows that I was too ambitious, which I pretty much knew by thursday when I had yet to have a functioning game of any kind. I was actually shocked that the final version, at least from what I could tell, "worked". I finally solved the main remaining issues Saturday morning and implemented the rest of the game logic in the next few hours.
Making a mulitplayer game for a contest like this is not recommended. The biggest issue is with the judging process - the game requires 2 players to play, and none of the judges are going to sit around and wait for an opponent. (I didn't really implement any real matchmaking either). I certainly wouldn't! I also made a poor decision of randomly choosing which player is "first" - since you are restricted from making moves during your opponents turn, the effect this had was that new players are randomly going to have a "frozen" screen with no way to interact, and aren't going to sit around and wait for the opponent to join to take the first turn.
Beyond the multiplayer "mistakes", I also was using a new framework, kivy. Some of the "not working" were probably due to issues getting that framework installed. I didn't really provide guidance on this requirement or the version the game was meant for, so not doing myself any favors there! I actually had a difficult time getting this framework to lay out the widgets the way I wanted them to. That was actually a bigger technical challenge for me than building the netcode. I don't know if it's a case of my brain not working the way the kivy creatores set it up, if I made too many assumptions and didn't read the documentation clearly, or if the documentation is lacking. There are some patterns it uses that I haven't had a lot of experience with, so I'm happy to blame myself.
I think the main features that fell on the floor due to my slow start with the framework, are player feedback. There is no description in the game of how the game works or what you are supposed to be doing. There's very little clear feedback showing why you couldn't do a thing, or what change your doing a thing actually did, making everything very confusing. I probably could have polished these elements if I had gotten the game part done a day earlier. Although I don't know - each new widget or animation was going to be hours of tweaking values to make kivy display it where and how big I wanted it to.
My main goal was to get a jump start building a multiplayer card game engine. I knew going in it was very ambitious, and wasn't sure I'd be able to pull it off. I'm pretty happy with the results, and now have something to build on for this and future card game experiments I'd like to do. The next step is to clean up the code a bit, and then work on those feedback pieces to make it easier to keep things clear. I'm interested in the possibilities, and because I used kivy, I can actually make apps - which is the real goal here!