Players queue for Heroes of Might and Magic
It’s not like we at work don’t have things to do, but suddenly we decided to play Heroes of Might and Magic. So we created a virtual machine, installed the game and started a hot-seat game via RDP.
For notifying about next players turn we created a Slack channel. But soon enough it became annoying to announce next turns manually, so I created a simple web-application for that.
Implementing the queue
I decided to implement the queue with .NET Core MVC, although it can be any other web-framework really.
So, where to store players and current turn? Database would be an overkill, so I went with a JSON file in the web-root (/queue.json
):
{
"players": [
{
"name": "Chad",
"slackID": "U4JDL9QBC",
"playing": true,
"currentTurn": false
},
{
"name": "Martin",
"slackID": "U4JLODQ3F",
"playing": true,
"currentTurn": true
},
"..."
]
}
Here’s how to read a JSON file using Newtonsoft.Json:
public class HomeController : Controller
{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly string queueFile = "queue.json";
public HomeController(IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
queueFile = Path.Combine(_hostingEnvironment.WebRootPath, queueFile);
}
// ...
private JObject ReadQueueFile()
{
using (StreamReader reader = new StreamReader(queueFile))
{
return (JObject)JToken.ReadFrom(new JsonTextReader(reader));
}
}
}
View the queue
Player class:
namespace homm_queue.Models
{
public class Player
{
public string Name { get; set; }
public bool CurrentTurn { get; set; }
}
}
Controller code:
public IActionResult Index()
{
List<Player> queue = new List<Player>();
JObject queueJson = ReadQueueFile();
foreach(var player in queueJson["players"])
{
if (Convert.ToBoolean(player["playing"]))
{
queue.Add(new Player()
{
Name = Convert.ToString(player["name"]),
CurrentTurn = Convert.ToBoolean(player["currentTurn"])
});
}
}
return View(queue);
}
View code:
@model IEnumerable<homm_queue.Models.Player>
<div class="heading"><h3>Total players: @(Model.Count())</h3></div>
@foreach (var item in Model)
{
@if (item.CurrentTurn)
{
<div class="player turn">@item.Name</div>
}
else
{
<div class="player">@item.Name</div>
}
}
Make a turn
Every time next
button is clicked, current player’s currentTurn
is set to false
, and for the next player it is set to true
. If there is no next player (end of the list), then it just starts from the first player in the list.
[HttpPost("/MakeTurn")]
public JsonResult MakeTurn()
{
string nextPlayerID = "unknown";
bool setCurrentPlayerTurn = false;
JObject queueJson = ReadQueueFile();
foreach(var player in queueJson["players"])
{
if (Convert.ToBoolean(player["playing"]))
{
if (setCurrentPlayerTurn)
{
player["currentTurn"] = true;
nextPlayerID = Convert.ToString(player["slackID"]);
setCurrentPlayerTurn = false;
break;
}
if (Convert.ToBoolean(player["currentTurn"]))
{
player["currentTurn"] = false;
setCurrentPlayerTurn = true;
}
}
}
if (setCurrentPlayerTurn)
{
queueJson["players"][0]["currentTurn"] = true;
nextPlayerID = Convert.ToString(queueJson["players"][0]["slackID"]);
}
using (StreamWriter file = new StreamWriter(queueFile, false))
using (JsonTextWriter writer = new JsonTextWriter(file))
{
queueJson.WriteTo(writer);
}
PostToSlackChannel(nextPlayerID);
return Json("OK");
}
Players order in JSON file obviously has to match the players order you have in game.
How to call this method from view:
<script>
function makeTurn()
{
let xhr = new XMLHttpRequest();
xhr.responseType = "json";
xhr.open("POST", "/MakeTurn");
xhr.send();
xhr.onload = function()
{
if (xhr.status != 200) { alert("some error"); }
else { location.reload(true); }
};
}
</script>
Get current player
And a simple REST API for getting the current player:
HttpGet("/GetCurrentTurn")]
public JsonResult GetCurrentTurn()
{
JObject queueJson = ReadQueueFile();
try
{
return Json(
queueJson.SelectToken("$.players[?(@.currentTurn == true)]")["name"]
);
}
catch
{
return Json("unknown");
}
}
Slack notifications
Slack notifications are done via Slack Bot. You only need to set an incoming webhook for the channel:
And then you can post notifications using HttpClient:
async void PostToSlackChannel(string userSlackID)
{
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri("https://hooks.slack.com/");
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Post,
"services/YOUR/SLACK/WEBHOOK"
);
StringBuilder msg = new StringBuilder()
.Append("{\"text\":\"")
.Append($"<@{userSlackID}>'s turn!")
.Append("\"}");
request.Content = new StringContent(
msg.ToString(),
Encoding.UTF8,
"application/json"
);
try
{
var httpResponse = await httpClient.SendAsync(request);
var httpContent = await httpResponse.Content.ReadAsStringAsync();
Console.WriteLine($"[DEBUG] {(int)httpResponse.StatusCode} - {httpContent}");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
Full source code of the project is here.
Social networks
Zuck: Just ask
Zuck: I have over 4,000 emails, pictures, addresses, SNS
smb: What? How'd you manage that one?
Zuck: People just submitted it.
Zuck: I don't know why.
Zuck: They "trust me"
Zuck: Dumb fucks