Last class and homework, we worked on creating a networking protocol to implement our rock-paper-scissors game. This homework, we are going to start the basic framework for using the network protocol to implement the game. To do this, we need consider states the currently is in. Because RPS is a pretty simple game, we have only N states:
It's important to understand that the states above are just a list. It helps to consider all the possible states a game can be in, and how the game moves from one state to another. Sometimes it helps to draw out the states in a diagram. Here is a diagram I drew to show the logic of the states:
Our job is to implement these states. To do this we will implement a very common design for games, which is called a state machine. A state machine is very simple to describe, and very useful for implementing all kinds of processes. In code, we use an enum
to represent the states. An enum
is a special type used to take on one of several values of a given type. You have already used enums in Raylib. The Colors
enum for example is a list of predefined Color
structs. Defining an enum is pretty easy:
enum GameState {
Start,
Playing,
GameOver
}
Now the type GameState
is usable just like an integer, and can have one of 3 values, GameState.Start
, GameState.Playing
, or GameState.GameOver
. You can add as many states as you need.
To handle what to do at each state, we need some input. Something has to change either by the player, or by a timer, or a message received, or something, that moves the current game state to another state. The new state is waited on until something else happens. It is totally up to you what states you want to define, and how the game moves from one state to the next. Each of these moves is called a state transition.
For an exercise before the real thing, let's implement a simple state machine. In the class, we will implement the real state machine, so it will be good to have some practice on this. Here is some starting code, including a state:
import std.stdio;
import std.string;
import std.conv;
void main()
{
enum State {
FirstNumber,
SecondNumber,
ThirdNumber,
Unlocked
}
int first = 1;
int second = 2;
int third = 3;
State state = State.FirstNumber;
while(state != State.Unlocked)
{
// display a prompt
// [Insert code here]
// read a number
string line = strip(readln());
int num = to!int(line);
// figure out next state
// [Insert code here]
}
writeln("You have unlocked the lock!");
}
Imagine this is a combination lock, with 3 dials (the combination is 1 2 3, you may want to change that). But we want to give hints to the user that they completed each number. So here's what the code should do:
State.FirstNumber
, ask the user for the first number. If they enter it incorrectly, tell them, and let them try again. If they enter it correctly, set the state to State.SecondNumber
State.SecondNumber
. This time, set the state to State.ThirdNumber
if correct.State.ThirdNumber
, but this time set the state to State.Unlocked
if correct.Each of these things should be done in the loop, and the next step should be done after looping again. Why do we do this instead of just handling the next state? Because in a real game, you need to handle other things in the loop -- updating the display, reading network data, etc. You handle one thing, update the state, and then let the loop handle the next thing.
For the next class, we will be doing a very similar thing with the states above. If you haven't constructed the code yet from the discord messages, here is the whole starting point. Everyone get this code in their visual studio code, and have it running so we can work on it. If you need help with getting it running, let me know.
import std.stdio;
import raylib;
import enet.enet;
import client;
// this is the message we will send to the others
enum playerchoice {
undecided,
decided,
paper,
rock,
scissors
}
struct PlayerInfo
{
string name; // players name
playerchoice weapon;
bool playerActive;
}
// rules:
// Send PlayerInfo out whenever
// a. you connect for the first time
// b. a new peer is added.
// c. you change your choice of RPS
// When PlayerInfo is received
// store the player info associated with the Id of the player
struct GameStartMessage {
}
// running a game:
// 1. send message that a game should start
// 2. players pick a weapon
// 3. send PlayerInfo with decided message
// 4. once everyone has decided, every client then sends the true weapon picked.
// 5. all clients decide who wins the round.
// 6. in case of a tie, after 2 seconds reset all players to undecided. Goto step 2.
// 7. If there are winners and losers, all losers are out of the game, and winners continue
// 8. If there is only one winner, the game is over, reset.
void main()
{
InitWindow(500, 500, "Rock Paper Scissors");
PlayerInfo[size_t] players; // state of all the users
enum State {
GameStart,
SelectWeapon,
WaitForReady,
WeaponTransmit,
CheckRound,
Lost,
Won
}
State state = State.GameStart;
playerchoice myWeapon = playerchoice.undecided;
// initialize enet
enet_initialize();
// create the client
Client client;
client.initialize("codingcat.club", 6666);
SetTargetFPS(60);
while(!WindowShouldClose())
{
// handle network traffic
client.process();
BeginDrawing();
ClearBackground(Colors.WHITE);
// client.validPeers is an array of ids of other players that are
// currently registered on the server.
foreach(idx, id; client.validPeers)
{
}
// Draw anything else you need for the User Interface.
EndDrawing();
}
// disconnect the client from the server
client.disconnect();
// shut down the networking system
enet_deinitialize();
// close the window
CloseWindow();
}
©2021 Steven Schveighoffer