News:

Take a look at what's going on, at The Town Crier!

Main Menu
Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Topics - Son of the King

#1
Stories and AARs / Thurazur's Field Notes
June 29, 2025, 12:42:18 AM
WARNING: Spoilers ahead for the Pathfinder 2e Beginner's Box adventure.

This is a little AAR of the Pathfinder game I played in (as a player for once!) this weekend. More to come as I write my scruffy notes up into something that feels like the diary Thurazur might write in the evening.

There's a second session planned in a few weeks, so hopefully this tale ends up with quite a few entries.



Thurazur's Field Notes

Otari

It was around lunchtime that we came upon the seaside town of Otari. The road wound down through the vineyards into town as Sister Cynthia and I arrived, looking for adventure and interesting discoveries. At this point I've been travelling with the cleric for a few months; she's an interesting character, but a good person to have at your back in a pinch.

Presently we found ourselves at the door of the Rowdy Rockfish, an entertaining looking tavern. Hungry and thirsty from the road, we decided to step inside. Pushing the door open, the Rowdy Rockfish didn't live up to its name. Entirely lacking in rockfishes, and not at all rowdy, the Rockfish seemed to be a relatively upmarket establishment. The bartender stood behind the bar polishing a wine glass as the locals sat around the tables enjoying their lunch and chattering to each other. There were only two notable things in the whole place; a shady-looking character lurking by the far corner of the bar, and a table near the door at which a huge orc and diminutive gnome were just sitting down.

I decided to engage with these two points of interest in reverse order, and sat down at the table whilst Cyn went to the bar to make the first and perhaps most important discovery about Otari - what wine was available. I introduced myself to the orc, named Grugnog Thunderpants (I think I heard that right), and the gnome who just goes by Clive. Apparently Grugnog owes something of a life debt to Clive after he saved him from an unseen attacker, though Grug seems not entirely pleased with this situation.

As soon as I got talking to these two, an elf walked in and apparently had the exact same thought process as myself. She sat down at our increasingly unusual looking table and introduced herself as Elderberry, a druid from the mountains come down to the coast for a change of scenery. She has a small mushroom friend who she has trained to backflip, which provided a nice icebreaker standing on the table. Conversation flowed easily, especially when Cynthia returned from the bar with Pinot Grigio and Pinot Noir for all. As it turned out we were all in the same boat, in town seeking excitement and adventure. We all agreed that the shady character by the bar looked worth talking to. I asked a nearby local if they knew who it was, and apparently its a local fish seller named Tamily Tanderveil who has been having some trouble. A very promising lead then. Clive volunteered to do the honours.

The lead turned out maybe not so promising as Clive reported back that apparently Tamily's cellar has been ransacked, and her entire stock of dried fish has gone missing. She wants some brave adventurous types to venture down there and work out what did it. It's not exactly the height of adventure, battling dragons and demons, but I guess honest pay for honest work. Tamily offered 5 gold pieces for the job, which seems almost too generous for a simple "check out the cellar" job. We agreed to help, after a large helping of potato tapas from the excellent bartender. I really recommend the food in the Rockfish, I'll have to come back sometime. The hassleback potatoes were a particular delight.

Otari Fishery Cellar

After lunch we made our way across town. The directions of "it's by the sea" seemed a bit questionable, but it turned out to be pretty easy to find the fishery. Upon entering we headed straight down into the problematic cellar. Lit by torches in sconces on the walls, it was easy to see the problem. The barrels were all ripped open by animal claws, and there was a great gaping hole in the far wall. Interesting that Tamily didn't mention that part. No wonder she offered 5 gold pieces.

We took a look around the cellar, but there wasn't really much else in the way of clues. Deciding to take the obvious one we headed towards the hole in the wall. Grugnog and I took the front, confident that between us we could take whatever made the hole. As we approached it, we started to hear skittering and scampering sounds in the darkness. Suddenly, a huge rat leapt out, followed by another, and another, and two more after that. These were no ordinary rats, they were the size of dogs and with teeth just as sharp. Grugnog began proceedings, dispatching the first rat easily with his greatsword. This was the high point of our first engagement together, as I followed up by dealing a terrible blow to the wall next to a rat, Grugnog greatly amused by my ineptitude. Elderberry wrapped magical vines around that same rat before giving it a quick jolt with an arc of magical electricity. This is where things started to go downhill.

The rat wriggled free of the vines, and went for me with a viciousness I wasn't expecting. Its teeth sank into my leg, somehow fitting painfully through the links in my chainmail. Another rat went for Grugnog, biting his legs over and over. I managed to pull my axe free of the wall in time to swing at it as it bit, but that only drew its attention to me. Infuriated by the wounding I gave it, the rat lunged at my currently rat-free leg, ripping and tearing as if my armour were paper.

As the blood poured down my legs and I began to wonder if this is really how it all ends, Clive showed some of the tenacity that led to Grug's debt, charging into the maelstrom of teeth and tails to club and punch the enraged rodent into submission. A magical ray of healing from Cynthia rejuvenated my spirits and strength as I steadied myself and readied my axe. Yet another monstrous rat scuttled past me heading straight for Grugnog, though it never found its target. My axe came crashing down on it, driven on by my pain as I cleaved the rat in two.

Grugnog and I combined to see off another of the swarm, before Cynthia showed our new friends just how unsettling she can be. Her eyes glowed as she destroyed the mind of the final rat, an eldritch lance fatally piercing its sanity. Elderberry made up some healing salves and administered them to the wounded as we sat on the cellar floor. As we recovered our strength and gazed into the now horrifying darkness of the tunnel, we were in unanimous agreement. Our payment terms needed renegotiating.
#2
I figured its about time I posted something reasonably detailed here, it's been a long time since I was last properly around!

This document is the "cliff notes" of my homebrew fantasy RPG setting that I've been running Pathfinder and D&D games in on and off for many years now. I have a few other worldbuilding notes and things from this world that I'll post later on in this thread.

This incarnation of the setting is aimed specifically at D&D 5e, and has mostly been used for running a bunch of vaguely connected one-shot games with players dropping in and out, so overall it's a relatively "standard" fantasy setting to reduce their barrier to entry.

