Skip to content

Commit aa187f2

Browse files
committed
Embryonic example turn-based strategy game
1 parent 035a1a0 commit aa187f2

5 files changed

Lines changed: 375 additions & 1 deletion

File tree

game-nation/code.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"use strict"
2+
3+
/*
4+
The WAIT command causes a turn (3 months) to pass.
5+
It is handled in four phases, with the results output at the end
6+
7+
initTurn Initialise stuff
8+
beforeTurn Calculate bonuses
9+
doTurn Apply bonuses to determine world state
10+
afterTurn React to the new world state
11+
12+
*/
13+
14+
findCmd('Wait').script = function() {
15+
for (const s of ['initTurn', 'beforeTurn', 'doTurn', 'afterTurn']) {
16+
for (const key in w) {
17+
const o = w[key]
18+
if (o[s]) o[s]()
19+
}
20+
}
21+
22+
if (home.output) {
23+
msg(home.output.join('|'))
24+
}
25+
else {
26+
msg('Not much happens.')
27+
}
28+
29+
return world.SUCCESS
30+
}
31+
32+
/*
33+
Something that progresses over the course of the game, tracked with the progress
34+
attribute.
35+
36+
You should give it alias, examine and efficiency attributes, and also flag it to
37+
appear in whatever inventory. Set discovered to true to have it going from the start
38+
*/
39+
const PROGRESSABLE = function() {
40+
const res = NPC()
41+
res.loc = 'me'
42+
res.progress = 0
43+
res.invShow = function() { return this.discovered},
44+
res.initTurn = function() {
45+
this.bonus = 0
46+
}
47+
res.beforeTurn = function() {
48+
if (!this.discovered) return
49+
50+
this.progress += this.efficiency * (100 + this.bonus) / 100
51+
home.output.push('Science marches on!')
52+
}
53+
return res;
54+
}
55+
56+
57+
58+
const createDiscovery = function(name, data) {
59+
60+
const o = createItem(name, data)
61+
if (!o.beforeTurn) {
62+
o.beforeTurn = function() {
63+
if (this.active) w[this.bonusTo].bonus += this.bonusValue
64+
}
65+
}
66+
67+
createItem(o.name + '_convTopicQ', TOPIC(), {
68+
loc:o.belongsTo,
69+
alias:sentenceCase(o.alias),
70+
showTopic:true,
71+
discoveryName:o.name,
72+
hideAfter:false,
73+
isVisible:function(loc) { return w[this.loc].discovered },
74+
script:function(options) {
75+
msg(w[options.topic.discoveryName].question)
76+
if (!w[options.topic.discoveryName].choices) {
77+
w[options.topic.discoveryName].choices = [
78+
{
79+
alias:'Yes',
80+
properNoun:true,
81+
script:function() {
82+
w[options.topic.discoveryName].active = true
83+
msg(options.topic.alias + ": Enabled")
84+
}
85+
},
86+
{
87+
alias:'No',
88+
properNoun:true,
89+
script:function() {
90+
w[options.topic.discoveryName].active = false
91+
msg(options.topic.alias + ": Disabled")
92+
}
93+
},
94+
]
95+
}
96+
showMenuDiag(w[options.topic.discoveryName].question, w[options.topic.discoveryName].choices, function(result) {
97+
log(result)
98+
result.script()
99+
})
100+
},
101+
})
102+
}
103+
104+

