parent
3098eb9867
commit
3d5baa3e89
|
@ -8,8 +8,6 @@ game jam.
|
|||
It is a parody of the 1973 business simulation game [Lemonade Stand](https://en.wikipedia.org/wiki/Lemonade_Stand)
|
||||
by Bob Jamison.
|
||||
|
||||
**Note:** This game has not yet been released. The following links are placeholders.
|
||||
|
||||
## Play
|
||||
|
||||
### Browser
|
||||
|
|
95
game/game.go
95
game/game.go
|
@ -37,6 +37,8 @@ type Game struct {
|
|||
inputLetters bool // Whether to allow the user to input letters.
|
||||
|
||||
sim *Simulation
|
||||
|
||||
gameOver bool
|
||||
}
|
||||
|
||||
func loadFont() font.Face {
|
||||
|
@ -115,14 +117,13 @@ func (g *Game) inputActive() bool {
|
|||
|
||||
func (g *Game) acceptInput(text string) (handled bool) {
|
||||
if text == "" {
|
||||
log.Println("blank", g.currentView)
|
||||
switch g.currentView {
|
||||
case world.ViewStartDayProduction1:
|
||||
if g.sim.MakePretzels == -1 {
|
||||
if g.sim.MakePretzels == -1 || g.sim.MakePretzels*PretzelCost > g.sim.Money {
|
||||
return false
|
||||
}
|
||||
case world.ViewStartDayProduction2:
|
||||
if g.sim.MakeSigns == -1 {
|
||||
if g.sim.MakeSigns == -1 || g.sim.MakeSigns*SignCost > (g.sim.Money-(g.sim.MakePretzels*PretzelCost)) {
|
||||
return false
|
||||
}
|
||||
case world.ViewStartDayProduction3:
|
||||
|
@ -138,16 +139,20 @@ func (g *Game) acceptInput(text string) (handled bool) {
|
|||
}
|
||||
switch g.currentView {
|
||||
case world.ViewStartDayProduction1:
|
||||
if i < 1 || i*PretzelCost > g.sim.Money {
|
||||
return false
|
||||
}
|
||||
g.sim.MakePretzels = i
|
||||
case world.ViewStartDayProduction2:
|
||||
if i*SignCost > (g.sim.Money - (g.sim.MakePretzels * PretzelCost)) {
|
||||
return false
|
||||
}
|
||||
g.sim.MakeSigns = i
|
||||
case world.ViewStartDayProduction3:
|
||||
g.sim.PretzelPrice = i
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("accept input:" + text)
|
||||
|
||||
g.currentView++
|
||||
partialTransition := g.currentView == world.ViewStartDayProduction2 || g.currentView == world.ViewStartDayProduction3
|
||||
if partialTransition {
|
||||
|
@ -158,9 +163,7 @@ func (g *Game) acceptInput(text string) (handled bool) {
|
|||
g.viewTicks = 0
|
||||
}
|
||||
|
||||
log.Println(g.currentView)
|
||||
if g.currentView == world.ViewDay {
|
||||
log.Println("start day")
|
||||
g.sim.StartDay()
|
||||
}
|
||||
|
||||
|
@ -220,6 +223,18 @@ func (g *Game) drawDay() error {
|
|||
g.setDayCell(a.X+1, a.Y, 'o')
|
||||
}
|
||||
|
||||
// Draw labels.
|
||||
drawText(17, 0, fmt.Sprintf("DAY %d", g.sim.Day))
|
||||
drawText(8, 2, fmt.Sprintf("STOCK %d", g.sim.MakePretzels-g.sim.PretzelsSold))
|
||||
drawText(24, 2, fmt.Sprintf("SOLD %d", g.sim.PretzelsSold))
|
||||
if g.sim.Disaster != 0 {
|
||||
label := disasterLabel(g.sim.Disaster)
|
||||
drawText(20-len(label)/2, 12, label)
|
||||
|
||||
drawText(11, 14, "NO CUSTOMERS TODAY")
|
||||
drawText(7, 17, "PRESS ENTER TO CONTINUE...")
|
||||
}
|
||||
|
||||
g.textBuffer.Clear()
|
||||
for y := range g.dayBuffer {
|
||||
if y != 0 {
|
||||
|
@ -234,6 +249,29 @@ func (g *Game) refreshBuffer() error {
|
|||
// TODO only do this when the view buffer or input buffer changes
|
||||
// TODO fix trailing newline causing scroll bar to appear
|
||||
|
||||
if g.gameOver {
|
||||
g.textBuffer.Clear()
|
||||
g.textBuffer.Write([]byte(fmt.Sprintf(` DAY %d
|
||||
|
||||
|
||||
|
||||
|
||||
YOU HAVE NO MONEY!
|
||||
|
||||
YOU ARE NOW HOMELESS!
|
||||
|
||||
THERE IS NO HOPE LEFT
|
||||
IN YOUR EYES... ONLY PAIN!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
GAME OVER`, g.sim.Day)))
|
||||
return nil
|
||||
}
|
||||
|
||||
if g.currentView == world.ViewDay {
|
||||
return g.drawDay()
|
||||
}
|
||||
|
@ -241,10 +279,11 @@ func (g *Game) refreshBuffer() error {
|
|||
income := g.sim.PretzelsSold * g.sim.PretzelPrice
|
||||
totalIncome := fmt.Sprintf("$%d.%02d", income/100, income%100)
|
||||
|
||||
totalExpenses := "$1.45"
|
||||
expenses := (g.sim.MakePretzels * PretzelCost) + (g.sim.MakeSigns * SignCost)
|
||||
totalExpenses := formatMoney(expenses)
|
||||
|
||||
profit := "$3.55"
|
||||
assets := "$6.60"
|
||||
profit := formatMoney(income - expenses)
|
||||
assets := formatMoney(g.sim.Money)
|
||||
|
||||
viewBytes := viewText[g.currentView]
|
||||
writeLines := g.viewTicks + 1
|
||||
|
@ -272,13 +311,13 @@ func (g *Game) refreshBuffer() error {
|
|||
var lines [][]byte
|
||||
switch g.currentView {
|
||||
case world.ViewStartDayProduction1:
|
||||
viewBytes = []byte(fmt.Sprintf(string(viewBytes), g.sim.Day))
|
||||
viewBytes = []byte(fmt.Sprintf(string(viewBytes), g.sim.Day, formatMoney(g.sim.Money)))
|
||||
lines = bytes.Split(viewBytes, []byte("\n"))
|
||||
case world.ViewStartDayProduction2:
|
||||
viewBytes = []byte(fmt.Sprintf(string(viewBytes), g.sim.Day, g.sim.MakePretzels))
|
||||
viewBytes = []byte(fmt.Sprintf(string(viewBytes), g.sim.Day, formatMoney(g.sim.Money), g.sim.MakePretzels))
|
||||
lines = bytes.Split(viewBytes, []byte("\n"))
|
||||
case world.ViewStartDayProduction3:
|
||||
viewBytes = []byte(fmt.Sprintf(string(viewBytes), g.sim.Day, g.sim.MakePretzels, g.sim.MakeSigns))
|
||||
viewBytes = []byte(fmt.Sprintf(string(viewBytes), g.sim.Day, formatMoney(g.sim.Money), g.sim.MakePretzels, g.sim.MakeSigns))
|
||||
lines = bytes.Split(viewBytes, []byte("\n"))
|
||||
case world.ViewFinancialReport:
|
||||
viewBytes = []byte(fmt.Sprintf(string(viewBytes), g.sim.Day, g.sim.PretzelsSold, formatCents(g.sim.PretzelPrice), totalIncome, g.sim.MakePretzels, g.sim.MakeSigns, totalExpenses, profit, assets))
|
||||
|
@ -331,6 +370,10 @@ func (g *Game) Update() error {
|
|||
}
|
||||
}
|
||||
|
||||
if g.gameOver {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle user input.
|
||||
if g.inputActive() {
|
||||
err := etk.Update()
|
||||
|
@ -362,10 +405,21 @@ func (g *Game) Update() error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
g.sim.DisasterAcknowledged = true
|
||||
|
||||
if g.sim.Money < PretzelCost {
|
||||
g.gameOver = true
|
||||
}
|
||||
}
|
||||
g.currentView++
|
||||
}
|
||||
g.viewTicks = 0
|
||||
|
||||
err := g.refreshBuffer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if g.currentView == world.ViewDay {
|
||||
|
@ -380,9 +434,13 @@ func (g *Game) Update() error {
|
|||
}
|
||||
}
|
||||
|
||||
if g.sim.DayFinished {
|
||||
if g.sim.DayFinished && (g.sim.Disaster == 0 || g.sim.DisasterAcknowledged) {
|
||||
g.currentView++
|
||||
g.viewTicks = 0
|
||||
|
||||
if g.sim.Money < PretzelCost {
|
||||
g.gameOver = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -409,3 +467,12 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
|||
func (g *Game) Exit() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func formatMoney(v int) string {
|
||||
var prefix string
|
||||
if v < 0 {
|
||||
prefix = "-"
|
||||
v *= -1
|
||||
}
|
||||
return fmt.Sprintf("%s$%d.%02d", prefix, v/100, v%100)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,69 @@ import (
|
|||
"math/rand"
|
||||
)
|
||||
|
||||
const (
|
||||
PretzelCost = 15
|
||||
SignCost = 125
|
||||
)
|
||||
|
||||
const (
|
||||
DisasterWeather = iota + 1
|
||||
DisasterParentsLostJobs
|
||||
DisasterMomMedical
|
||||
DisasterDadMedical
|
||||
DisasterPlayerMedical
|
||||
DisasterCompetitor
|
||||
DisasterChildLabor
|
||||
DisasterBankAccount
|
||||
DisasterCreditCard
|
||||
DisasterInflation
|
||||
DisasterDeflation
|
||||
DisasterStockMarket
|
||||
DisasterThugs
|
||||
DisasterDebt
|
||||
DisasterSued
|
||||
DisasterWar
|
||||
)
|
||||
|
||||
func disasterLabel(d int) string {
|
||||
switch d {
|
||||
case DisasterWeather:
|
||||
return "RAIN, RAIN, GO AWAY!"
|
||||
case DisasterParentsLostJobs:
|
||||
return "YOUR PARENTS LOST BOTH OF THEIR JOBS!"
|
||||
case DisasterMomMedical:
|
||||
return "YOUR MOM NEEDS EXPENSIVE MEDICAL WORK!"
|
||||
case DisasterDadMedical:
|
||||
return "YOUR DAD NEEDS EXPENSIVE MEDICAL WORK!"
|
||||
case DisasterPlayerMedical:
|
||||
return "YOU NEED EXPENSIVE MEDICAL WORK!"
|
||||
case DisasterCompetitor:
|
||||
return "A MEGA-CORP COMPETITOR OPENS NEARBY!"
|
||||
case DisasterChildLabor:
|
||||
return "YOUR ARE ACCUSED OF USING CHILD LABOR!"
|
||||
case DisasterBankAccount:
|
||||
return "YOUR BANK ACCOUNT IS FROZEN!"
|
||||
case DisasterCreditCard:
|
||||
return "YOUR CREDIT CARD IS FROZEN!"
|
||||
case DisasterInflation:
|
||||
return "MASSIVE INFLATION PLAGUES THE ECONOMY!"
|
||||
case DisasterDeflation:
|
||||
return "MASSIVE DEFLATION PLAGUES THE ECONOMY!"
|
||||
case DisasterStockMarket:
|
||||
return "THE STOCK MARKET HAS CRASHED!"
|
||||
case DisasterThugs:
|
||||
return "THUGS KNOCK OVER YOUR PRETZEL STAND!"
|
||||
case DisasterDebt:
|
||||
return "YOUR CAR IS REPOSSESSED DUE TO DEBT!"
|
||||
case DisasterSued:
|
||||
return "A CUSTOMER IS SUING YOU FOR FRAUD!"
|
||||
case DisasterWar:
|
||||
return "WAR HAS BEEN DECLARED!"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
type SimulationActor struct {
|
||||
X, Y int
|
||||
TargetX, TargetY int
|
||||
|
@ -26,11 +89,20 @@ type Simulation struct {
|
|||
MakeSigns int
|
||||
|
||||
PretzelPrice int // In cents.
|
||||
|
||||
NoSaleNoStock int
|
||||
|
||||
Disaster int
|
||||
DisasterDay int // Last disaster day.
|
||||
Disasters []int
|
||||
|
||||
DisasterAcknowledged bool
|
||||
}
|
||||
|
||||
func NewSimulation() *Simulation {
|
||||
return &Simulation{
|
||||
Day: 1,
|
||||
Day: 1,
|
||||
Money: 420,
|
||||
|
||||
MakePretzels: -1,
|
||||
MakeSigns: -1,
|
||||
|
@ -38,8 +110,39 @@ func NewSimulation() *Simulation {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Simulation) nextDisaster() int {
|
||||
if s.Day == 1 || len(s.Disasters) == 16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
if s.Day-s.DisasterDay < 3 && rand.Intn(2) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
DISASTERS:
|
||||
for {
|
||||
disaster := rand.Intn(16) + 1
|
||||
for _, d := range s.Disasters {
|
||||
if d == disaster {
|
||||
continue DISASTERS
|
||||
}
|
||||
}
|
||||
s.Disasters = append(s.Disasters, disaster)
|
||||
s.DisasterDay = s.Day
|
||||
return disaster
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Simulation) generateActors() {
|
||||
for i := 0; i < 7; i++ {
|
||||
if s.Disaster != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
numActors := 3 + rand.Intn(7)
|
||||
for i := 0; i < s.MakeSigns; i++ {
|
||||
numActors += 2 + rand.Intn(10)
|
||||
}
|
||||
for i := 0; i < numActors; i++ {
|
||||
x := -2 + rand.Intn(40)
|
||||
tx := 44 - x
|
||||
y := 18
|
||||
|
@ -62,6 +165,11 @@ func (s *Simulation) StartDay() {
|
|||
s.DayFinishedTicks = 25
|
||||
s.PretzelsSold = 0
|
||||
|
||||
s.NoSaleNoStock = 0
|
||||
|
||||
s.Disaster = s.nextDisaster()
|
||||
s.DisasterAcknowledged = false
|
||||
|
||||
s.generateActors()
|
||||
}
|
||||
|
||||
|
@ -72,6 +180,17 @@ func (s *Simulation) Tick() error {
|
|||
|
||||
standX, standY := 19, 10
|
||||
|
||||
spaceAvailable := func(x, y int, thisActor *SimulationActor) bool {
|
||||
for _, a := range s.Actors {
|
||||
if a == thisActor || a.Inactive {
|
||||
continue
|
||||
} else if a.X == x && a.Y == y && (a.TargetActive == thisActor.TargetActive || (x == standX && y == standY)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var active int
|
||||
var tx, ty int
|
||||
for _, a := range s.Actors {
|
||||
|
@ -90,16 +209,18 @@ func (s *Simulation) Tick() error {
|
|||
} else {
|
||||
tx, ty = standX, standY
|
||||
}
|
||||
moveX, moveY := 0, 0
|
||||
if a.X < tx {
|
||||
a.X++
|
||||
moveX++
|
||||
} else if a.X > tx {
|
||||
a.X--
|
||||
moveX--
|
||||
}
|
||||
if a.Y < ty {
|
||||
a.Y++
|
||||
moveY++
|
||||
} else if a.Y > ty {
|
||||
a.Y--
|
||||
moveY--
|
||||
}
|
||||
a.X, a.Y = a.X+moveX, a.Y+moveY
|
||||
|
||||
if a.TargetActive && (a.X < -2 || a.X > 40 || a.Y > 17) {
|
||||
a.Inactive = true
|
||||
|
@ -108,10 +229,15 @@ func (s *Simulation) Tick() error {
|
|||
|
||||
if a.X == tx && a.Y == ty {
|
||||
if !a.TargetActive {
|
||||
s.PretzelsSold++
|
||||
if s.MakePretzels > s.PretzelsSold {
|
||||
s.PretzelsSold++
|
||||
a.WaitTicks = 100 + rand.Intn(150)
|
||||
} else {
|
||||
s.NoSaleNoStock++
|
||||
a.WaitTicks = 25 + rand.Intn(25)
|
||||
}
|
||||
|
||||
a.TargetActive = true
|
||||
a.WaitTicks = 100 + rand.Intn(150)
|
||||
continue
|
||||
} else {
|
||||
a.Inactive = true
|
||||
|
@ -119,10 +245,17 @@ func (s *Simulation) Tick() error {
|
|||
}
|
||||
}
|
||||
a.WaitTicks = a.WalkTicks
|
||||
if !spaceAvailable(a.X+moveX, a.Y+moveY, a) || !spaceAvailable(a.X+moveX-1, a.Y+moveY, a) || !spaceAvailable(a.X+moveX+1, a.Y+moveY, a) {
|
||||
a.WaitTicks = 50 + rand.Intn(100)
|
||||
}
|
||||
}
|
||||
if active == 0 {
|
||||
s.DayFinishedTicks--
|
||||
if s.DayFinishedTicks == 0 {
|
||||
income := s.PretzelsSold * s.PretzelPrice
|
||||
expenses := (s.MakePretzels * PretzelCost) + (s.MakeSigns * SignCost)
|
||||
s.Money = s.Money + income - expenses
|
||||
|
||||
s.DayFinished = true
|
||||
}
|
||||
}
|
||||
|
|
12
game/view.go
12
game/view.go
|
@ -49,12 +49,20 @@ YOU WILL BEGIN WITH $4.20 CASH (ASSETS).
|
|||
// viewStartDayProduction1
|
||||
[]byte(` DAY %d
|
||||
|
||||
|
||||
ASSETS %s
|
||||
|
||||
|
||||
HOW MANY PRETZELS ($0.15 EACH) DO YOU
|
||||
WISH TO MAKE ?`),
|
||||
|
||||
// viewStartDayProduction2
|
||||
[]byte(` DAY %d
|
||||
|
||||
|
||||
ASSETS %s
|
||||
|
||||
|
||||
HOW MANY PRETZELS ($0.15 EACH) DO YOU
|
||||
WISH TO MAKE ?%d
|
||||
|
||||
|
@ -64,6 +72,10 @@ DO YOU WISH TO MAKE ?`),
|
|||
// viewStartDayProduction3
|
||||
[]byte(` DAY %d
|
||||
|
||||
|
||||
ASSETS %s
|
||||
|
||||
|
||||
HOW MANY PRETZELS ($0.15 EACH) DO YOU
|
||||
WISH TO MAKE ?%d
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
project_name: pretzel-tycoon
|
||||
|
||||
builds:
|
||||
-
|
||||
id: pretzel-tycoon
|
||||
goos:
|
||||
- js
|
||||
- linux
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- wasm
|
||||
archives:
|
||||
-
|
||||
id: pretzel-tycoon
|
||||
builds:
|
||||
- pretzel-tycoon
|
||||
replacements:
|
||||
386: i386
|
||||
format_overrides:
|
||||
- goos: js
|
||||
format: zip
|
||||
- goos: windows
|
||||
format: zip
|
||||
files:
|
||||
- ./*.md
|
||||
- LICENSE
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
Loading…
Reference in New Issue