The Pathfinder games (of which there's at least one AAR elsewhere on Exilian) took place hundreds to thousands of years prior to this point in the timeline.



The Imperium of Forao

Things mentioned in here are things that many/most people who have spent any time in the Imperium will be at least vaguely aware of; this is a version of the document that I give to players to help answer "does this character idea fit in to somewhere in the world already?" and similar questions.

// Note on Alignments:
The good/neutral/evil alignments aren't really super relevant in this world other than as a rough guide to roleplay. I don't really get on with the idea of people in this world being inherently evil by their nature, although obviously sometimes people do terrible things.

People

Species

Pretty much all playable species at least exist in the world somewhere, if not all native to the Imperium itself. Generally society is quite accepting of outsiders, the Imperium's expansionism having largely been settled down for most of its inhabitants living memory.

The most populous species in the Imperium is humans, however elves, halflings, dwarves, orcs, and gnomes are still numerous and have been in the region since time immemorial. Other species such as tieflings, dragonborn, genasi, and aasimar are less numerous but still a common sight in most larger towns. More obscure species (e.g. animalfolk like tortles and tabaxi) are a rarer sight and may occasionally turn heads in out-of-the-way areas, but it's generally considered rude to comment on a stranger's lineage.

Notably rarer are the likes of goblins, hobgoblins, and kobolds; according to myths they were driven out of the mountains in ancient days, and beset with such a fear that they rarely return. They're plenty common elsewhere in the world, but usually a notable sight in the Imperium. A period of reckoning with the problematic implications of those myths has already occurred in the early days of the Imperium, and these days those species are also welcomed despite their rarity.

// Mechanical note:
A notable tweak to the rules as written is around species lifespans. Overall lifespan range of creatures on the prime material plane is compressed around an average human lifespan, so no species are particularly long-lived.

SPECIESYOUNG ADULTADULTHOODELDERLY
Human18-3030-6565-90+
Elf18-5050-150150-175+
Half-elf18-4040-100100-120+
Dwarf18-5050-120120-150+
Halfling20-4040-8080-100+
Orc18-2525-6060-70+
Half-orc18-3030-6565-75+
Gnome20-4040-120120-150+
Tiefling18-3030-6565-85+
Dragonborn18-2525-5555-70+
Aasimar18-3030-9090-120+
Genasi18-3030-6565-80+

Names everyone knows

The most famous person alive in the Imperium is Marcus III, Emperor of Forao, King of Hirn, Voice of Vaeforus, and Keeper of the Imperial Peace. They ascended to the throne upon the death of their predecessor 9 years ago, becoming the 49th holder of the title.
Other nobles tend to be less well known outside of their own circles; but one or two have some name recognition, notably the Marcellae family who have been a wealthy and influential family in Imperium politics for several generations, being based in Forao and owning several vast estates in the country.

Places

(note: the climate notes in this section are subject to change, I'm in the process of a tectonic history simulation to determine the exact location of this continent on a globe, with the intention to do proper climate mapping afterwards, and I don't think this description will fully survive this process)

The Imperium covers around 720,000 square miles, an area roughly the size of Mexico (or three times the size of France). To the west and north is an ocean, partially frozen in the north. To the south is an almost impassable hot and arid desert, and the eastern border is marked by a combination of mountains, rivers, and in the south-east a smaller ocean. Beyond the borders are uncivilised and unknown lands (aka the neighbouring kingdom).

The Imperium is split into several provinces. The capital city Forao and its surrounding area are known as the Imperial Province. Forao is located south of the Stonehearth Mountains, and roughly 100 miles inland from the western ocean. Between Forao and the coast is the verdant province of Summervale, which along with the Bleakhorn Coast directly to the north is known as the "breadbasket of the empire".

To the north of the Stonehearths is the province of Old Hirn, a large grassland plain criss-crossed with rivers and fringed with forests. Further north are the cold and towering Aros Mountains, which form the practical northern border of the Imperium despite the official claims of ownership of lands further northwards.

South of Forao is the province of Mykaros, which along with Pelopos to the west stretches all the way south to the Great Expanse, the large southern desert.

To the east of Forao, the province of Essicao stretches to the great river Tirius, with the region of Sarmion stretching southwards and becoming more tropical as it reaches the Keshic Sea in the south east. North of Essicao is the region of Vardnor, which historically had many violent conflicts with the rulers of what is now Old Hirn, before the Imperium brought peace between them. Further east across the Tirius is the independent nation of Oskesh.

Some notable islands are provinces of their own. Off the coast of Old Hirn is Isle Dumont, and off the coast of Summervale is the Ilvao Archipelago.

Society

The Emperor holds absolute power over the Imperium. Their word is law.

Smaller levels of government are actually quite democratic, with most provincial governors holding elected titles, although elections are held only irregularly (usually triggered by death, disagreements with the Emperor, or egregious misconduct leading to civil unrest) and offer a selection of candidates put forward by the Emperor. A notable exception is the hereditary title of Lord Dumont, provincial governor of Isle Dumont.

In a similar vein, local "Ealdors" are elected for most towns above a certain size. These perform a role similar to that of a town mayor, being responsible for the town itself and the surrounding wilderness. Enfranchisement varies based on where you are; towns in Isle Dumont elect a small senate who in turn elect and assign Ealdors, whereas towns in the Bleakhorn Coast usually hold full ballots with any adult who has permanently lived in the town for over a year welcome to vote. These Ealdor elections work similarly to their provincial counterparts, occurring on an irregular basis.

Security is enforced mostly by local militia or guard forces employed by whoever is responsible for the area being guarded. There is no centralised police force or jail system.

For the most part, laws are adjudicated by local leaders as they see fit; and punishments are usually based around fines paid to the wounded party, or community work. The death penalty is an option, although meted out extremely rarely. Criminals have some right to appeal to higher authority for a second opinion, but there is no formalised court hierarchy.

It is also acceptable for the injured party to seek direct recompense without involving authority figures at all, and family elders will often come together to adjudicate and agree on things in this situation.

History

The known history of the Imperium is largely split into three eras, pre-Hirnic, Hirnic, and Foran. The present day is the year 586 of the Foran Era (FE).

Foran Era
This era began when the Imperium was founded after the conquest of the Kingdom of Hirn by the first emperor, Vaeforus.

The following 450 years were marked by a repeating pattern of peace followed by militaristic expansionism eastwards, depending on the whims of the emperor and their advisors. Most histories in the Imperium will frame this as a tale of glorious liberation of various peoples from tyrannical rulers, bringing them into the safe, vibrant, and modern society of Forao.

The last 100 years or so have been notable for their peace and stability. The Imperium has largely reached its natural borders, being surrounded by oceans, imposing mountain chains, and a desert in the south-east which is almost uninhabitable to most species in the Imperium.

Hirnic Era

The Kingdom of Hirn was the predecessor state to the Imperium, and the emperor still carries the title of King of Hirn even after almost 600 years. The former kingdom makes up a large part of the northern and western provinces of the Imperium, and it was upon conquering that kingdom that Vaeforus felt worthy to call themself Emperor.

This era of history spans from when various ancient peoples were first united under the Hirnic banner through to the fall of the kingdom, in 1043 of the Hirnic Era (HE).

Pre-Hirnic Era

This is pretty much "the rest of time", broadly analogous to prehistory in the real world. Spanning roughly 10,000 years, it's a catch-all term for a period that isn't very well understood by people in-universe outside of mythology passed down through time and antiquarian investigations into ruins.

Religion

The Imperium has a single, extensive pantheon which encompasses pretty much all gods that have ever existed in people's minds in the region. Religious tolerance is the norm in the Imperium, with disputes usually occurring along the same lines as disputes between deities themselves, and being rooted in those problems rather than stemming from general xenophobia.

The pantheon of the Imperium has developed mostly organically over the course of recorded (and unrecorded) history, and as such is split into three rough groups along the same lines as that history. Deities belong in the era that they are most strongly associated with.

The deities of the Foran Reformation either solidified as concepts or entirely came into being during the upheavals of the early FE or late HE, the Hirnic Pantheon deities similarly were settled into the characters worshipped in the present day during the Hirnic Era, and the Ancient Spirits are deities whose nature and names are passed down through many layers of tradition from pre-Hirnic days.

There are a number of "major deities", who are beings of immense power and influence over occurrences on the material plane.

DEITYPRONOUNSDOMAINSFOCUSSYMBOLS
Foran Reformation
Vaeforusthey/themKnowledge, OrderLeadership, Imperial powerThe Imperial crown
Victarushe/himOrder, WarSoldiers, victoryLaurel branch resting on a shield
Volcashe/herForgeBlacksmithingA hammer and anvil
Hirnic Pantheon
Aldirihe/himNature, TempestFishing, oceansA boat with a trailing net, a fish
Basarinnhe/himArcana, DeathMisery, pain, sufferingA closed fist with knuckles outward
Brelfhe/himTrickeryDebauchery, hedonism, partyingA lavish goblet of wine
Fanallashe/herPeaceCommon people, festivalsA tankard of ale
Fellmarhe/himDeath, GraveDeath and the deadRavens, skulls
Ikashe/herNature, TempestAir, sky, weatherA collection of clouds, a snowflake, a sail
Meralashe/herNatureRivers, travelA waterfall crashing onto rocks, a sailboat
Morwelathey/them/anyLife, PeaceInterpersonal relationships, love, familyA tree with wide branches providing shelter
Pandirthey/them/anyOrder, WarCombat, martial artsA crossed sword and axe
Stenvirhe/himForgeJewellery, fine metalworkAn intricately carved ring, often stylised
Wisfaershe/herKnowledgeAcademic pursuits, foresightA scroll in a ring of runes, an owl
Ancient Spirits
Certhiashe/theyArcana, NatureNatural magic and druidcraftA sycamore leaf with cutout runes
Erithahe/theyNature, TrickeryForests, fey creaturesA crown of ivy
Hirnalfar, god of two facesthey/themLight, TwilightTime (each face has a more specific focus)A disk with one half golden with sun rays, the other pale and flat as the moon
  Hirnal, sun facehe/himLightDaytime, sunlight, and the futureA stylised sun
  Hirnfar, moon faceshe/herTwilightNighttime, moonlight, the past, and darknessA crescent moon
Ornhe/himKnowledge, PeaceLeadership, nobilityA finely carved staff, a bearded face
Taekdehe/himLife, Nature, TwilightPlants, animals, the living worldA pair of antlers tangled with leaves
Ysmereldashe/herArcana, TempestWind, sometimes fortune/fateStarlings

In addition to the major deities listed here, there is an almost innumerable set of lesser deities or spirits which people somewhere worship or entreat. These range from simple embodiments of certain places in the world, like the local god of a forest glade, up to figures with similar renown to the major deities but with less actual power over their specific domain. Many of these figures overlap with the major deities in terms of their domain, but generally are seen as subservient or at least having only shared responsibility for their domain.

For example, the aforementioned god of the glade would be venerated by the local gnomes, but in conjunction with veneration of Taekde or Eritha as the major deity in the relevant domain. Some cults might focus entirely on a minor deity, attempting to further their goals and expand their power for whatever reason.

// Mechanical note:
The Warlock class in the basic rules states that "the beings that serve as patrons for warlocks are not gods".

The opposite is true in this world, with many warlock patrons (specifically for the Celestial, Fiend, and potentially Archfey or Great Old One pacts) having a spot somewhere in this pantheon as lesser deities/spirits. Angels, demons, devils, and the like are not distinguished from regular deities in this world.

It's also not unheard of for Warlock patrons in this world to be major deities, but that's pretty rare and unusual compared to a Cleric or Paladin style relationship.
Mundane warlock patrons (i.e. other pacts) continue to not be gods, as makes obvious sense, unless there's a narrative reason for them to be one.

#3
General Gaming - The Arcade / Vintage Story
June 16, 2025, 12:22:29 AM
Has anyone else here played Vintage Story?

It's a survival game that has swallowed up all of my free time for the last week or so, and I thought it would be of interest to the people here that played Haven and Hearth back in the day.

Visually it is similar to Minecraft, but in terms of mechanics and actual gameplay it's quite different. I've spent several in-game months getting to this point, finally working with copper to be able to make decently sized chests.

Freshly poured copper
Spoiler


The forging is one of a few in-world crafting systems that I think are the real core of why this game feels so good compared with similar survival games; you actually hammer your hot ingot into shape voxel by voxel. Stone tools are crafted by knapping flint into shape voxel by voxel, and pottery is done by actually shaping the pots in a similar way then firing them in a kiln.

Forging some nails for chests
Spoiler

It's been really scratching the Haven and Hearth itch for me, only without the MMO aspect (which has its positives and negatives).

Since I took those screenshots a couple of days ago, summer began to turn to autumn and I've been working non-stop to try to prepare myself for winter; watering my farm daily to try to get a final harvest in before everything freezes, foraging plants that will stop growing in the winter, hunting boars for meat and hide (to make fur clothes to not freeze to death), and preserving food in my newly-dug cellar. My latest project is working out how to brew wine, since I have no other good way to preserve fruit over the winter months yet.

I'll update this thread with more pictures from my homestead as the year goes on if people are interested.
#4
Stories and AARs / SotK's Short Stories
January 06, 2018, 11:04:45 AM
I'll start this thread with the complete version of the story I posted the first 7 sentences of in the 7 lines challenge thread. I'll probably add more stories in the future, as and when I convert them from ideas into something more coherent.




Viknar swung his pick. The sound echoed through the empty mine and almost seemed to add to the chill in the air. The others had begun refusing to come this deep once the unnatural cold had set in, but Viknar's dwarf blood could feel the valuable ore close by.

Viknar swung his pick. Stonetown needed him to find the ore. The economy was already suffering greatly after the old mine dried up, and Newdelve turning out to be a false hope would be more than the remaining miners could bear. They would move on, sapping yet more life out of the dwindling village.

Viknar swung his pick. Aldoric had promised to handsomely reward the miners once this excavation was a success. Land, power, riches, all potentially just behind the next piece of rock. The others' voices drifted down the tunnel; they had begun drinking already.

Viknar swung his pick. He liked ale just as much as the others, but the work was too important. He was afraid, just like the others, but he couldn't give in now. Why wouldn't they help? Why wouldn't they see?

Viknar swung his pick. One of the support beams creacked, and some dust fell into his beard. Viknar swung his pick. An icy breeze blew from somewhere. Viknar swung his pick. The sound changed; there was a hollowness to it.

Viknar swung his pick. The rock peeled away easily, exposing what looked like a stone wall. The tunnel grew colder than ever, and the breeze returned. Viknar paused, and steeled himself.

Viknar swung his pick. More rock fell away, there was definitely a wall. The stones were icy cold. Viknar thought about telling the others, but he could still faintly hear the sound of them drinking without him. Again.

Viknar swung his pick. The pick smashed straight through the cold stone, and pulling it out brought a good portion of the wall down. The breeze whistled through the hole, and a shiver went down Viknar's spine.

Viknar took his lamp and crept inside. He was in a long room, with a door at the far end. A dim glow was seeping through the gap around the door. In the centre of the room was a pedestal, almost as tall as Viknar. Each side wall had two alcoves, each housing a statue and what appeared to be a sarcophagus.

Viknar stepped forwards. He thought for a moment that he heard one of the others shout his name. The room became utterly silent and his breath hung in the air as mist.

The door exploded off its hinges. Viknar was thrown back out of the room, and into the wall of the mine. A blue flash, and everything was frozen. A shadow burst out of the room, and Viknar saw nothing else.

A support beam creaked and cracked, blocking the tunnel with splintered wood and rubble. The others sprinted towards the mine's opening in a drunken panic. An ice cold blast forced them onward, but only as soon as they could feel the sun, a shadow from behind caught them. Their limbs froze and the world went dark as the shadow drained the life from their bodies. More rubble fell, and the shadow passed on, out into the world beyond the mine.
#5
yamlui - Declarative UI in pygame
Code is here on GitHub

Introduction

So, I've begun the process of making a thing that I had an idea for just over a year ago. One of the most convoluted parts of the unreleased python version of Township I did was the UI code. I had tried to generalise it, and mostly succeeded, but it was still a horror show. The 3d Township thing also ran out of steam because of no easy way to combine my existing code with some kind of easy UI layout tool.

So I decided to think about how I wanted to be able to define a UI. Most importantly, I wanted to not have to write much code to do it. Something that was great when I was first learning to program using Visual Basic was the ease of creating a UI by dragging elements onto a "form" and writing code that is run when events happen on those UI elements. It made it wonderfully easy to throw together a UI for something. This is much easier than the approach used by tkinter and pyqt (though Qt has a declarative thing called QML which is nice), which involves lots of wrangling UI elements in the code itself (so not really much of an improvement on the unreleased python Township I mentioned).

Another benefit of being able to describe a user interface in a configuration file is that it is then really easy for modders to change the UI as they see fit. One can customise as much as they like without having to actually rewrite any of the code.

The result of my thinking was a plan for yamlui. This would theoretically let me write a yaml file which defines everything about the user interface of a pygame application, from the size, colour, and positioning of things, to how they change (and what behaviour they should perform) when events happen, such as them being clicked on, or them being typed at.

Example

So the idea is that. So far, I've implemented only a few UI widgets, so its not even as functional as my old Township UI. However, it is all definable easily in a configuration file. Here is an example:

object: window
properties:
  text: Township Test UI
  image: examples/images/background.png
  width: 1280
  height: 800
children:
- object: container
  properties:
    opacity: 50%
    colour: [0, 0, 0]
    position: [10, 100]
    width: 625
    height: 600
  children:
  - object: label
    properties:
      text: World Settings
      width: 605
      font: arial
      font-size: 18
      position: [10, 10]
      display: relative
  - object: label
    properties:
      text: These options allow you to customise the settings used by the map generator when generating your world.
      width: 605
      font: arial
      font-size: 14
      font-colour: [175, 175, 175]
      position: [10, 50]
      display: relative
- object: container
  properties:
    opacity: 50%
    colour: [0, 0, 0]
    position: [645, 100]
    width: 625
    height: 600
  children:
  - object: label
    properties:
      text: Township Settings
      width: 605
      font: arial
      font-size: 18
      position: [10, 10]
      display: relative
  - object: label
    properties:
      text: These options allow you to customise the starting situation of your township's populace.
      width: 605
      font: arial
      font-size: 14
      font-colour: [175, 175, 175]
      position: [10, 50]
      display: relative


That file defines this UI:

Spoiler

The best part of that is that the only code I need to write outside of the yamlui library is this:

import pygame

import yamlui


pygame.init()

window = yamlui.generate_ui('examples/testui.yaml')

while True:
    for event in pygame.event.get():
        window.handle_event(event)
    window.update()
    window.draw()


Playing around

If this is of interest to anyone, it should be pretty easy for you to play around with. Just grab the latest version of yamlui from its GitHub page (there is a big green download button on the right), put the example code from this post in a script in the root directory (alongside the README and what-have-you) and run it with python.

It works with both python 2.7 and python 3.4 (probably other 3.x versions too, but I've only tested 3.4). If you're on Windows I'd recommend using python 3.4, since that makes it nice and easy to install pygame these days.

Assuming you installed python 3.4 on Windows, open a command prompt and run
C:\Python34\python.exe -m pip install -U pip
C:\Python34\python.exe -m pip install pygame pyyaml


to install the dependencies. You'll need to do this before you can successfully run the examples provided, obviously.

If you're on Linux (or probably OSX too) you can disregard the C:\Python34\python.exe -m bit of those commands.

I'll post/be cheerful in this thread when I implement cool new parts of it, like textboxes, and functional buttons.
#6
The Welcome Hall - Start Here! / Welcome!
January 27, 2015, 10:16:40 PM
Welcome to Exilian! We're one of the friendliest communities on the internet, democratically run and existing to help facilitate a range of projects in fields including game modding/content creation, programming, drama, music, debating, writing, RPG and wargame design, e-learning, and more. You can also come here to chat, discuss the news, post poetry, play forum games, and just to meet a diverse range of people from all over the world.

Enjoy your stay!
#7
SotK's Oneshots One: Particles!

Welcome to SotK's Oneshots, a series of Python programming tutorials which will be spread across this area and the general Software Tutorials section depending on their content. They will all be "oneshots", that is each will be unrelated and a standalone guide. They will probably vary in length, and I intend for them to pass on some knowledge of programming techniques beyond their specific focus.

This first installment is called Particles!, and is unsurprisingly about particles. Specifically, particle systems in games. Particle systems have a number of uses, from making weather, through nice looking smoke to realistic hair. At the end of this tutorial, you should have a program that is a basic 2D simulation of snowfall. I'm posting this before the content is actually finished, since I like to live on the edge, and also so you can follow along with me, or take your own direction and see how our results differ.

You Will Need

  • Python (version shouldn't matter, I don't think I'm doing anything specific to 2.7 or 3.*. I am using python-2.7 if you want to be safe.)
  • Pygame - version should be the latest for your installation of Python
Instructions on how to get pygame are here. For Python, look here.

Step I - Create a window

The first thing we need is a window to display things in! This is covered in detail in my 6th Python tutorial, so here I'll just give you the code that results from that. If you want explanation of it, look in the aforementioned thread.

Code (Step I) Select
import pygame

def run():
    # initialise pygame
    pygame.init()
    screen = pygame.display.set_mode((600, 600))
    pygame.display.set_caption('Hello, world!')
   
    # game loop
    running = True
    while running:
        # event loop
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                # Terminate on window closure
                running = False
            elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
                # Terminate on Esc keypress
                running = False
   
        # drawing code
        screen.fill((0, 0, 0))
   
        pygame.display.flip()

if __name__ == '__main__':
    run()


Step II - Generate some particles

The next step is to display something on that screen! My 7th Python tutorial talks about displaying a box, but we don't really want to use shapes for this. The window (represented by the screen variable) is what pygame calls a "Surface". A surface is something which can be drawn or drawn on, and the screen surface is the window's main surface, anything drawn in it is displayed in the window (assuming its drawn within the window).

We want to draw particles on this Surface as small as we can. Luckily, pygame Surfaces have a method (more on the meaning of this later) called set_at, which allows us to set the colour of the pixel at a given coordinate.

screen.set_at(coordinate, colour)

In this call, colour is of the same form as in screen.fill. We want white pixels, since we're making "snow", so colour should be (255, 255, 255). The coordinate is the position of our particle. This is a pair of integers (can't have fractional numbers of pixels), which we can either decide on or randomly choose. Randomly choosing seems fun, and snow doesn't all start in one place!

Python has a module called random for just this purpose. random has a function called randint which generates a psuedo-random integer between two integers. Our screen has coordinates from 0 to 600 on both axes, and we don't want to spawn them right on the edge, so lets pick random numbers between 1 and 599. We'll also want to store all our particles so they don't disappear after one frame. We'll store them in a set. This is basically a representation of a mathematical set. It can only contain one instance of an item (i.e. if we try to put two of the same coordinates in, only one will end up stored). This is a minor optimisation at this point, to stop us drawing the same point multiple times per frame.

particles = set()

next_p = (random.randint(1, 599), random.randint(1, 599))
particles.add(next_p)


We also need to actually draw our particles. We do this by looping through the elements of the set and setting the colour at the correct pixel for each one.

for particle in particles:
    screen.set_at(particle, (255, 255, 255))


Lets put this together. We'll initialise the set before the game loop, add one particle each time, and draw all of them each frame.

Code (Step II) Select
import random
import pygame

def run():
    # initialise pygame
    pygame.init()
    screen = pygame.display.set_mode((600, 600))
    pygame.display.set_caption('Hello, world!')
   
    # game loop
    particles = set()
    running = True
    while running:
        # event loop
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                # Terminate on window closure
                running = False
            elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
                # Terminate on Esc keypress
                running = False

        next_p = (random.randint(1, 599), random.randint(1, 599))
        particles.add(next_p)
        # drawing code
        screen.fill((0, 0, 0))
        for particle in particles:
            screen.set_at(particle, (255, 255, 255))
   
        pygame.display.flip()

if __name__ == '__main__':
    run()


Step III - Monte Carlo

That creates us a nice screen full of particles. However they are a square shape. This might not be bad, but circles are nicer. How do you get your random numbers to produce particles in a circle?

We will use a technique called Monte Carlo randomisation to generate our coordinates inside a circle. This is based on the fact that the equation of a circle is:

(x - a)2 + (y - b)2 = r2

For the "unit circle" - that is, a circle at the origin with radius 1 - this simplifies to:

x2 + y2 = 1

Therefore, if we want a point inside this circle, we just need to pick x and y such that:

x2 + y2 <= 1

That sounds like a while loop to me!

def monte_carlo():
    randx = 2
    randy = 2
    while (randx * randx) + (randy * randy) > 1:
        random.seed()
        randx = random.uniform(1, -1)
        randy = random.uniform(1, -1)
    return [randx, randy]


random.uniform(a, b) creates a random number between a and b. random.seed() resets the random seed used to generate the numbers, calling it each time marginally improves the randomness. This is a brute force way to get random numbers for the x and y values within the unit circle, but its not slow enough to be a problem this point.

Now lets modify our particle creation code to use this function.

point = monte_carlo()
next_p = (int((200*point[0]) + 300), int((200*point[1]) + 300))
particles.add(next_p)


The second line in that snippet looks ugly, so lets break it down. It takes the point in the unit circle returned by monte_carlo(), then scales it up by a factor of 200 (this gives values between -200 and 200) and translates it by (300, 300) (this moves them such that the values are relative to the centre of the window). Finally, the x and y values are being cast to integers. They are floats (decimals) when returned by monte_carlo(), and we need them to be integers to use them with set_at.

Lets see the finished code at this point:

Code (Step III) Select
import math
import random
import pygame

def monte_carlo():
    randx = 2
    randy = 2
    while (randx * randx) + (randy * randy) > 1:
        random.seed()
        randx = random.uniform(1, -1)
        randy = random.uniform(1, -1)
    return [randx, randy]

def run():
    # initialise pygame
    pygame.init()
    screen = pygame.display.set_mode((600, 600))
    pygame.display.set_caption('Hello, world!')
   
    # game loop
    particles = set()
    running = True
    while running:
        # event loop
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                # Terminate on window closure
                running = False
            elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
                # Terminate on Esc keypress
                running = False

        point = monte_carlo()
        next_p = (int((200*point[0]) + 300), int((200*point[1]) + 300))
        particles.add(next_p)
        # drawing code
        screen.fill((0, 0, 0))
        for particle in particles:
            screen.set_at(particle, (255, 255, 255))
   
        pygame.display.flip()

if __name__ == '__main__':
    run()


Step IV - Tidying up

That horrible line did three different things and was hard to understand, so lets split it up. When programming, its a good idea to keep things as simple as possible so that someone with no knowledge of your code can understand it easily. This is achieved in part by naming your variables and functions sensibly, partly by useful comments (the code in this tutorial does not contain useful comments at this point), and partly by keeping your functions small and atomic (one function serves one purpose). Lets write three functions to do the different parts of that line:

def scale(point, sf):
    return (point[0] * sf, point[1] * sf)

def translate(point, dx, dy):
    return (point[0] + dx, point[1] + dy)

def make_int(point):
    return (int(point[0]), int(point[1]))


These are pretty self explanatory, what they do is obvious from looking, and was explained above :) . Now lets use them to replace the ugly line:

next_p = make_int(translate(scale(point, 50), 300, 300))

We could simplify this further by replacing point with monte_carlo(), but we'll be moving this code soon so this will do for now. The code at this stage should look something like this:

Code (Step IV) Select
import math
import random
import pygame

def monte_carlo():
    randx = 2
    randy = 2
    while (randx * randx) + (randy * randy) > 1:
        random.seed()
        randx = random.uniform(1, -1)
        randy = random.uniform(1, -1)
    return [randx, randy]

def scale(point, sf):
    return (point[0] * sf, point[1] * sf)

def translate(point, dx, dy):
    return (point[0] + dx, point[1] + dy)

def make_int(point):
    return (int(point[0]), int(point[1]))

def run():
    # initialise pygame
    pygame.init()
    screen = pygame.display.set_mode((600, 600))
    pygame.display.set_caption('Hello, world!')
   
    # game loop
    particles = set()
    running = True
    while running:
        # event loop
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                # Terminate on window closure
                running = False
            elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
                # Terminate on Esc keypress
                running = False

        point = monte_carlo()
        next_p = make_int(translate(scale(point, 50), 300, 300))
        particles.add(next_p)
        # drawing code
        screen.fill((0, 0, 0))
        for particle in particles:
            screen.set_at(particle, (255, 255, 255))
   
        pygame.display.flip()

if __name__ == '__main__':
    run()


Step V - Getting older

OK, we're generating particles forever, and none of them are disappearing. This is Not GoodTM. We will eventually run out of memory and crash (actually the spawning area would end up entirely white and we'd stop adding particles since we use a set not a list, but if your spawning area is big enough you will cause a crash). Lets make our particles age. You will want aging particles so you can keep a lid on them nicely. If you randomly generate a lifespan for them, some disappearing and others appearing should seem nice and constant.

