Where I Got the Idea I am a huge Pirates of the Caribbean fan (no seriously, I'm drooling as I type this just thinking about the new one). About a week ago, I was hanging out in my living room with my father, and he had Pirates of the Caribbean: Dead Man's Chest on. You can bet your bottom dollar I stuck around to see how it played out, despite me already knowing the ending. As you get older and experience things over and over, you tend to notice the more minute details about them; as I was watching the scene with Will Turner and Davy Jones playing a high stakes game of dice, I started to pick up on how the game was played... and it fascinated me. I quickly looked to Google and found that it is a real dice game, known as Liar's Dice or Pirate's Dice. I personally had never heard of it before then, and frankly loved the idea of such a game: quick paced, intense, and pirates played it, I mean, come on, do I need another reason to like it? I headed straight to my work station -- after the movie was over, of course -- to begin crafting an online Pirate's Dice game. Now, while I do admit I could find myself buried in hours of play, I don't think anyone else willing to give it a try would find themselves that addicted; so the question remained: how could I make this into something that a wider audience of people might enjoy, or at least try? A "Dice Game" maker (horrendously generic, I know).
Dice are evil -- pure, unadulterated evil. They bounce around, fly off of tables, and occasionally land on a corner so perfectly that they launch four feet into the air. So, what if you could play any dice game at any time, or make up your own dice game, or just roll some d*** dice because you feel like it? Well, truth is you already can, just my dice whatever merbobber let's you do it all without chasing those things around. Oh and Lua, did I mention it's all in Lua?
How to play Liar's/Pirate's Dice (This is really the only thing you need to read before downloading): There are many, and a I mean many (okay, not really too many) different ways to play Liar's Dice, the version I made to distribute with this framework is one of the more restrictive versions of "Common Hand"; however, most versions have a common goal: to guess the amount of any face value on the field, and have your opponent lose a challenge, or to challenge your opponent and win the challenge.
Rules:
- 2 Players. Each player had 5 dice. They are rolled once, out of sight of the opponent.
- Each player rolls an extra dice, highest value decides to bid first or pass.
- 1 are wilds.
- Bids can neither go down in quantity or face, if I bid 3 5s, my opponent must bid 3 6s, 4 5s, 4 6s, etc. or challenge
- Challenge means you are challenging your opponents last bid, all dice are revealed, if they guessed the amount of the said face, or less, i.e bid was 4 5s there were 4 or less 5s, they win. If there bid was higher, you win.
For more specifics refer to
Wikipedia Liar's Dice is working!!! Download here:Download(mediafire): ~810kb compressed (.zip)Okayyy... So, What is it? The (Really) Brief Synopsis My Dice-whatever-you-wanna-call-it is essentially a Lua framework. I use
Sol2 -- an amazing C++ <-> Lua binding library -- add a "Dice" class, some network classes, some animations/images,
Dear ImGui, throw it all into the compiler, down a cup of coffee, take a few breaks to go outside and shovel some snow, and
BOOM!: you can make dice games in Lua.
So now that you have a really vague idea of what this actually does/is, I'll try to explain some more. Explaining things has never really been my strong suit, so strap in and I'll just take you down code-snippet lane.
The Part Where I Show You Something Useful/Just CodeSol2 stuff
So with every great
(and not so great) idea, you start with the dice:
Dice.hpp
class Dice : sf::Drawable
{
private:
int xFace;
sf::IntRect xTexRect; // Texture rectangle is got by doing {(xFace - 1) * 32, 32} after rolling. Texture file looks like ([1][2][3][4][5][6]).png
...
public:
...
int roll(); //Does some voodoo with std::uniform_distribution, now we have a face value. Because clearly dice don't have a face prior to rolling.
int getFace(); //Incase you didn't feel like capturing it when roll returned it.
...
};
Sol2 lets us do something even cooler: Use it in Lua:
Somewhere inside Source.cpp, because that sh** is a mess.
sol::state Lua;
...
Lua.new_usertype<Dice>("Dice", ...
"roll", &Dice::roll,
"face", sol::property(&Dice::getFace)
); //BOOM Dice is now usable in Lua.
run.lua
myDice = Dice.new()
myDice:roll()
print(myDice.face .. " you can do this fancy thing in Lua where you append things to your print call with .. ")
-- Actually calls our getFace, but looks as if we directly access our xFace.
Rinse and repeat, we need to access the World Wide Web(WWW):
Network.hpp
class Server
{
private:
std::vector<std::unique_ptr<sf::TcpSocket>> xClientList; //This is horrific: I need a Client class, which would then have the unique_ptr to the socket and handle everything gracefully, but for now this works.
...
public:
Server(unsigned short); //Yup, you guessed it (or maybe not): that variable is our port.
std::vector<sf::Socket::Status> broadcast(std::string msg); //Broadcast msg to every client, only string for now, because I am to lazy to implement everything else, hence the [WIP]. Return the status of each send() call, in the order the clients are connected (this is why I need a Client class. D*** it).
sf::Socket::Status send(std::string msg); //Hopefully you can guess what that does.
MessageBundle update(); //Update listens for new connections, and receives messages, then returns a MessageBundle which is just a struct with the string message and integer index to the sender.. I know, I know: the client class.
...
};
class Client //Not the client class I need to make, this just does client side stuff, send messages, and receive, etc. It's very similar to server, so I'm not gonna bother typing it all out.
{
...
};
Pollute Source.cpp with some more sol2 usertypes, (Including sf::Socket::Status enum, we need that). And we are well on our way to making a dice game.
Dear ImGui stuff
So I could I just have some static GUI system that whomever decides to make a game has to just deal with, and some of you may think this is the better way to go, buuuuuuut I decided to let loose ImGui into the world of Lua.
Using Sol2's set_function I was able to do something like this for a selected few of ImGui's functions:
Lua.set_function("ImGui_Begin", &ImGui::Begin);
some of them required some extra work regarding overloads and variables, etc. but I got the functions I found useful working, and plan to get the majority of the added at some point. He is a simple gui I might make for a dice game:
function Update() -- Update is automatically run once per cycle in our C++
ImGui_Begin("Menu")
if ImGui_Button("Press me") then
ImGui_Text("How dare you!")
end
ImGui_End()
end
Okay, okay. Enough Talk, Where do I Download it? Unfortunately, it is in a horrible state to distribute, I'm not gonna do that yet. I'm going to clean it up a bit, and optimize it (
*cough* *cough* Client class) then dice games shall once again rule the world (I don't think they ever did, but lets pretend). Hopefully in a couple days I can release the kraken
(get it), and you can enjoy some dice games. I also wanna setup this spare RasPi of mine as a server, so everyone can try their hands at liar's dice, and not just download a useless framework.
I know there will be at least 1 person -- hopefully -- who will be interested in looking at the Liar's Dice Lua file to learn how the Framework will work, and feel. So here it is, keep in mind this is in dire need of being cleaned up, not just the Lua, the C++ too. I made a Player class, which is fine, but I feel it is needless garbage, and will without a doubt be removed before I release this. Without farther ado, the files:
Run.lua
DiceTextureLoc = "res/images/OldDie.png"
LastBet = {1, 2}
GuiCurQuantity = LastBet[1] + 1
GuiCurFace = LastBet[2]
yourTurn = 0
gameOverMsg = "You lost!"
gameOver = false
totalDice = 10
cn = ClientNetwork.new()
function mysplit(inputstr, sep)
if sep == nil then
sep = "%s"
end
local t={} ; i=1
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
t[i] = str
i = i + 1
end
return t
end
function Init()
if cn:connect("127.0.0.1", 5576) == SocketStatus.Done then
print("Connected!")
cn:send("join")
else
print("Connection to server failed!")
end
LocalPlayer:giveDice(5)
LocalPlayerHand = {0,0,0,0,0,0}
LocalPlayerHand[1] = LocalPlayer:rollDice(1)
LocalPlayerHand[2] = LocalPlayer:rollDice(2)
LocalPlayerHand[3] = LocalPlayer:rollDice(3)
LocalPlayerHand[4] = LocalPlayer:rollDice(4)
LocalPlayerHand[5] = LocalPlayer:rollDice(5)
end
function Update()
message = cn:update()
if message ~= " " then
print(message)
if message == "Full" then
print("Server full")
elseif message == "go" then
yourTurn = 1
elseif string.find(message, ",") then
LastBet = mysplit(message, ",")
LastBet[1] = tonumber(LastBet[1])
LastBet[2] = tonumber(LastBet[2])
GuiCurQuantity = LastBet[1]
GuiCurFace = LastBet[2]
yourTurn = 1
elseif message == "stop" then
yourTurn = 0
elseif message == "chal" then
cn:send("hand," .. LocalPlayerHand[1] .. "," .. LocalPlayerHand[2] .. "," .. LocalPlayerHand[3] .. "," .. LocalPlayerHand[4] .. "," .. LocalPlayerHand[5])
yourTurn = 0
else
gameOver = true
gameOverMsg = message
end
end
ImGui_Begin("Menu")
ImGui_BeginChild("Quantity", ImVec2.new(175, 140), true)
for i = 2, 10 do
if i ~= nil and LastBet[1] ~= nil then
if i >= LastBet[1] then
if ImGui_Selectable(i, GuiCurQuantity == i) then
GuiCurQuantity = i
end
end
end
end
ImGui_EndChild()
ImGui_SameLine(190)
ImGui_BeginChild("Face", ImVec2.new(175, 140), true)
for j = 1, 6 do
if j ~= nil and LastBet[2] ~= nil then
if j >= LastBet[2] then
if ImGui_Selectable(j, GuiCurFace == j) then
GuiCurFace = j
end
end
end
end
ImGui_EndChild()
if (LastBet[1] ~= GuiCurQuantity or LastBet[2] ~= GuiCurFace) and yourTurn == 1 then
if ImGui_Button("Bid") then
LastBet = {GuiCurQuantity, GuiCurFace}
GuiCurQuantity = LastBet[1]
GuiCurFace = LastBet[2]
cn:send(GuiCurQuantity .. "," .. GuiCurFace)
end
ImGui_SameLine(75)
end
if (LastBet[1] ~= 1 or LastBet[2] ~= 2) and yourTurn == 1 then
if ImGui_Button("Challenge Bid") then
cn:send("chal")
end
end
if gameOver then
ImGui_Text(gameOverMsg)
end
ImGui_End()
end
function Close()
cn:disconnect()
end
function Debug()
print(yourTurn)
end
Server.lua
sn = ServerNetwork.new(5576)
PlayersHands = { p1Set = false, p1 = {"hand",0,0,0,0,0}, p2Set = false, p2 = {"hand",0,0,0,0,0} }
LastBet = {1, 2}
Connected = {0, 0}
GameOver = false
function mysplit(inputstr, sep)
if sep == nil then
sep = "%s"
end
local t={} ; i=1
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
t[i] = str
i = i + 1
end
return t
end
function printTable(inputTable)
for i = 0, #inputTable do
if (inputTable[i] ~= SocketStatus.Error) then
print(i .. ": " .. inputTable[i])
end
end
end
function CountFaces(hand, faceToCount)
ret = 0
for _, face in ipairs(hand) do
if face == faceToCount or face == 1 then
ret = ret + 1
end
end
return ret
end
function Init()
end
function Update()
message = sn:update()
if message.msg ~= " " then
print(message.msg)
if message.msg == "join" then
if Connected[1] == 0 then
Connected[1] = 1
print(sn:send("go", message.sender))
elseif Connected[2] == 0 then
Connected[2] = 1
else
sn:send("Full", message.sender)
sn:disconnect(message.sender)
end
elseif string.find(message.msg, "hand") then
if not PlayersHands[1] then
PlayersHands[2] = mysplit(message.msg, ",")
PlayersHands[2][2] = tonumber(PlayersHands[2][2])
PlayersHands[2][3] = tonumber(PlayersHands[2][3])
PlayersHands[2][4] = tonumber(PlayersHands[2][4])
PlayersHands[2][5] = tonumber(PlayersHands[2][5])
PlayersHands[2][6] = tonumber(PlayersHands[2][6])
PlayersHands[1] = true
elseif not PlayersHands[3] then
PlayersHands[4] = mysplit(message.msg, ",")
PlayersHands[4][2] = tonumber(PlayersHands[4][2])
PlayersHands[4][3] = tonumber(PlayersHands[4][3])
PlayersHands[4][4] = tonumber(PlayersHands[4][4])
PlayersHands[4][5] = tonumber(PlayersHands[4][5])
PlayersHands[4][6] = tonumber(PlayersHands[4][6])
PlayersHands[3] = true
end
elseif string.find(message.msg, ",") then
LastBet = mysplit(message.msg, ",")
LastBet[1] = tonumber(LastBet[1])
LastBet[2] = tonumber(LastBet[2])
sn:send("stop", message.sender)
sn:broadcast(message.msg, message.sender)
elseif message.msg == "chal" then
challenger = message.sender
sn:broadcast(message.msg, -1)
else
print("Unknown message")
end
end
if PlayersHands[1] and PlayersHands[3] and not GameOver then
FaceCount = CountFaces(PlayersHands[2], LastBet[2])
FaceCount = FaceCount + CountFaces(PlayersHands[4], LastBet[2])
print(FaceCount >= LastBet[1])
if FaceCount >= LastBet[1] then
sn:send("You lost! There were " .. FaceCount .. " " .. LastBet[2] .. "s.", challenger)
sn:broadcast("You won! Your opponent challenged your bid, but there were " .. FaceCount .. " " .. LastBet[2] .. "s.", challenger)
else
sn:send("You won! There were " .. FaceCount .. " " .. LastBet[2] .. "s.", challenger)
sn:broadcast("You lost! Your opponent called your bluff, there were " .. FaceCount .. " " .. LastBet[2] .. "s.", challenger)
end
GameOver = true
end
end
function Close()
--sn:shutdown()
end
function Debug()
print(PlayersHands[2][2] .. " " .. PlayersHands[2][3] .. " " .. PlayersHands[2][4] .. " " .. PlayersHands[2][5] .. " " .. PlayersHands[2][6])
print(PlayersHands[4][2] .. " " .. PlayersHands[4][3] .. " " .. PlayersHands[4][4] .. " " .. PlayersHands[4][5] .. " " .. PlayersHands[4][6])
end
Ehhem.. screenshots. So, I am not artist, and I didn't make the dice texture either, I just modified some things and made it into a texture, so it's not pretty. I mean, it's actually just five dice and a menu. But the h*** with it: here are some screenies.
Client View:
Server View:
Nice, nice. But What All Could I Do with this? Everything is exposed to Lua, you could even write AI Code for offline mode play, Admittedly, I attempted to and failed miserably, and decided to stick to something I know how to do. AI's are no joke man. Here is a list of things I have thought of, but this is definitely not the exhaustive list.
Features(?):
- Automatically run functions (Init(), Update(), Close())
- A Debug() function, press F2 at any point to do whatever
- Create, roll, and get the value of dice.
- Communicate with a server, server tools to manage it
- Lists folders in "Games" directory, game folders should include "server.lua" for servertools, and "run.lua" for the client.
- Very extendable: possible to make AI, for offline play, possible to make rating (ELO?) system, and store data server side.
Finally, the end It seriously took me about two hours to write this, but strangely, I enjoyed it. I hope I touched on everything, and explained well enough what this is. If not, feel free to ask questions. I can't say how often I will be checking this, I will probably come back to talk about updates, and possibly a few times while I'm supposed to be paying attention in school. Last message: if you read all of this, you are a true trooper, and thanks for that.