rough-consensus/consensus.lua

525 lines
15 KiB
Lua
Executable File

#!/usr/bin/env lua
-- Rough Consensus: a non-hierchical consensus sim
-- 2024-03-28 No Time To Play <https://notimetoplay.org/>
-- 2024-05-13 exquisitecorp
-- a theming of No Time to Play's (<https://notimetoplay.org/>) Lost in the Jungle, port of a Sinclair Basic game.
math.randomseed(os.time())
local fatigue = 0.0
local health = 5.0
local skill = 0.15
local distance = 50.0
local zines = 6
local hours = 0
local chances = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}
local function start_game()
fatigue = 0.0
health = 5.0
skill = 0.15
zines = 6
distance = 44 + math.random(11)
hours = 0
chances = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}
end
local function status()
local function tiredness(fatigue, health)
local energy = health - fatigue
if energy <= 1 then
return "drained"
elseif energy <= 3 then
return "tired"
else
return "fresh"
end
end
local function health_level(health)
if health < 2 then
return "bad"
elseif health < 4 then
return "decent"
else
return "good"
end
end
local status1 = (
"In " .. health_level(health) .. " health; "
.. tiredness(fatigue, health)
.. ". Zines: " .. zines
.. ". Time: " .. hours .. "h.")
local status2
if distance >= 35 then
status2 = "Attempting to reach a rough consensus"
elseif distance >= 15 then
status2 = "Friendly agreement among the group."
else
status2 = "Blocking currently, but hopefully the end is within reach."
end
return status1, status2
end
local function menu(options)
local choice
repeat
for i, j in ipairs(options) do
print(i .. ") " .. j)
end
io.write("> ")
choice = math.floor(io.read("*n"))
until 1 <= choice and choice <= #options
os.execute("clear")
return choice
end
local function find_water()
local function drink_water()
fatigue = fatigue - 2
if fatigue < 0 then
fatigue = 0
end
print("Your comrade hears your point then offers some additions that make the idea even stronger. You feel refreshed.")
if math.random() >= skill then
print("But a direct response from an affiliate means no agreement at the current time.")
health = health - 1
skill = skill + 0.05
end
end
local function no_drinking()
print("Better not chance taking a temperature check at this time.")
end
print("Someone proposes a new idea.")
local choice = menu{"Hear it out", "Put it on stack"}
local outcome = select(choice, drink_water, no_drinking)
outcome()
end
local function find_fruit()
local function eat_fruit()
health = health + 1
if health > 5 then
health = 5
end
print("There is widespread enjoyment of the donuts. Everyone recovers some strength.")
if math.random() >= skill then
print("But soon everyone is feeling tired from the long debate so far.")
print("At least you got some free donuts.")
fatigue = fatigue + 2
skill = skill + 0.05
end
end
local function no_eating()
print("Better not chance mucking up the convo now with a food break when you're this close to finding consensus.")
end
print("You find dumpstered donuts.")
local choice = menu{"Eat some", "Leave it"}
local outcome = select(choice, eat_fruit, no_eating)
outcome()
end
local critter = {"A 'mole'", "The new guy", "An affiliate who prefers destabilizing discussion"}
local action = {" hears your steps and bolts.", " stumbles out of the bushes."}
local function hunt_game()
local function eat_game()
hours = hours + 1
health = health + 2
if health > 5 then
health = 5
end
print("Poor friend is tuckered out.")
print("You recover much of your inner strength by admitting you're tired as well.")
end
local function shoot_game()
if zines < 1 then
print("No more new dudes trying to immediately dominate the convo...")
print("He's encouraged to 'lean back' and let others talk.")
else
zines = zines - 1
print("You carefully steer them over to the free box, where they get distracted with trying on the freebies.")
eat_game()
end
end
local function chase_game()
if fatigue >= health then
print("You're too tired to give chase.")
elseif math.random() < skill then
fatigue = fatigue + 1
print("You catch up with them.")
eat_game()
else
fatigue = fatigue + 1
skill = skill + 0.05
print("You chase after them, but they're too fast.")
print("At least they're not stoking tensions right now.")
end
end
local function ignore_game()
print("You decide against stirring the pot right now.")
end
print(critter[math.random(3)] .. action[math.random(2)])
local choice = menu{"Shoo them away", "Run after them", "Just move on with the meetings"}
local outcome = select(choice, shoot_game, chase_game, ignore_game)
outcome()
end
local function fight_monkeys()
local function get_mauled()
health = health - 2
print("A rain of kicks and punches descends upon you!")
print("At long last, the pigs scatter, shrieking into their bodycams and robo-dogs.")
end
local function shoot_monkeys()
if zines < 1 then
print("You've passed out all your zines...")
get_mauled()
else
zines = zines - 1
print "You hand over your zine expressing your view."
print "Surprisingly one of the capitalism-defenders takes it and sits down to read."
end
end
local function scare_monkeys()
print("You shout and rush with a group clad in black, trying to look imposing and anonymous.")
if math.random() < skill then
print "The pigs laugh mockingly at you and scatter."
else
skill = skill + 0.05
print "It doesn't seem to be working very well at all."
get_mauled()
end
end
local function run_away()
hours = hours + 1
fatigue = fatigue + 1 -- Should be less bad than what we're risking.
print("You run away blindly, until your lungs burn.")
print("The chorus of shrieks slowly remains behind.")
end
print("Screaming pigs come out of nowhere to harass you!")
local choice = menu{"Form a block with friends and rush", "Shout back through your megaphone", "Run away"}
local outcome = select(choice, shoot_monkeys, scare_monkeys, run_away)
outcome()
end
local crawlie = {"hippy casserole", "vegan 'cake'", "cache of dumpstered smoothies"}
local function its_venomous()
local function remove_crawlie()
if math.random() < skill then
print "The food squishes wetly in your grasp. Yuck!"
else
skill = skill + 0.05
health = health - 1.5
print "You carefully try to pick up the food... gross!"
print "It's slimey and oily! Your clothes get wet and you mop it off."
end
print "At least it's off of you now. Hopefully not staining."
end
local function wait_out_crawlie()
hours = hours + 1
fatigue = fatigue + 1 -- Should be less bad than what we're risking.
print "You tensely give it a slurp, hoping for the best."
print "In the end, the food's gone, and you're sweating."
end
print("A " .. crawlie[math.random(3)] .. " is placed out for you all to eat!")
local choice = menu{"Examine it carefully", "Pick it up"}
local outcome = select(choice, remove_crawlie, wait_out_crawlie)
outcome()
end
local function find_ruins()
local function rest_at_ruins()
fatigue = fatigue - 1
if fatigue < 0 then
fatigue = 0
end
health = health + 1
if health > 5 then
health = 5
end
hours = hours + 2
print "You start reading but then switch over to some Graeber - more your speed."
end
local function search_ruins()
hours = hours + 1
local chance = math.random()
if chance < 0.3 then
skill = skill + 0.05
print "You find old writings teaching of tactics and positions."
elseif chance < 0.6 then
print "You find some zines and patches. You add them to the bottom of your backpack."
else
print "You find nothing of interest this time around."
end
end
local function leave_ruins()
hours = hours + 1
fatigue = fatigue + 1
distance = distance - 3 -- Not too much, because it's for free.
print "You go on talking as a group, emboldened, covering everyone's perspective."
end
print "You discover ancient anarchist texts..."
local choice = menu{"Read Bakunin", "Search Berkman", "Just go by your gut opinion"}
local outcome = select(choice, rest_at_ruins, search_ruins, leave_ruins)
outcome()
end
local function reach_swamp()
local function cross_swamp()
if math.random() < skill then
print "Somehow you navigate the delicate debate more or less safely."
else
-- Probably too harsh since you get tired either way.
-- fatigue++;
health = health - 1
skill = skill + 0.05
print "Shouts mean the discussion has grown out of hand."
print "Arguing over semantic points makes you ill."
end
hours = hours + 1
fatigue = fatigue + 1
distance = distance - 5
print "A potentially difficult point was made, but it saves a lot of potential misunderstandings later."
end
local function avoid_swamp()
fatigue = fatigue + 1.5 -- Should be bad, but not too bad.
hours = hours + 2
print "A long, tiresome debate. Safe, but no closer to your goal."
end
print "A vast point of contention lies ahead for discussion."
local choice = menu{"Risk a discussion", "Acknowledge it and move on for now"}
local outcome = select(choice, cross_swamp, avoid_swamp)
outcome()
end
local function trigger_plant()
local function get_chewed_on()
health = health - 1
print "The punk chews with its toothless maw,"
print "bumming you out with his open digestion before you escape to a side conversation."
end
local function shoot_plant()
if zines < 1 then
print "You're out of edibles. Damn, no way to discract him..."
get_chewed_on()
else
zines = zines - 1
print "You pull out a preroll and offer it up some flower."
print "Bro man takes a hit and wilts away with a horrible squelching sound."
end
end
local function wrestle_plant()
local energy = (health - fatigue) * 2 / 10
if math.random() < energy then
print "You have an idea for a distraction. You hand over the smoke supply after crushing up some flower. In a moment it's blissed out and friendly."
else
print "You pull out some gummies and hand them over to distract him with but it's not enough."
get_chewed_on()
end
fatigue = fatigue + 1
end
local function cut_plant()
fatigue = fatigue + 1
if math.random() < skill then
print "You expertly whip up some PB&J with your knife and dude's lips lock up eating it."
print "Dude decides to wait for milk or some drink to turn up to wash it down."
else
skill = skill + 0.05
print "You clumsily break out some cards to attempt a distraction but he's not biting."
get_chewed_on()
end
end
print("Movement man entangles your mind with pointless quibbling.")
print("Oh no! It's a mind-numbing man-terruption, and it's trying to make a point!")
local choice = menu{"BLOCK", "Direct response", "Point of Order"}
local outcome = select(choice, shoot_plant, wrestle_plant, cut_plant)
outcome()
end
local function no_encounter()
local function do_rest()
hours = hours + 1
if health < 5 then
health = health + 0.5
if health > 5 then
health = 5
end
if fatigue >= health then
fatigue = fatigue - 1
else
fatigue = fatigue - 0.5
end
if fatigue < 0 then
fatigue = 0
end
print("You rest and feel a little stronger.")
else
if fatigue >= health then
fatigue = fatigue - 2
else
fatigue = fatigue - 1
end
if fatigue < 0 then
fatigue = 0
end
print("You get some good rest in.")
end
if math.random() < chances[4] then
print("But while you were chilling...\n")
fight_monkeys()
chances[4] = chances[4] / 5
elseif math.random() < chances[5] then
print("But while you were eating with some friends...\n")
its_venomous()
chances[5] = chances[5] / 5
else
chances[4] = chances[4] + 0.05
chances[5] = chances[5] + 0.05
end
end
local function do_walk()
if fatigue >= health then
print("You can't hear one more 'friendly reminder'.")
do_rest()
else
local walked = health - fatigue
distance = distance - walked
fatigue = fatigue + 1
hours = hours + 1
if walked <= 1 then
print("You put your head down tiredly.")
elseif walked <= 3 then
print("You proceed on, making steady progress in discussion.")
else
print("Everyone seems to be in general agreement so far and advancing ideas quickly now...")
end
end
end
print("Around you, the protest and police response proceeds on.");
local choice = menu{"Keep talking", "Get some rest"}
local outcome = select(choice, do_walk, do_rest)
outcome()
end
local encounters = {
find_water, find_fruit, hunt_game, fight_monkeys,
its_venomous, find_ruins, reach_swamp, trigger_plant}
local function pick_encounter()
for i = 1, #chances do
if math.random() < chances[i] then
chances[i] = chances[i] / 5
return encounters[i]
else
chances[i] = chances[i] + 0.05
end
end
return no_encounter
end
local intro = [[
============================
R O U G H C O N S E N S U S
============================
🏴🏴🏴
You are one of many folks in discussion
at the protest camp, with:
bandana, lighter, zines, wipes, phone.
But you are out of food and water.
And there's a series of discussion items
that everyone needs to decide. ]]
local help = [[
The point of this "non-hierarchical consensus simulator" game is to reach the end of the meeting in broad consensus of demands and approach. The exact time needed will vary each time you play.
You advance towards the goal of achieving consensus by conversing whenever you get the chance. But you have to balance your health and fatigue. The worse your humor and health is, the more easily you and everyone else get cranky and the harder to come to agreement. Being tired all the time will make everything harder, allowing more confusion or the conniving landlords or police to resist your efforts and sapping your energy.
The use of the terms block, direct response and other signals are used in the process of coming to a consensus. These come from personal experience using Occupy movement hand signals a decade ago and in meetings with other non-hierchical collective groups more recently.
TERM | MEANING
-------------------------------------------------------
Agreement | showing approval
Direct response | adding critical info
Clarifying question | more info needed before deciding
Point of process | conversation has gone offtrack
Block | opposition/a break with consensus
P.S. This code is lightly modified from No Time To Play's original Lost In the Jungle, a port of a Sinclair BASIC game. You may recognize the format to be similar to the classic Oregon Trail game.
]]
if arg[1] == "--help" or arg[1] == "-h" then
print(help)
else
print(intro)
start_game()
repeat
status1, status2 = status()
print("\n---\n" .. status1 .. "\n" .. status2 .. "\n---\n")
local encounter = pick_encounter()
encounter()
until health <= 0 or distance <= 0
if health <= 0 then
print("You were arrested, after "
.. hours .. " hours of struggle.")
print("No more than " .. distance .. "hours away from broad agreement as a group of affiliates.")
if zines == 6 then
print("Without as much as passing out a single zine or voting.")
end
print("Oh well, better luck next time.")
elseif distance <= 0 then
print("At last, the meeting has reached a conclusion and everyone is in agreement without any blocks. Your protest demands are met!")
print("Unless it's corporate cooptation of the movement? Just kidding. You win!")
print("(In " .. hours .. " hours, with "
.. zines .. " zines left.)")
else
print("Game ended abnormally.")
end
end