We're going to need to be able to store more than one piece of information about each particle now (the age and the position). Lets store them as dictionaries rather than just a coordinate. We could go all object oriented on this, but we'll save that for later :) .

def add_particle(particles):
    particle = {
        'name': 'snow',
        'age': 0,
        'position': make_int(translate(scale(monte_carlo(), 50), 300, 300)),
        'colour': (255, 255, 255)
    }
    particles.append(particle)


Here we add a function which creates a particle. Note we are using particles.append now, rather than particles.add. This is because we need to change particles from a set to a list. This is a downside of using a dictionary to represent a particle - dictionaries are not "hashable types" in Python, and items in a set must be "hashable" (hashable means that the object can be converted to a unique string which can then be converted back into the object).

The add_particle function is again pretty self-explanatory. We set the variable particle to be a dictionary containing 4 keys. These keys are 'name', 'age', 'position', and 'colour'. Each of these keys has a "value" - in the case of 'name', the value is 'snow'. Notice the value of 'position' - that is the ugly next_p line which we neatened. I told you we'd move that code soon!

Now we should utilise this code in our main loop. First, our particle adding code is replaced by a single function call:

add_particle(particles)

Next, lets increment the age each frame and remove any particles that get too old from the list:

particle['age'] += 1
if particle['age'] > 1000:
    particles.remove(particle)


