Files
Lee Hanken 7c8efd2228 Initial commit: HPR Knowledge Base MCP Server
- MCP server with stdio transport for local use
- Search episodes, transcripts, hosts, and series
- 4,511 episodes with metadata and transcripts
- Data loader with in-memory JSON storage

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 10:54:13 +00:00

357 lines
30 KiB
Plaintext

Episode: 2748
Title: HPR2748: Writing Web Game in Haskell - Special events
Source: https://hub.hackerpublicradio.org/ccdn.php?filename=/eps/hpr2748/hpr2748.mp3
Transcribed: 2025-10-19 16:19:24
---
This is HPR episode 2007-148 entitled, writing web game in Haskell, Special Events.
It is hosted by Tuku Toroto and is about 44 minutes long and currently in a clean flag.
The summary is, Tuku Toroto walks through implementation of Special Events in web-based
game.
This episode of HPR is brought to you by An Honest Host.com.
At 15% discount on all shared hosting with the offer code HPR15, that's HPR15.
Better web hosting that's Honest and Fair at An Honest Host.com.
Hello listeners, I'm Tuku Toroto, Tuku Toroto for short and you are listening to Hacker
Public Radio.
This episode is about making of gaming in a Haskell and this time I'm focusing on the Special
Events and mostly on the server side.
So the goal for this I got from our kid who is in the kindergarten with the friends came
up with the Kragi worms and told me about them.
These are small 10 cm 4 inches long worms that burrows in the ground, are drawn to foam
fields and large concentrations of people.
They are dangerous, eat harvest and sometimes even people and you have three options.
You can either avoid them, you can try to fight them or you can try and tame them because
they can improve the quality of the soil.
All these I was told by the kid so and then I was told that you have to make this in
your game.
So off we go.
This is building on top of the new system that I talked about in the previous episode.
I think it was 200733 and it uses quite a bit of structure from that one.
I'm not going to go through all of the same things again, only the parts that are different.
So there's a resource, the server that you can read, just make a get and the URL address
and then you get the list of the news and if there's any special events that are written
in the same structure.
So we have to have the same to change and from change and to DDO, from DDO instances that
I'll talk about the last time and not going to go through that part.
So only concentrating on what happens between the database and the actual simulation and
a little bit on the before sending the data to the client.
So the first real difference comes the news and special events are stored in the database
and they are stored in the station because I want to have lots of very structured data
and I'm using a relational database and that's a bit a bad match.
So I have to serialize complex data into text, so that I can store it there.
And when you are loading the data from the database, when you are using it, it will give
me all the news.
There's this part where they said check that if it's a special news, you instead of just
returning the database ID and the news article, we are going to add available options to that.
And if that's a regular news, we are just returning the key news article pair.
And this is a case structure I didn't find a better way of doing this.
I seem to be writing these a lot.
They have to be a better way of doing this.
I have to try and find that but I haven't been able to find out.
So why are we getting the, why aren't we storing the options into the database?
This is because between the time that the special event has been stored in the database
and it means the time it's loaded, the situation might have changed, it might be that the
you're not even owner of the planet anymore or some scientists came up with something that
you can use against the rooms of anything.
So basically I have to evaluate the options every single time somebody requests the info about
that special news.
So where do the special events options come from?
These are, there's a yet again a type class that defines what the special news or special
event order has to be able to do.
It gets a three-parameter type and in this case, in the case of the cracky rooms, there's
the cracky rooms event, cracky rooms choice and cracky results that are given as a parameter.
These three things are the what encapsulate everything that is related to this cracky attack
special event.
So it's the special event of type cracky rooms event.
The choices are defined in the cracky rooms choice and the result of the simulation running
is cracky results.
And there's a two functions, event options is what we are interested right now, it's
for listing current options and then there's the resolved event that resolves the event
according to the choice that player may or may have not been made.
Hence it has a maybe as a type, so it options that are available are written as a list.
These contain a title explanation of the option and a tag and this tag is what, when the
client is sending info back to the server that the user has selected, this course of action,
the tag is the what is, or option choices, what is, what is used to identify that.
So it's a record of three fields.
The explanation of the choice is a list of text.
I was originally thinking that the text would be enough, but then I realized that list
of text is better because then I can have multiple items there and the server doesn't take
any option, opinion, how it should be shown to the client, client can sort them all in a long
line or group them somehow or put them one after another on a different lines.
So all those layout choices are done on the client side, so it doesn't have to worry about
all.
In this particular case, the options don't depend on the current situation, so there is no
database access here. It just creates a list of three user options.
In the show notes, I'm not going to spell all of the code out, it's pretty long
and it basically just repeats, but there's the first one, that first constructs
user option, where the title is, avoid the worms and the explanation is keep using fields while
avoiding the worms and hopefully eventually leave. Next line, 50 units of biological
loss and 25% chance of worms leaving, so you are hoping that by avoiding the worms they will
eventually move away and the option choices evade worms.
And then there's the similar ones for the attacking and trying to use them in your other
improving the soil. And I like spelling out the results,
opposite results, to the players, because then they can make choices, they can make
informed choices, it's not just a blind guest that they are going to do this, do that,
because realism in the case of cracky attacks is maybe a bit incorrect word,
but I want to think that they are not making a, in the simulated world, they are not making
a snap decisions, but they are having some sort of discussions and they are some sort of
specialists and some professionals and people have opinions and then they can gather all
this information and give this to you, to the player and say that, here's what we know and
here's our options, please help us to make a choice what we should be doing.
And if there would be a, if the game would be a more complex at this point, I could make a
database query here and check that if there's a cracky worm specialist present on the planet,
they could give me a fourth option that has some info or that cracky worm specialist
improve the chances of doing something and change the options accordingly.
So now that the data has been sent to the client,
so on to the user, they can bond up out which, which choice to make, or they might even
forget to make the choice or they might be completely over, not be able to make the choice,
but in that case, there would be nothing to select, but if they select, the data is sent,
the choice is sent back, they can change their mind, somebody else in the faction,
this is a multiplayer game where players can form factions and share resources,
press responsibilities and such. So in such a situation, one player might show that he
via coin to try and take them and they can, they could have a discussion about this,
or not, they might just decide to select that, this is a correct choice to make.
So in any case, choice is made and that is sent back to the client, there's an update,
so it's a HTTP put and the whole structure of the special event is sent back.
And code is relatively simple, yeah, no, not simple, relatively straightforward,
and the first, we are just checking the authorization, checking that the player is logged in and
member of a faction and then checking that the message that comes as in the body of the HTTP request
is a special event because you're not allowed to edit regular news items, at least not at this point,
at least and definitely not if you are not an administrator user, but that's a stuff that I haven't
yet written. And then the code loads the message and it pays attention that the loading is done
by the news ID and the faction ID, the combination of them because otherwise it might be possible
for a mentor of different faction, performing a change to an event that is not concern of them,
so the faction ID has to be checked here. I'm doing an integrated, I'm loading a list of
messages where the ID is what we are interested of and the faction ID is the
faction ID of the version logged in and then just picking the first one off from the
the list. Actually in this case, I'm not even picking the first one, I'm just running an update if there's a
if the amount of loaded messages isn't zero, then I'm just running an update, changing the content of the
according to the ID and if the amount of the loaded messages is zero, that means that somebody tried
to either modify a news ID, a news that has an incorrect ID or somebody tried to access
a news item of a different faction. In both cases, we are just going to return for
message, not found, resource not found. Here's a bit, bit, bit, bit, nice the problem that I have
yet solved because we are just updating the news content with the or special event content
with what the client sent. We are not doing any checks on the content of the content of that.
This means that if there's a for example, Krakki attack, a crafty or malicious user might
craft a message, where there's a special event of plenty full harvest and use that to override
the Krakki event, they could replace the event and not just the choice that has been made,
they could replace the whole event. So, in essence, they would avoid the Krakki attack and get
a plenty full harvest instead. I have to write some sort of check on this place too. There's
quite a long to do list of little things to do. But in the end, we are writing to the
database on that special event that now this choice has been made. Okay, then comes the
resolving the event. So, when the turn is processed in the beginning of the processing,
the system loads all the special events, handles them and handles them. I decided to do
it that way because then there's not and not at the end of the turn because then there's no,
it makes the processing easier because I don't have to check when the event was created.
Just enough, all the events that are loaded at the beginning of the processing are old ones.
If I were to do that at the end of the processing, then there might be an event that we created
during the current turn and where the users haven't had the chance to react yet.
And I wouldn't want to process those yet, but those will have to be processed on the next turn.
So, I'm doing the processing at the beginning of the turn or simulating the turn. It's easier.
I can just load all the processes, resolve them and then carry on with the simulation.
Okay, so there's a two. This resolve event is defined in that special event type class. It has
in this case, it can be different for each and every special event, of course, but in this case
and usually there's a two case of two branches. One is where the choice has been made,
so the choice is just choice. And the other one where the choice hasn't been made, so the choice is just nothing.
But the overall structure in both cases is the same, so we have to run right at the dot run maybe
the function that is going to do the processing key event there is a contains a database ID and the news article.
No, sorry, but now you saw the call to the special event in inaudible to crack you at the special event.
So, what are run right at the end, run maybe the end what are they doing here? So,
they're related to the monarchs. I'm not going to go through the monarchs because I don't,
I'm not able to explain and teach them in a sufficient way.
The incident is going to try and tell what they are used here. So,
there are monarch transformers, so they basically are wrapping the code in a, like they are taking a,
I have a maybe monarch that is wrapped in a writer, monarch, and inside of the maybe monarch.
More, more structure, but in this part there may be maybe the and wrong and fight at the
are what are interesting, interesting. The writer, the adds ability to record information as the
competition is performed and the maybe the adds ability to stop computing early on if any
subcomputation returns nothing. So, it will, I hope it will be clear when I show
how I'm using those a little bit, little bit later when I go through those functions. They
certainly took me some work to get working. I have used those before, but it has, it has been a
while and I was a bit unsure in which order to stack them, but essentially the SS stack of
monarch transformers that form a completely new monarch. But for example, if the player has chosen
to avoid the crutching rams and keep working on the part of the, part of the fields,
there's this choose to avoid function that does that, does the simulation, what happens in that,
in that gate. So, first is, first it is loading the section information and here's the first
chance of aborting, if the, if the action is not found for some reason. Then, and nothing is
returned, then we cannot continue the competition. So, we are just bailing out early on returning
nothing. And then we are, yeah, we are bailing out early on, going to have a look at the
return return pattern, little bit later. And the choose to avoid function says that it is,
that the event, event removal is what it's returning inside of, that monarch, monarch transforms
structure. And the event, event removal is, is signaling the calling code that has this, has this
event been processed in a way that it can be removed, or is it, is the situation such that it needs
to stay in the database and needs to be evaluated. In the later, later point, because might be
that the crutching are not driven away, they stay there and on the next turn the player has,
player faces the same, same dilemma they have to choose what to do. And then the simulation has to
run again and simulate what happens. And this happens until the event removal is, remove original
event in, in which case the event is finally removed. Or if the, if the processing fails to run,
to run to the completion and decides that I cannot continue for, for some reason, then the event
is also removed because there might be some sort of problem in the system, yes, silently discarding
those special events because they are not that special, all that, that important and giving
the system up and running. In any case, so get faction event, take the event, loads the, loads the
planet that is related to that event, checks that who owns that planet and returns that. And if
they have couple, couple, couple points, that might fail. The planet might not be present anymore,
the planet might be present, but is not owned by anyone, nobody has settled it. Or the
faction that has been, has, has been marked as a owner of a planet doesn't exist anymore.
Any of these three cases happens, nothing is there, and choose to avoid
function, realizes this and stops processing automatically, it doesn't continue, just remains
nothing. And there is no, there's no if statement present here in the call. It says that faction
are all to the left, get faction event. And that, that does the automatically, the second,
if nothing has, has been returned, they'll out that is the, that is what they may be doing here.
Then we are calculating, we are calculating new, so this calculates the amount of the
biological matter that is consumed in, in this case, in this case, in this case, it is
50 units, because farmers cannot work on whole field, they are, so they are producing a
less of harvest. And I could of course have done this in a way that during the, during the simulation
phase where they are actually simulating the amount of, amount of harvest produced, take the event
into account, but I felt that it started to ruin here, because then the simulation code on the
farms is a slightly cleaner, and I, I can be, I can create a report saying that because you were
not using all the, all the farms you lost this amount of harvest, it's all about choices and
figuring out where to put the code and how to structure, so how to structure things, that's the,
that's the trickiest, trickiest thing in my opinion, in the coding test.
And in addition to the naming things, naming is awfully hard in my opinion.
But here, so we are calculating the biological matter, and here we are using the,
that section, that was loaded in the previous stage, we are getting a, what's the amount of
biological matter they have in stock? And if it's more, then what we are, I mean if it's more,
then zero, there's some, some left, we are going to return a double, where the first item is the
cost, that's the 50 units of harvest, the, the cracky consume, and the second element is the
maximum of two things, it's, it's either zero, or it's the current amount minus the 50 units.
So it's always zero or more, but it's always the current amount minus 50.
So, so this, this tells you that we, we consumed, cracky consume, this amount of the harvest and
this is what, what is left. And again, if there is, if there is not, if the amount of biological matter
is zero, there's nothing to, nothing to update, so else is, the nothing is returned and the
competition will come. Then comes part, where you're actually destroying the crops of biological matter,
here we are just updating the database, the, the function information, and putting the
amount of biological matter left there. And then this line, tell crops destroyed cost.
And this is, this is, this is what signals, this is what, what uses the variety, this is the first,
first part where we are lighting to the variety that, some, some information, and yeah,
recording crops destroyed, data there, and the parameter is the cost that was, the amount of
biological matter that was destroyed. And then we are returning just a unit, unit is just on,
it's not, it's not void on nothing, it's just, it's just signal that this, this, this function is
returning something, but it's not something that you can do some computation on.
Basically, you can, I usually think that it's returning nothing in particular, or nothing at all,
it's just something that needs to be returned when there's nothing to be returned.
Okay, and this, tell crops destroyed cost is the, is the, is the reason why we are using
writer because we can, we can record information on that, on that writer instance,
or writer data, however you should be saying that, and we don't have to carry that writer specifically,
we don't have to pass it in as a parameter and we don't have to return as a value, it's, it's in the
context of the function. Oh, context, let the function is executed. So this, this, this is what the
monotransformers are used to add a extra structure into the, into the execution. So you can do
things easier without having to resort to write a lot of boilerplate, like the, one, one part is
that the, the maybe the automatically, automatically stops, keeps the execution of the rest of the
code if nothing is returned, and the priority is carrying along this list of
cracky results where, where I can just record information that has been happening.
Okay, the last, last part, name to remove news is a bit, the name is in perfect, I should come up with another, another name,
but here we are throwing a horizontal die against the odds, and if, if we are beating the odds,
we get the success, then we are telling, telling the, sorry, then we are recording to the right
that the, the, burns the, removed, and we are returning just to remove original event,
signaling the calling system that, okay, this is done, cracky, cracky are leaving, you can get rid of the event.
If it didn't, it, it, we are, it's a recording that the, burns are still present, and not, and returning
the, keep, already, returning, just keep original event, signal in that. Yeah, this is the cracky,
still present, do not remove this special event. So the result of hold this thingy is a
bubble, where the first element is a list of cracky results, and the second element is a,
maybe, event removal, it's maybe because the processing might have, the computation might have,
looked at nothing at some point, in, in which case, the first of the code isn't evaluated or
computed, in which case, remove news was never handled, in which case, we don't have a,
that, die throw, if the cracky, they are present or not, so that's why it's a, maybe,
event removal. And of course, what, the power of the result of the, of the,
disavaluation is the whole lot of database activity, so reading and writing.
Okay, now that this is, now that the pieces are in place, that it's time to put things in motion.
So this, the next step is handled in the, in the, in the, during the, during the processing of
the, the admin has said that, okay, now it's time to run the simulation for one step forward.
So there's a, for, for given faction, we are loading all 100 special events.
And for each of those special events, we are calling handled special events function.
And in there, we are just loading all the, 100 special events, I'm sorry, yeah, we are,
we are loading all the, 100 special events, discarding all the, all the, all the news items that
are not special events, they shouldn't be any, but because it's not captured in the title,
I have to do it here, so, so that I can move from the news item, I can, sorry, news, from news article
into, into a special event. And then we are calling the handles special event for each of those,
which does that processing that I explained just a moment ago.
In any case, the regardless of if the, if the user has made choice or not, in any case, we are,
the handles special event is going to insert a report that tells what the, what the, what was the result of the
simulation. And that's the, that's the final piece of the puzzle. It's a yet another type class
called a result report that takes the same trip, trip parameter, so in all case,
cracky rooms, event, cracky rooms, choice and cracky results.
And the cracky results are what the right that he was writing, or recording in during the
processing, he, he, he, we have a list of things that tell what, what happened,
what, what, what things, yeah, what, what happened during the evaluation or simulation of the
process. And now we are taking those and turning, turning them into a news, which can be then saved
on the database and then the user next time looks at the news articles that report that states
that what happened. And this, this is, this is just a plain news news article that I talked about last
time. The big part of the, of the gold here is to writing out cases or handling for
combination of everything. Because there's a four different, there's three choices that the
player, the player could have made. And then there's cases that they did not make any choice.
So in, for example, if the, if the player did not make choice, then the news article is going to
read, no, this is in what to do about worms had been taken. That's the start of the news articles.
If they decided to avoid, then that, that will be local farmers had chosen to work on their fields
while avoiding the cracky rooms. And then the next part is where they removed or not. And how,
how were they removed? Did we, did we decide, these are to tame them? Did we try to attack them?
Did we ignore them? For example, if we decide to evade them and remove all the successful, it will
read after sometime there has been no new cracky sightings. And it seems that it is now over.
If we decided to avoid them and the cracky decided to stick around, it will read cracky as
we'll present on the planet and hand the farming operations, consider going, see their relative.
So there's a just a little piece of text for each and every option that might have happened.
And here the half scale is helping me because if I'm miss a combination of the, of the option
or result or something, then it will, it will tell me that hey, by the way, this case has not been handled.
And if there's an interest, that is done by checking that if there's a farmers insert element
in the results, if there's one, then the news item will have a text, some of the personal
involved in the event will seriously insert, or if there's not, then it will read, there are no
known reports of personal injuries. And the same thing with the destruction of the harvest,
it might read in the end, 50 units of harvest was destroyed, or it might read that this part of
all these, no harvest was destroyed. So all these things are checked and then tacked together
to form a complete report of the events. And in the code, I'm using the diamond symbol
to combine, combine these text together because text is a high level, it's just a,
you know, well, if it were a string, it would be a list of, list of chance,
but because it's a text, it's something, some, some more, more evolved structure that is,
that is a more performant, but they both are defining monoid instances
that work in the same vein, and the diamond is the monoid operator, in the case of lists,
it's just, conca, conca tenets, the lists together. I talk, talk about monos, at some point I
probably have been talking about them quite often, maybe. I'll try to get rid of them.
So, the diamond is a simple way to conca teneting the lists.
So, there's a quite a lot of moving parts in, for such a simple thing. And in the end,
it uses a couple, couple lines of text, some choices to make, and then the result of the report,
it's kind of, I really didn't expect this to be so, inward thing to code. But now that the whole
pipe or infrastructure or processing, whatever you want to call it, is in place, I'm hoping that
the next special event will be a lot easier to code, because I don't have to think how all these
pieces fit together, I can just look that I need these three classes, one to define the special
event parameters or data, and one to define, what options you have, and one to define
what the result will be. And then the third one to, I thought one to define what the
report that is shown to the user about the whole thing.
There's still some problems left that I want to tackle, like this current system is
in a way that the cracky might add a cut planet that already has a cracky attack, then it has
a two cracky attacks going on, and you can even choose that, I'm going to ignore these rooms,
I'm going to drive away these rooms, and that's just plain silly, but I don't have
system in place where I could track such things yet, I have some ideas,
ruminating, but I haven't been able to crystallize them, or even write them down,
the another one is that the special event might be my fancy replace with another, that's an even bigger
problem, like you could just decide, I don't want to deal with the cracky so I'm going to replace
it with a good harvest, but that part shouldn't be too difficult, just having to prevent that.
And in the end, it might seem that the piece is met nicely, and there was a lot of
planning in a head, trolling all kinds of diagrams and thinking that this piece fits with
this piece and such, but at least in my case, that's definitely not the case, there's
in reality there was a lot of experimenting and errors, and going back and taking a
period and trying different things, I especially in the part where I had to decide to put this
put this stuff as a model, not all of this code is in one file, and I kept getting circular
references all the time, so a module A referring to module P and module
referring back to the module A, and that's not possible in this case, that does not compile.
And the monotransforms were a bit of a headache, but so I really didn't plan all this stuff
on paper in advance, I basically started with the news that I already had,
and then I figured out this is the starting point, and then I had that goal, I want to record
our special elements into some processing with them, and then started feeling my way from
from spot A towards the spot P, and sometimes going back and sometimes taking a period and
detours, but it's sort of like a connected dots, you know where you are, you know where you want to
go, so you try to find a sort of straight or reasonable path between those two points,
and in a way the pipes are helping me to do this, because they tell me that quite concretely
what I have and what I can do. Okay, thanks for listening to this episode, it was a bit long,
I really should try to make this a bit shorter, maybe too long to listen. The easiest way to catch
me nowadays is via email, that should be at the Hacker Public Radio page, or on the fediverse
to tour the atmosphere on that show, so without further ado, I'm Tukuturta at Astra.
You've been listening to Hacker Public Radio at Hacker Public Radio. We are a community podcast
network that releases shows every weekday, Monday through Friday. Today's show, like all our shows,
was contributed by an HPR listener like yourself. If you ever thought of recording a podcast,
then click on our contributing to find out how easy it really is. Hacker Public Radio was founded
by the digital dog pound and the infonomicon computer club, and it's part of the binary revolution
at binrev.com. If you have comments on today's show, please email the host directly, leave a comment
on the website or record a follow-up episode yourself, unless otherwise status, today's show is
released on the creative comments, attribution, share a live 3.0 license.