game-nation/data.js

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
"use strict"
2+
3+
createItem("me", PLAYER(), {
4+
loc:"lounge",
5+
synonyms:['me', 'myself'],
6+
examine: "Just a regular guy.",
7+
})
8+
9+
createRoom("lounge", {
10+
desc:"Welcome, your majesty...",
11+
})
12+
13+
14+
createItem("home", {
15+
progress:0, // i.e., turn
16+
population:100,
17+
food:100,
18+
money:100,
19+
happiness:'contented',
20+
21+
initTurn:function() {
22+
this.output = []
23+
},
24+
25+
beforeTurn:function() {
26+
this.progress += 1
27+
},
28+
29+
doTurn:function() {
30+
// handle discoveries
31+
for (const key in w) {
32+
const o = w[key]
33+
if (!o.discovery) continue
34+
if (o.discovered) continue
35+
log(o.name + ' (' + o.discoverAt + ')')
36+
const owner = w[o.belongsTo]
37+
log('- ' + owner.name + ' (' + owner.progress + ')')
38+
if (!owner) {
39+
return errormsg('The "belongsTo" attribute of ' + o.name + ' is wrong or missing:' + o.belongsTo)
40+
}
41+
if (o.discoverAt <= owner.progress) {
42+
o.discovered = true
43+
home.output.push(o.discovery)
44+
}
45+
}
46+
},
47+
})
48+
49+
// for convenience
50+
const home = w.home
51+
52+
// progress tracks how far we have got in each aspect
53+
// efficiency allows us to tweak how well we do each turn
54+
// bonus is an in-game modifier
55+
56+
57+
createItem("agriculture", {
58+
foodBySeason:[0, 15, 50, 135],
59+
foodCommentBySeason:['No food produced in winter', 'Hunters have found limited food across the spring.', 'Hunters have found plenty of food across the summer.', 'Food has been harvested from the fields'],
60+
foodFactor:0.4, // one person eats this much food each turn
61+
populationGrowth:0.02, // pop increases by this each turn unless starving
62+
starvationFactor:2, // how quickly people die when starving
63+
efficiency:1, // how well farms do
64+
65+
initTurn:function() {
66+
this.bonus = 0
67+
},
68+
69+
doTurn:function() {
70+
log("Bonus: " + this.bonus)
71+
const season = home.progress % 4
72+
home.output.push(this.foodCommentBySeason[season])
73+
home.food += this.foodBySeason[season] * this.efficiency * (100 + this.bonus) / 100
74+
75+
const foodConsumption = this.foodFactor * home.population
76+
if (foodConsumption > home.food) {
77+
home.population -= Math.floor((foodConsumption - home.food) / this.starvationFactor)
78+
home.food = 0
79+
home.output.push('People are starving!')
80+
}
81+
else {
82+
home.population += Math.floor(home.population * this.populationGrowth)
83+
home.food -= foodConsumption
84+
}
85+
},
86+
})
87+
88+
89+
90+
91+
createItem("science", PROGRESSABLE(), {
92+
alias:'Science Minister',
93+
dept:true,
94+
efficiency:1,
95+
examine:"Science is important to any nation; it can lead to more efficient agriculure, new industries and better weapons. And no good ruler wants anyone to think his kingdom is a technologcal backwater.",
96+
discovered:true, // known from the start
97+
98+
99+
})
100+
101+
102+
103+
createDiscovery("crop_rotation", {
104+
discovery:'"Your magesty, our neighbours are getting better yields from the land using a system called crop rotation, whereby a field is used for grain one year, legumes the next and left fallow for livesock the next. Perhaps you might discuss with the miniser for the land."',
105+
belongsTo:'science',
106+
discoverAt:2,
107+
bonusValue:20,
108+
bonusTo:'agriculture',
109+
question:"Implement crop rotation?",
110+
})
111+
112+
113+
114+
createItem("druids", PROGRESSABLE(), {
115+
alias:'Druid representative',
116+
dept:true,
117+
efficiency:1,
118+
examine:"Druids worship the earth mother, at a sacred grove in the forest.",
119+
120+
belongsTo:'home',
121+
discoverAt:7,
122+
discovery:'"Your magesty, a group of druids have found a sacred grove in the forest, and wish to use it in their arcane rituals."',
123+
})
124+
125+
126+
127+
createDiscovery("arcane_rituals", {
128+
discovery:'"Your majesty, the earth goddess has indicated she wishes us to perform certain rituals. I can assure you that if you will permit us to perform these sacred duties properly, crops will be even more bountiful"."',
129+
alias:'Fertility rituals',
130+
belongsTo:'druids',
131+
discoverAt:1,
132+
bonusValue:20,
133+
bonusTo:'agriculture',
134+
question:"Allow the druids to perform arcane rituals?",
135+
})
136+
137+
138+
139+
createDiscovery("moonlight_dancing", {
140+
discovery:'"Your majesty, the earth goddess has indicated she wishes us to perform certain rituals in a more natural form under the light of the full moon, but the yokels object to our naked dancing. I can assure you that if you will permit us to perform these sacred duties properly, crops will be even more bountiful"."',
141+
alias:'Fertility rituals 2',
142+
supercedes:"arcane_rituals",
143+
belongsTo:'druids',
144+
discoverAt:4,
145+
bonusValue:20,
146+
bonusTo:'agriculture',
147+
beforeTurn:function() {
148+
if (this.active === 2) w[this.bonusTo].bonus += this.bonusValue
149+
if (this.active === 1) w[this.bonusTo].bonus += w[this.supercedes].bonusValue
150+
},
151+
question:"Allow the druids to perform naked rituals under the moonlight at the risk of alienating locals?",
152+
choices:[
153+
{
154+
alias:'Allow naked moonlit rituals',
155+
properNoun:true,
156+
script:function() {
157+
w[options.topic.discoveryName].active = 2
158+
msg(options.topic.alias + ": Naked/moonlit")
159+
}
160+
},
161+
{
162+
alias:'Allow basic rituals only',
163+
properNoun:true,
164+
script:function() {
165+
w[options.topic.discoveryName].active = 1
166+
msg(options.topic.alias + ": Basic")
167+
}
168+
},
169+
{
170+
alias:'Forbid rituals',
171+
properNoun:true,
172+
script:function() {
173+
w[options.topic.discoveryName].active = 0
174+
msg(options.topic.alias + ": Disabled")
175+
}
176+
},
177+
],
178+
179+
})
180+
181+
182+
183+