We'll put that in the for loop we use for set_at. particle['age'] gets the value stored in the dictionary stored in particle with the key 'age'.

I said that you should make the lifespan random, but in this case it looks fine with a fixed lifespan. As each particle is created one frame after the last, there is a steady flow of particles disappearing and appearing. The code now looks like this:

Code (Step V) Select
import math
import random
import pygame

def monte_carlo():
    randx = 2
    randy = 2
    while (randx * randx) + (randy * randy) > 1:
        random.seed()
        randx = random.uniform(1, -1)
        randy = random.uniform(1, -1)
    return [randx, randy]

def scale(point, sf):
    return (point[0] * sf, point[1] * sf)

def translate(point, dx, dy):
    return (point[0] + dx, point[1] + dy)

def make_int(point):
    return (int(point[0]), int(point[1]))

def add_particle(particles):
    particle = {
        'name': 'snow',
        'age': 0,
        'position': make_int(translate(scale(monte_carlo(), 50), 300, 300)),
        'colour': (255, 255, 255)
    }
    particles.append(particle)

def run():
    # initialise pygame
    pygame.init()
    screen = pygame.display.set_mode((600, 600))
    pygame.display.set_caption('Hello, world!')
   
    # game loop
    particles = []
    running = True
    while running:
        # event loop
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                # Terminate on window closure
                running = False
            elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
                # Terminate on Esc keypress
                running = False

        add_particle(particles)

        # drawing code
        screen.fill((0, 0, 0))
        for particle in particles:
            particle['age'] += 1
            if particle['age'] > 1000:
                particles.remove(particle)
            screen.set_at(particle['position'], particle['colour'])
   
        pygame.display.flip()

if __name__ == '__main__':
    run()


Step VI - Getting moving

We now have an easy way to add information to our particles, we can simply add to the dictionary created when we generate them! Lets get our particles moving around the screen a bit, they're boring stood still.

def add_particle(particles):
    particle = {
        'name': 'snow',
        'age': 0,
        'lifespan': random.randint(1, 1000),
        'position': round_to_int(translate(scale(monte_carlo(), 100), 300, 300)),
        'velocity': monte_carlo(),
        'colour': (255, 255, 255)
    }
    particles.append(particle)


We have added a 'velocity' key to the dictionary (also a 'lifespan' one, I decided to make it random anyway). This uses our monte_carlo() function from earlier to get two values inside the unit circle. Note that make_int has changed to round_to_int. I learnt something writing this, casting to int in Python truncates the number rather than rounding (so 3.6 gets turned into 3 not 4). Here is the round_to_int function:

def round_to_int(point):
    return [int(round(point[0], 0)), int(round(point[1], 0))]


round(number, decimal_places) is being used here. This does proper rounding, not truncation.

Now we want to make our particles understand the velocity they have.

particle['position'][0] += particle['velocity'][0]
particle['position'][1] += particle['velocity'][1]


We'll put this in the place where we deal with removing our particles, in the for loop. However, it won't work yet since the position + the velocity will be a float not an int, and therefore can't be used when we set pixels. We'll need to turn it into a pair of integers. Lucky we have a function for that eh?

particle['display_position'] = round_to_int(particle['position'])
screen.set_at(particle['display_position'], particle['colour'])


We're growing a lot of code in this for loop now. Remembering what I said about simplifying code, lets make an update_particle function.

def update_particle(particle, particles):
    particle['age'] += 1
    if particle['age'] > particle['lifespan']:
        particles.remove(particle)
    particle['position'][0] += particle['velocity'][0]
    particle['position'][1] += particle['velocity'][1]
    particle['display_position'] = round_to_int(particle['position'])


This is just the code that was in that for loop. We can now replace that with a single function call:

update_particle(particle, particles)

Easy! The code should now look like this:

Code (Step VI) Select
import math
import random
import pygame

def monte_carlo():
    randx = 2
    randy = 2
    while (randx * randx) + (randy * randy) > 1:
        random.seed()
        randx = random.uniform(1, -1)
        randy = random.uniform(1, -1)
    return [randx, randy]

def scale(point, sf):
    return (point[0] * sf, point[1] * sf)

def translate(point, dx, dy):
    return (point[0] + dx, point[1] + dy)

def round_to_int(point):
    return [int(round(point[0], 0)), int(round(point[1], 0))]

def add_particle(particles):
    particle = {
        'name': 'snow',
        'age': 0,
        'lifespan': random.randint(1, 1000),
        'position': round_to_int(translate(scale(monte_carlo(), 100), 300, 300)),
        'velocity': monte_carlo(),
        'colour': (255, 255, 255)
    }
    particles.append(particle)

def update_particle(particle, particles):
    particle['age'] += 1
    if particle['age'] > particle['lifespan']:
        particles.remove(particle)
    particle['position'][0] += particle['velocity'][0]
    particle['position'][1] += particle['velocity'][1]
    particle['display_position'] = round_to_int(particle['position'])

def run():
    # initialise pygame
    pygame.init()
    screen = pygame.display.set_mode((600, 600))
    pygame.display.set_caption('Hello, world!')
   
    # game loop
    particles = []
    running = True
    while running:
        # event loop
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                # Terminate on window closure
                running = False
            elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
                # Terminate on Esc keypress
                running = False

        add_particle(particles)

        # drawing code
        screen.fill((0, 0, 0))
        for particle in particles:
            update_particle(particle, particles)
            screen.set_at(particle['display_position'], particle['colour'])
   
        pygame.display.flip()

if __name__ == '__main__':
    run()


Coming soon

  • Acceleration due to gravity
  • Making things configurable
  • Snow falls from more than one place really
  • The floor is dry, why not stick?
#8
Welcome to the 5th Council of Venarta in the year 348
It is now day. Night will begin at 1800 GMT on 9th January.