game-nation/settings.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"use strict"
2+
3+
settings.title = "Nation!"
4+
settings.author = "The Pixie"
5+
settings.version = "0.1"
6+
settings.thanks = []
7+
settings.warnings = "No warnings have been set for this game."
8+
settings.playMode = "dev"
9+
10+
settings.disableChecks = true
11+
12+
settings.compassPane = false
13+
settings.textInput = false
14+
settings.cmdEcho = settings.playMode === 'dev'
15+
settings.suppressTitle = true
16+
settings.roomTemplate = []
17+
18+
settings.noTalkTo = false
19+
settings.funcForDynamicConv = 'showMenuDiag'
20+
21+
settings.seasons = ['winter', 'spring', 'summer', 'autumn']
22+
settings.startYear = 327
23+
24+
25+
settings.toolbar = [
26+
{content:function() { return 'It is ' + settings.seasons[home.progress % 4] + ', ' + Math.floor(settings.startYear + home.progress / 4) }},
27+
{title:true},
28+
{buttons:[
29+
{ title: "Dark mode", icon: "fa-moon", cmd: "dark" },
30+
{ title: "Save", icon: "fa-upload", cmd: "save game ow" },
31+
{ title: "Load", icon: "fa-download", cmd: "load game" },
32+
{ title: "About", icon: "fa-info-circle", cmd: "about" },
33+
]},
34+
]
35+
36+
37+
settings.inventoryPane = [
38+
{name:'Departments', alt:'depts', test:function(o) { return o.dept && o.invShow() } },
39+
{name:'Factions', alt:'factions', test:function(o) { return o.faction && o.invShow() } },
40+
{name:'Neigbours', alt:'nations', test:function(o) { return o.nation && o.invShow() } },
41+
]
42+
43+
44+
45+
settings.statusPane = "Status"
46+
settings.statusWidthLeft = 120
47+
settings.statusWidthRight = 40
48+
settings.status = [
49+
function() { return "<td>Population</td><td>" + Math.floor(home.population) + "</td>" },
50+
function() { return "<td>Treasury</td><td>" + Math.floor(home.money) + "</td>" },
51+
function() { return "<td>Food</td><td>" + Math.floor(home.food) + "</td>" },
52+
function() { return "<td>Attitude</td><td>" + home.happiness + "</td>" },
53+
]
54+
55+
56+
settings.setup = function() {
57+
createAdditionalPane(1, "Time", 'next-turn', function() {
58+
let html = '<input type="button" onclick="runCmd(\'wait\')" value="Next turn" />'
59+
return html
60+
})
61+
62+
msg("Welcome...")
63+
}
64+
65+

game-nation/style.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#next-turn {
2+
text-align: center;
3+
padding: 5px;
4+
}
5+
6+
#next-turn [type="button"] {
7+
color:red;
8+
border-radius: 6px;
9+
height:24px;
10+
font-style: italic;
11+
font-weight: bold;
12+
}
13+
14+
#next-turn [type="button"]:hover {
15+
background-color: yellow;
16+
color: black;
17+
}
18+
19+
20+

lib/_world.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,9 @@ const world = {
441441
// Otherwise, do this is the commanded ended in success or if the author wants failures to count as a turn
442442
game.turnCount++
443443
game.elapsedTime += settings.dateTime.secondsPerTurn
444-
for (const key in w) w[key].endTurn()
444+
for (const key in w) {
445+
w[key].endTurn()
446+
}
445447
for (const m of settings.modulesToEndTurn) m.endTurn()
446448

447449
util.handleChangeListeners()

0 commit comments

Comments
 (0)