Only a few months ago, this council was much larger, around 30 people would have been sat here. However, they have all died unexplained deaths, disappeared, or otherwise been removed from your number. Coincidence and outside motives have been considered and discounted, it must now be assumed that these removals are being orchestrated by someone (or some group) within the council itself. The council is currently leaderless, but due to the unfortunate circumstances currently, the election has been postponed until the plotters are found.

Killing during a council session is unheard of and would probably cause an outcry among the Lesser Representatives
[1], so you should probably try to stick to imprisoning each other [in reality this will just be flavour for the "lynches"]. While they have no real power, you must be quick to show that you still do. If someone shows that the council can be outmanoeuvred and eliminated, they all might try to take power! Surely now is the last chance to stop whoever is carrying out these acts; that you are all gathered here to solve it will make it easy for them to kill you if they wish.

[1]: The Lesser Representatives are a much larger council with a representative from each town in the Venartan Republic sent to discuss policies. They are supposed to be able to influence the discussions and decisions of the Council, but in reality they talk and the Council largely just ignores them.

Day 1
The council entered the chambers for the first day's discussion one by one. The tension in the air was palpable, each knew this could be their last day as a council member, perhaps even their last day alive. When everyone was present the debates of what to do about this crisis, and who to do it to could begin.

Procedures/Rules
Shamelessly ripped from assorted people

If you break one of these rules, you will be either warned, replaced, or modkilled, depending on which rule it is and the severity.

The game will follow the usual day-night sequence. We will start with a day so that no-one is dead before the game starts.

  • Dead players do not speak. Giving away any information after death will not be tolerated.
  • Be bold with voting. A vote that isn't in bold won't be counted.
  • Lynches require a majority of votes. Once a player has reached the majority, his/her pleas are useless and any attempts to unvote will be ignored. You may also vote for no lynch.
  • NO EDITING YOUR POSTS.
  • Do not post often during the night. A little small talk is allowed to keep the thread bumped, but keep night posting to a minimum. Do not reveal any important game information.
  • Once your death scene has been posted, stop posting. You can make a farewell post or two, but no discussion of the game.
  • No communication with other players outside this thread unless I say it is permitted in your role email. This excludes mafia.

Current Players










PlayerStateRole
JubalAlive-
comrade_generalAlive-
The KhanAlive-
Dripping DAlive-
ColossusAlive-
OthkoAlive-
Mother of DragonsAlive-
#9
OK, I want to run a mafia. I think we need a mafia here.

It will be a basic 5/2 setup unless we get more people than that. State your interest here!

I will write some setting here later if people would like that.

Currently in:

  • Jubal
  • CG
  • The Khan
  • DD
  • Colossus
  • Othko
  • Mother of Dragons
#10
C++ / Welcome: Say Hello
May 15, 2014, 08:34:13 PM
Hey all of you!

I am SotK, or Adam. I'm a programmer who works for a company called Codethink in Manchester. I have a degree in Computer Science with Mathematics from Leeds University, and in my free time I play video games, program random things and a game called Township, and spend time with my wonderful fiancée Mother of Dragons. For my final year project at uni I generalised the scan conversion algorithm for rasterizing shapes to n dimensions, something that had never been done before!

So, who are you? What have you done with your lives so far? What do you know about programming?
#11
C++ / Welcome: Course Info
May 15, 2014, 08:21:43 PM
Introduction to Programming in C++

This course will teach you the basics of programming in C++. It is designed to give a good foundation in the basics of both programming in general and more specifically in C++. In addition to the basic stuff, the course touches upon stuff towards the end that shows the range of things that can be done once the groundwork is laid.

Sections:

  • Setting Stuff Up:
    • Getting and installing a compiler
    • Installing Qt Creator
  • Programming 101:
    • Making things happen
    • Doing some maths
    • Logic!
  • Arrays and Vectors:
    This section is a little more specific to C++, the things learnt in section 2 apply very easily to any language.
    • Arrays
    • Vectors
    • Pointers and other delights of C++
  • File Handling:
    • Reading from files
    • Writing to files
  • Quick Introduction to Object Oriented Programming:
    • Classes
    • Inheritance
    • Getting abstract
  • User Interfaces:
    • Introduction to Qt
    • Making things happen again
    • Interaction
    • Linking it with the rest
  • Basic Graphics:
    Only 2D graphics in this course.
    • Drawing blobs
    • Drawing lines
    • Between the lines
    • Curves

Details:

Requirements:

  • A computer that can run Qt Creator
  • Basic mathematical knowledge (geometry in particular) would be beneficial
  • Some programming experience would also be beneficial, if only because it will make the beginning parts of the course easier
Max credits: 7

Students:

  • Othko

We'll be starting soon!
#12
SotK's Python Tutorials
Part 7: More Pygame




Introduction:

Last time, we made a window open on the screen. Its not a lot, but its the basis of anything with pygame (obviously). By the end of this tutorial we will have made a box on the screen, and been able to move it around with WASD! This is clearly a basis upon which a simple game can be built.

Township started out similar to this, I had an assignment to create something with dynamic graphics using pygame, and was devoid of inspiration so made a bunch of spheres chase each other around the screen and got an idea from there.

Representing a box:

At the end of the last tutorial we had the following, which opened a window and coloured it black.


# import pygame modules
import pygame

# initialise pygame
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption('Hello, world!')

# game loop
running = True
while running:
    # event loop
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            # Terminate on window closure
            running = False
        elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
            # Terminate on Esc keypress
            self.running = False

    # drawing code
    screen.fill((0, 0, 0))

    pygame.display.flip()


We want to have a box at a position on this window. Lets say [295, 295] as that will but the box in the centre of the window. This position is the top-left corner of the box. It will be a good idea to store this position in a variable, so that we can modify the position easily. Since in the future we will want to make this box move, it will be a good idea to have a variable to represent its speed too. We are working in 2D, so we'll make the speed a two-element list of the form (horizontal speed, vertical speed). For now this can be [0, 0]. Our box could also use a height and width, so we'll make those too, a square of edge length 10 pixels. We'll also want a colour for the box to be, in this case we'll choose white (or (255, 255, 255)).

Note we use lists for the position and the speed so that we can change them later on.

Finally, we will need to make a "Rect" object, which is pygame's representation of a rectangular shape.


# import pygame modules
import pygame

# initialise pygame
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption('Hello, world!')

# set up box stuff
box_pos = [295, 295]
box_speed = [0, 0]
box_height = 10
box_width = 10
box_colour = (255, 255, 255)
box = pygame.Rect(box_pos, (box_width, box_height))

# game loop
running = True
while running:
    # event loop
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            # Terminate on window closure
            running = False
        elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
            # Terminate on Esc keypress
            self.running = False

    # drawing code
    screen.fill((0, 0, 0))

    pygame.display.flip()


That is all the data we need to draw the box, and potentially move it in the future, so on to drawing!

Drawing the box:

Clearly, the drawing code for our example is at the end of the loop. We want to draw the box after filling the screen in black (otherwise our box will be drawn "underneath" the black of the screen). Imagine you are painting a picture, so the things nearest the front need to be drawn last.

Drawing a rectangle in pygame is simple. We use the function pygame.draw.rect(), which takes a surface (the screen), the colour of the rectangle and the Rect object to draw.


# import pygame modules
import pygame

# initialise pygame
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption('Hello, world!')

# set up box stuff
box_pos = [295, 295]
box_speed = [0, 0]
box_height = 10
box_width = 10
box_colour = (255, 255, 255)
box = pygame.Rect(box_pos, (box_width, box_height))

# game loop
running = True
while running:
    # event loop
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            # Terminate on window closure
            running = False
        elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
            # Terminate on Esc keypress
            self.running = False

    # drawing code
    screen.fill((0, 0, 0))

    pygame.draw.rect(screen, box_colour, box)

    pygame.display.flip()


Now, there is a box in the middle of the screen!

Moving the box:

Great, there is a box, but it doesn't do a lot. Lets make that box move around!

In the last tutorial I mentioned the pygame.KEYUP event. There is an opposite event, pygame.KEYDOWN. We will use these to make the WASD keys make the box move. When a key is pressed down, we need to check which one is pressed and change the speed of the box accordingly. When the key goes up again we need to put the speed back to 0 in the relevant direction. The vert_keys and hori_keys tuples store the vertical and horizontal keys to make the checking nicer for when the key goes up.


vert_keys = (pygame.K_w, pygame.K_s)
hori_keys = (pygame.K_a, pygame.K_s)
if event.type == pygame.KEYDOWN:
    if event.key == pygame.K_w:
        box_speed[1] = -1
    if event.key == pygame.K_s:
        box_speed[1] = 1
    if event.key == pygame.K_a:
        box_speed[0] = -1
    if event.key == pygame.K_d:
        box_speed[0] = 1
elif event.type == pygame.KEYUP:
    if event.key in vert_keys:
        box_speed[1] = 0
    if event.key in hori_keys:
        box_speed[0] = 0


We now integrate this into the event loop of our main program. We should also make the position of the box change so that we can see an effect. We will need to move our definition of the Rect object into the loop since it takes the position as a parameter.


# import pygame modules
import pygame

# initialise pygame
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption('Hello, world!')

# set up box stuff
box_pos = [295, 295]
box_speed = [0, 0]
box_height = 10
box_width = 10
box_colour = (255, 255, 255)

vert_keys = (pygame.K_w, pygame.K_s)
hori_keys = (pygame.K_a, pygame.K_d)

# game loop
running = True
while running:
    # event loop
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_w:
                box_speed[1] = -1
            if event.key == pygame.K_s:
                box_speed[1] = 1
            if event.key == pygame.K_a:
                box_speed[0] = -1
            if event.key == pygame.K_d:
                box_speed[0] = 1
        elif event.type == pygame.KEYUP:
            if event.key in vert_keys:
                box_speed[1] = 0
            if event.key in hori_keys:
                box_speed[0] = 0
            if event.key == pygame.K_ESCAPE:
                running = False

    # update box
    box_pos[0] += box_speed[0]
    box_pos[1] += box_speed[1]

    # drawing code
    screen.fill((0, 0, 0))

    # draw box
    box = pygame.Rect(box_pos, (box_width, box_height))
    pygame.draw.rect(screen, box_colour, box)

    pygame.display.flip()


Now the box will move when you press the WASD keys! It moves fast, so be careful.

Next time I will talk about how we can use classes to make this code neater.
#13
In this thread I'll post the maps I make to visualise the fantasy world/continent I've been working on intermittently for some time now.

First up is Isla Venarta in the Early Venartan Era. This is the island that the Venartan refugees landed upon after fleeing their homeland following the as yet unnamed calamity caused by stuff. From here they built an empire spanning a good portion of the continent of Alatra.

Spoiler
#14
Township / Screenshots
December 09, 2013, 11:15:46 AM
Latest Screenshot:

Spoiler

This screenshot shows the newly implemented trees. This is the kind of area that would be ideal to found a township. Plenty of wood nearby, and fairly defensible due to the thinness of the land. Its also quite flat.

Previous Screenshots:

Spoiler
Spoiler

This screenshot shows the current output of the first version of the terrain generator. Before I can release it I'll need to redistribute the heights somewhat, so that there are actually areas of land flat enough to build on without it looking silly. At the moment the height of a tile is directly proportional to it's distance from water.
#15
Township / What to expect? (Next Release Preview)
December 05, 2013, 06:49:45 PM
The next release will be: Township pre-alpha 0.3

The last release was: Township pre-alpha 0.2

The Key Points

       
  • 0.2 required Python 3.x, this will work with either Python 2.7 or Python 3.4.
  • There will be maps of "unlimited" size.
  • It will be released once I have the basic map functionality fully implemented as a "tech demo" type thing.
  • There will also be an executable and installer for folk who don't want to battle with installing Python and all the required libraries.
Feature List

       
  • User interface for playing the game, rather than the old "only hotkeys" nightmare.
  • Infinite, 2D, map to build your townships.
  • Trees and stone scattered around the world, with stone common in higher areas and trees mostly gathered in the lowlands.
  • Villagers with a number of attributes that affect their ability to perform jobs in your township.
  • Monsters who roam the world, some which seek out your township and others who are passive unless disturbed.
#16
Could everyone please sign this petition in the interest of equality, and in general, of all that is right?

The advertising campaign associated with this petition is disgusting. Rape should never be treated lightheartedly, let alone be apparently condoned, or even incited.

The petition is here.

Thank you all.

SotK and MoD
#17
SotK's Python Tutorials
Part 6: Introduction to Pygame




What is Pygame?

Pygame is one method of creating a user interface in Python. It is used when you are aiming to make a game, which is the direction we are going to take with these tutorials. You can get Pygame from here. If you're on Windows you'll want "pygame-1.9.2a0.win32-py3.2.msi".

Pygame provides simple methods for drawing things into a window on screen, as well as support for sound and video.

Initialisation:

The first thing we need to do is import the pygame module. At the very top of your program, type import pygame to do this. With the module imported, we can use pygame's functionality. The following code will get us ready to make a window for you to draw things on!


pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption('Hello, world!')


The first line initialises pygame, and must be called before any other pygame functionality is used in your program. The second line creates a variable called screen. This is a 600 x 600 "Surface". A surface is basically an image that can be drawn to the monitor. screen will be our main surface, and the rest of our program will eventually be drawn on top of it. The third line sets the caption at the top of the window to be Hello, world!.

The next step to creating our blank window is to make an infinite loop, as we did in the last tutorial. This will be our game loop and will be where all of the functionality in the game is implemented. to do this we simply make a boolean variable, and use a while loop to create infinite repetition:


running = True
while running:
    # do stuff


Events:

We will also need a way to get out of this loop, so that we aren't stuck with an unresponsive program. To do this we will use another piece of functionality provided to us by pygame; the event loop.


for event in pygame.event.get():
    if event.type == pygame.QUIT:
        # Terminate on window closure
        running = False


pygame.event.get() returns a list of events that have occurred since the last time it was called, such as mouse movement, key presses, the close button being clicked, etc. This example is created when the close button is clicked. It is pretty much the same idea as the quit command we had in the calculator in the last tutorial. As an example, here is the code to close the program when the escape key is pressed:


elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
    # Terminate on Esc keypress
    self.running = False


As you can see here, events involving key presses have an attribute key, which you can use to determine which key was pressed. In a later tutorial we will use this to let us control things with the arrow keys. The pygame.KEYUP event occurs when a key is released. Similarly, pygame.KEYDOWN occurs when a key is pressed down.

Drawing:

Now we can keep our program running for as long as we want, its time to learn how to actually draw something on the screen. We'll start just by filling the screen with a colour:


screen.fill((0, 0, 0))


This will fill the window with black. This is a good place to start as it is the simplest method of drawing things available to us. We could just create a number of surfaces of different sizes and colours to represent different things in our game.

Once all your drawing code has executed, you will need to 'flip' the display. This will update all your changes made by drawing and show them on screen. This is equivalent to swapping buffers when using OpenGL, which I will go into at a later date.


pygame.display.flip()


Putting it together:

Now we know all the different elements we need to open a (600 x 600) pygame window on our screen, lets go ahead and do it. First we import pygame, then initialise it and set up the screen surface. Then we need our game loop, which will contain both the pygame event loop and our drawing code. This means that each frame consists of one pass through the game loop.


# import pygame modules
import pygame

# initialise pygame
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption('Hello, world!')

# game loop
running = True
while running:
    # event loop
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            # Terminate on window closure
            running = False
        elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
            # Terminate on Esc keypress
            self.running = False

    # drawing code
    screen.fill((0, 0, 0))

    pygame.display.flip()


Next time we will put something on this display, and have a little bit of user interaction!
#18
So yeah, Rome 2 was announced today!

Use this thread to discuss/explode with excitement. ;D

Check out this article for screenshots, concept art and information.
#19
So, Persian Invasion is finally released, and I thought I would make an AAR of my first campaign on it!

I looked at the factions in custom battles, and decided to play a Greek Hegemony campaign as Athens. I conquered a city, then ended my first turn. The a stroke of ridiculous bad luck as my faction leader died causing game over to occur. His son comes of age on turn 2 or 3...

I decided to try again, but this time with...




Alexander woke up from his dream. He had been dreaming of a time when Greece had been known to rule the world. A time when even the great Persian Empire was no more, and the people living in its bounds were heavily influenced by the Greek culture he knew. He had dreamt that the Greeks had ruled the world, rather than being a collection of city states fighting amongst themselves.

Alexander thought this was a good dream. Indeed, his opinion of it was made even better when the man's voice had proclaimed that the fallen empire was all the work of 'Alexander'. Whether this dream was a vision of his own future, a vision of the future of a descendant of his, or even just a simple dream he did not know. All he knew was what he wanted; to be this 'Alexander' spoken of in his dream, and lead all of the known world under the banner of the Kingdom of Macedonia!


Alexander's Kingdom

He ordered the creation of road infrastructure across his kingdom, as well as commissioning the building of places for the Macedonian Levies who defend the kingdom's borders from the Greek city states in the south and the barbaric tribesmen to the north to be trained more effectively. If one wishes to conquer the world, these things are a simple necessity.



Over a year had passed, and still Alexander's kingdom had not grown. He had become restless and hungry to begin his expansion some months earlier, and had sent his adopted heir Nearchos with a company of Royal Guards and a number of Levies to take the rebellious city state of Olynthus in the name of Macedonia. They had finally prepared to launch an assault by the winter...




Olynthus' days are numbered

I surveyed the scene outside the town. My first thought was that "city state" was too good a name for these backward rebels. Their so called city was little larger that some Macedonian villages I have seen, at least from the outside. I met with my commanders, and we decided on a frontal assault, seeing as how the town was defended by only a single troop of Hoplites. Arrogant rebels!

The rams we needed to break through their walls were soon constructed, and I sounded the order through the camp at dawn to prepare for a battle. Only a few hours later, the lines were assembled outside the town walls with our rams ready to move into position. I shouted some meaningless words of encouragement to the men and sounded the advance.



Rams advance towards the town

The rams seemed to take an eternity to break through, but when the walls were breached we met no resistance. I realised this must mean the garrison was protecting the centre of the town, having decided it impractical to defend their walls. Perhaps they are less arrogant than I imagined.


The walls are breached

Once again I sounded the advance and my men marched through the newly made openings and into the town. I ordered my most experienced troops, the Royal Guards, to lead the offensive and to head straight to the city, backed up by three of my units of Levies. One unit of Levies was kept back, in case the hoplites fought well enough to require a more refined manouver.


The advance begins

The Royal Guards had not long since engaged the hoplites when word reached me that these rebels are indeed stronger than anticipated! My Royal Guards were being crushed, the hoplites pinning them against walls with their long spears. I ordered the reserve Levies to join the fight, and lead my bodyguards into the city and around to the rear entrance to the town centre.





Guards are being crushed.
Levies reinforce.

Before the tide of the battle can be turned however the remaining Royal Guards decide that they have had enough, and turn to run from the battle. I would normally detest this kind of behaviour, but accept that a tactical error on my part caused them to lose far more men than they should. The hoplites began to push into the ranks of my weaker Levies, carving a path into their ranks.




Guards flee
Levies are cut down

As I turned the final corner to enter the town plaza, the sound of hooves behind the hoplites must have broken their resolve, as those left cast their spears on the ground and begged for mercy. Impressed by their bravery and fighting ability I give them their lives. The town however, is now under the rule of King Alexander I of Macedonia!


The town is ours!



Almost two years after the Siege of Olynthus, the town had grown to a respectable size and had been fully integrated into the kingdom. This was a period of calm as troops were replenished and buildings were improved. However, worrying news reached Alexander in early summer of the year 487BC. A messenger arrived from the city of Styberra, bringing news of a moderately sized Illyrian force near the region's north border. They were seemingly preparing to enter Macedonian lands.

The King ordered a deterrant to be placed in the city of Styberra in the form of a number of Macedonian Levies being moved from their various posts to garrison the city. Sure enough, upon hearing news of this the Illyrian army disappeared back into their own territory and once again Alexander began to dream of conquest...

#20
Total War Mods - The Engineer's Shed / SotK's Spain Mod
February 09, 2012, 02:00:47 AM





So, I mentioned I always thought it would be fun to play as Spain (Carthage crossed with barbarians seemed good). Phoenix said that they are bad, but I tried them out anyway. Sure enough, their unit selection made me want to cry. Just Carthage-lite with some naked guys. Oh and a couple of decent unique units, but they are only available from cities. When a nation that was famed for the prowess of its warriors has RTW's Iberian Infantry as their most easily accessible unit that isn't Town Militia/Peasants, and their only good troops come from a maxed out barracks or a single temple, there is a problem.

Problems are fixed by modding!

More info...

I renamed Spain to Iberia, and then this:

Spoiler
Barracks:

   - Town Militia
   - Caetrati

   - Town Militia
   - Caetrati
   - Scutarii

   - Town Militia
   - Caetrati
   - Scutarii
   - Scutarii Lanceari

   - Town Militia
   - Caetrati
   - Scutarii
   - Scutarii Lanceari
   - Heavy Scutarii

Stable:

   - Caetrati Cavalry

   - Caetrati Cavalry
   - Cantabrian Cavalry
   - Lanceari Cavalry

Range:

   - Slingers

   - Slingers
   - Archers

Celtiberian Barracks:

   - Celtiberian Swordsmen

Temple:

   - Bull Warriors

Skirmishers no longer exist, because they are superseded by Caetrati. These are a more historically accurate version of vanilla's Iberian Infantry. They are lightly armoured infantry who carry a number of pilum-style javelins, a falcata sword (historically adapted by the Romans into the gladius) and a small round shield (a caetra).

Caetrati

Also replacing Iberian Infantry are the Scutarii. These are in vanilla, but as the best mainline troops available to Spain (and as such only recruitable in cities). In my mod they are weakened, but still better than vanilla Iberian Infantry.

I added Scutarii Lanceari as there is a severe lack of spearmen available to Spain in vanilla. They can't form a phalanx (although I did toy with the idea), but are decent fighters and good against cavalry.

Heavy Scutarii are exactly what their name says. They are more heavily armoured and armed than the standard Scutarii, and so will be useful in the late game to hold out against more powerful enemies than the local competitors.

Heavy Scutarii

Caetrati Cavalry are essentially Round Shield Cavalry, but like the Caetrati they carry a few javelins. Similarly, Lanceari Cavalry are pretty much Long Shield Cavalry.

Celtiberian Swordsmen are exactly what their name suggests. Coming from the Celtic-influenced Celtiberian tribes, they have big swords and aren't afraid to use them. They will be recruitable by some factions in an AOR-style system in the minor city barracks too (in Celtiberia).

Screenshots later when I take some good ones ;D .




I recently played the swap game with Thrace and was struck by their limited roster and similar to Spain they felt like Macedon-lite with a bit of Dacia thrown in. While this is a bit more exotic than Carthage and naked fanatics, they still didn't feel like they were a faction with their own identity that would give a memorable experience to play as. So I added some hoplites and some more cavalry. Then things escalated!

More info...
Veteran Hoplites!

Spoiler

Also, thureophoroi (read weakened heavy peltasts with spears as their secondary weapon). I think I will use these to replace militia hoplites. They are actually marginally better in my opinion (slightly better armour, has a ranged weapon, same spears, although lack phalanx ability) and therefore are more expensive. I'll play a campaign with them replaced and see what its like compared with my game with militia hoplites in instead. They'll have a stronger version (Thorakitai) available to fill the role that I would have used them for as heavy peltasts. They will be a high-level range unit though, rather than from a basic barracks.

Spoiler

First up on the cavalry list are the Getic Lancers (Thrace own Campus Getae, lets give them some Getae horsemen :D ). They fill a similar role to Macedon's light lancers, albeit with a weaker charge. Their shields make up for that difference though.

Spoiler

The second addition is the Thracian Noble Cavalry. I kinda cheated a little here, they are just the General's Bodyguard guys. Excellent shock cavalry, recruitable from a level 3 stables (Hippodrome?).

Spoiler

I'm removing the Greek cavalry, since that spot is filled by the Getic Lancers. However, Getic Lancers will be recruited from a basic stables alongside Militia Cavalry. The second level of stables will add some horse archers, as yet unimplemented. Then finally the third level will add the Thracian Noble Cavalry.

That gives the following roster:

Spoiler
Barracks:

   - Thureophoroi

   - Thureophoroi
   - Falxmen
   - Thracian Hoplites

   - Thureophoroi
   - Falxmen
   - Thracian Hoplites
   - Getic Warriors
   - Phalanx Pikemen

   - Thureophoroi
   - Falxmen
   - Thracian Hoplites
   - Getic Warriors
   - Phalanx Pikemen
   - Thracian Elite Hoplites
   - Bastarnae

Stable:

   - Militia Cavalry
   - Getic Lancers

   - Militia Cavalry
   - Getic Lancers
   - Getic Horse Archers

   - Militia Cavalry
   - Getic Lancers
   - Getic Horse Archers
   - Thracian Noble Cavalry

Range:

   - Peltasts

   - Peltasts
   - Archers

   - Peltasts
   - Archers
   - Thorakitai                  (Ultra-heavy peltasts)

The elite hoplites are those pictured earlier in the thread, they are similar to armoured hoplites belonging to the Greek Cities. Thracian Hoplites will be similar to the standard Greek hoplites.