diff --git a/bomb.go b/bomb.go index 734f508..2448820 100644 --- a/bomb.go +++ b/bomb.go @@ -14,15 +14,14 @@ type Bomb struct { fti int64 // frames to impact } -func NewBomb(from *Connection, to *System) *Bomb { - origin := from.System() - dist := origin.DistanceTo(to) +func NewBomb(conn *Connection, from, to *System) *Bomb { + dist := from.DistanceTo(to) fti := int64(dist / (options.lightSpeed * options.bombSpeed)) eta := time.Duration(fti) * time.Second / time.Duration(options.frameRate) - log_info("bomb from: %s to: %s ETA: %v", from.Name(), to.Label(), eta) + log_info("bomb from: %v to: %v ETA: %v", from, to, eta) return &Bomb{ - profile: from, - origin: origin, + profile: conn, + origin: from, target: to, fti: fti, start: time.Now(), @@ -38,10 +37,10 @@ func (b *Bomb) Tick(frame int64) { if b.fti <= 0 { b.target.Bombed(b.profile) b.done = true - log_info("bomb went off on %s", b.target.Label()) + log_info("bomb went off on %v", b.target) } } func (b *Bomb) String() string { - return fmt.Sprintf("[bomb from: %s to: %s lived: %s]", b.origin.Label(), b.target.Label(), time.Since(b.start)) + return fmt.Sprintf("[bomb from: %v to: %v lived: %s]", b.origin, b.target, time.Since(b.start)) } diff --git a/broadcast.go b/broadcast.go index 2d97f09..1358c73 100644 --- a/broadcast.go +++ b/broadcast.go @@ -28,7 +28,7 @@ func (b *broadcast) Tick(frame int64) { if b.dist < candidate.dist { break } - candidate.s.NotifyInhabitants("message received from system %s:\n\t%s\n", b.origin.Label(), b.message) + candidate.s.NotifyInhabitants("message received from system %v:\n\t%s\n", b.origin, b.message) } } @@ -37,5 +37,5 @@ func (b *broadcast) Dead() bool { } func (b *broadcast) String() string { - return fmt.Sprintf("[broadcast origin: %s message: %s]", b.origin.name, b.message) + return fmt.Sprintf("[broadcast origin: %v message: %s]", b.origin, b.message) } diff --git a/commands.go b/commands.go index cf5c26e..d997168 100644 --- a/commands.go +++ b/commands.go @@ -3,52 +3,98 @@ package main import ( "fmt" "sort" - "strconv" + // "strconv" "strings" ) var commandRegistry map[string]*Command type Command struct { - name string - help string - handler func(*Connection, ...string) - mobile bool - debug bool // marks command as a debug mode command + name string + help string + arity int + variadic bool + handler func(*Connection, ...string) + debug bool // marks command as a debug mode command } -var infoCommand = &Command{ - name: "info", - help: "gives you some info about your current position", - handler: func(conn *Connection, args ...string) { - conn.Printf("current planet: %s\n", conn.System().name) - conn.Printf("bombs: %d\n", conn.bombs) - conn.Printf("money: %d space duckets\n", conn.money) - }, +type CommandSuite interface { + GetCommand(name string) *Command + Commands() []Command } -var nearbyCommand = &Command{ - name: "nearby", - help: "list objects nearby", - handler: func(conn *Connection, args ...string) { - system := conn.System() - neighbors, err := system.Nearby(25) - if err != nil { - log_error("unable to get neighbors: %v", err) - return - } - conn.Printf("--------------------------------------------------------------------------------\n") - conn.Printf("%-4s %-20s %s\n", "id", "name", "distance") - conn.Printf("--------------------------------------------------------------------------------\n") - for _, neighbor := range neighbors { - other := index[neighbor.id] - conn.Printf("%-4d %-20s %v\n", other.id, other.name, neighbor.distance) +func (c Command) GetCommand(name string) *Command { + if name == c.name { + return &c + } + return nil +} + +func (c Command) Commands() []Command { + return []Command{c} +} + +type CommandSet []Command + +func (c CommandSet) GetCommand(name string) *Command { + for _, cmd := range c { + if cmd.name == name { + return &cmd } - conn.Printf("--------------------------------------------------------------------------------\n") - }, + } + return nil } -var helpCommand = &Command{ +func (c CommandSet) Commands() []Command { + return []Command(c) +} + +// var gotoCommand = &Command{ +// name: "goto", +// help: "travel between systems", +// arity: 1, +// handler: func(c *Connection, args ...string) { +// dest, err := GetSystem(args[0]) +// if err != nil { +// c.Printf("%v\n", err) +// break +// } +// return NewTravel(c, i.System, dest) +// }, +// } + +// var infoCommand = &Command{ +// name: "info", +// help: "gives you some info about your current position", +// handler: func(conn *Connection, args ...string) { +// conn.Printf("current planet: %v\n", conn.System()) +// conn.Printf("bombs: %d\n", conn.bombs) +// conn.Printf("money: %d space duckets\n", conn.money) +// }, +// } + +// var nearbyCommand = &Command{ +// name: "nearby", +// help: "list objects nearby", +// handler: func(conn *Connection, args ...string) { +// system := conn.System() +// neighbors, err := system.Nearby(25) +// if err != nil { +// log_error("unable to get neighbors: %v", err) +// return +// } +// conn.Printf("--------------------------------------------------------------------------------\n") +// conn.Printf("%-4s %-20s %s\n", "id", "name", "distance") +// conn.Printf("--------------------------------------------------------------------------------\n") +// for _, neighbor := range neighbors { +// other := index[neighbor.id] +// conn.Printf("%-4d %-20s %v\n", other.id, other.name, neighbor.distance) +// } +// conn.Printf("--------------------------------------------------------------------------------\n") +// }, +// } + +var helpCommand = Command{ name: "help", help: "helpful things to help you", handler: func(conn *Connection, args ...string) { @@ -93,7 +139,7 @@ systems. Star systems that are farther away take longer to communicate with. }, } -var commandsCommand = &Command{ +var commandsCommand = Command{ name: "commands", help: "gives you a handy list of commands", handler: func(conn *Connection, args ...string) { @@ -111,84 +157,84 @@ var commandsCommand = &Command{ }, } -var scanCommand = &Command{ - name: "scan", - help: "super duper scan", - handler: func(conn *Connection, args ...string) { - if !conn.CanScan() { - conn.Printf("scanners are still recharging. Can scan again in %v\n", conn.NextScan()) - return - } - currentGame.Register(NewScan(conn.System())) - conn.RecordScan() - }, -} - -var broadcastCommand = &Command{ - name: "broadcast", - help: "broadcast a message for all systems to hear", - handler: func(conn *Connection, args ...string) { - msg := strings.Join(args, " ") - system := conn.System() - b := NewBroadcast(system, msg) - log_info("player %s send broadcast from system %s: %v\n", conn.Name(), system.Label(), msg) - currentGame.Register(b) - }, -} - -var gotoCommand = &Command{ - name: "goto", - help: "moves to a different system, specified by either name or ID", - handler: func(conn *Connection, args ...string) { - dest_name := strings.Join(args, " ") - to, ok := nameIndex[dest_name] - if ok { - conn.TravelTo(to) - return - } - - id_n, err := strconv.Atoi(dest_name) - if err != nil { - conn.Printf(`hmm, I don't know a system by the name "%s", try something else`, dest_name) - return - } - - to, ok = index[id_n] - if !ok { - conn.Printf(`oh dear, there doesn't seem to be a system with id %d`, id_n) - return - } - conn.TravelTo(to) - }, -} - -var mineCommand = &Command{ - name: "mine", - help: "mines the current system for resources", - handler: func(conn *Connection, args ...string) { - conn.Mine() - }, -} - -var colonizeCommand = &Command{ - name: "colonize", - help: "establishes a mining colony on the current system", - handler: func(conn *Connection, arg ...string) { - system := conn.System() - if conn.money > 2000 { - conn.Withdraw(2000) - if system.colonizedBy != nil { - system.colonizedBy.Printf("your colony on %s has been stolen by %s\n", system.Label(), conn.Name()) - } - system.colonizedBy = conn - conn.Printf("set up a mining colony on %s\n", conn.System().name) - } else { - conn.Printf("not enough money! it costs 2000 duckets to start a mining colony\n") - } - }, -} - -var winCommand = &Command{ +// var scanCommand = &Command{ +// name: "scan", +// help: "super duper scan", +// handler: func(conn *Connection, args ...string) { +// if !conn.CanScan() { +// conn.Printf("scanners are still recharging. Can scan again in %v\n", conn.NextScan()) +// return +// } +// currentGame.Register(NewScan(conn.System())) +// conn.RecordScan() +// }, +// } + +// var broadcastCommand = &Command{ +// name: "broadcast", +// help: "broadcast a message for all systems to hear", +// handler: func(conn *Connection, args ...string) { +// msg := strings.Join(args, " ") +// system := conn.System() +// b := NewBroadcast(system, msg) +// log_info("player %s send broadcast from system %s: %v\n", conn.Name(), system.Label(), msg) +// currentGame.Register(b) +// }, +// } + +// var gotoCommand = &Command{ +// name: "goto", +// help: "moves to a different system, specified by either name or ID", +// handler: func(conn *Connection, args ...string) { +// dest_name := strings.Join(args, " ") +// to, ok := nameIndex[dest_name] +// if ok { +// conn.TravelTo(to) +// return +// } +// +// id_n, err := strconv.Atoi(dest_name) +// if err != nil { +// conn.Printf(`hmm, I don't know a system by the name "%s", try something else`, dest_name) +// return +// } +// +// to, ok = index[id_n] +// if !ok { +// conn.Printf(`oh dear, there doesn't seem to be a system with id %d`, id_n) +// return +// } +// conn.TravelTo(to) +// }, +// } + +// var mineCommand = &Command{ +// name: "mine", +// help: "mines the current system for resources", +// handler: func(conn *Connection, args ...string) { +// conn.Mine() +// }, +// } + +// var colonizeCommand = &Command{ +// name: "colonize", +// help: "establishes a mining colony on the current system", +// handler: func(conn *Connection, arg ...string) { +// system := conn.System() +// if conn.money > 2000 { +// conn.Withdraw(2000) +// if system.colonizedBy != nil { +// system.colonizedBy.Printf("your colony on %s has been stolen by %s\n", system.Label(), conn.Name()) +// } +// system.colonizedBy = conn +// conn.Printf("set up a mining colony on %s\n", conn.System().name) +// } else { +// conn.Printf("not enough money! it costs 2000 duckets to start a mining colony\n") +// } +// }, +// } + +var winCommand = Command{ name: "win", help: "win the game.", debug: true, @@ -197,49 +243,49 @@ var winCommand = &Command{ }, } -var bombCommand = &Command{ - name: "bomb", - help: "bombs a system, with a big space bomb", - handler: func(conn *Connection, args ...string) { - dest_name := strings.Join(args, " ") - to, ok := nameIndex[dest_name] - if ok { - conn.SendBomb(to) - return - } - - id_n, err := strconv.Atoi(dest_name) - if err != nil { - conn.Printf(`hmm, I don't know a system by the name "%s", try something else\n`, dest_name) - return - } - - to, ok = index[id_n] - if !ok { - conn.Printf(`oh dear, there doesn't seem to be a system with id %d\n`, id_n) - return - } - conn.SendBomb(to) - }, -} - -var mkBombCommand = &Command{ - name: "mkbomb", - help: "make a bomb. Costs 500 space duckets", - handler: func(conn *Connection, args ...string) { - if conn.money < 500 { - conn.Printf("not enough money! Bombs cost 500 space duckets to build, you only have %d in the bank.\n", conn.money) - return - } - conn.Withdraw(500) - conn.bombs += 1 - conn.Printf("built a bomb!\n") - conn.Printf("bombs: %d\n", conn.bombs) - conn.Printf("money: %d space duckets\n", conn.money) - }, -} - -var playersCommand = &Command{ +// var bombCommand = &Command{ +// name: "bomb", +// help: "bombs a system, with a big space bomb", +// handler: func(conn *Connection, args ...string) { +// dest_name := strings.Join(args, " ") +// to, ok := nameIndex[dest_name] +// if ok { +// conn.SendBomb(to) +// return +// } +// +// id_n, err := strconv.Atoi(dest_name) +// if err != nil { +// conn.Printf(`hmm, I don't know a system by the name "%s", try something else\n`, dest_name) +// return +// } +// +// to, ok = index[id_n] +// if !ok { +// conn.Printf(`oh dear, there doesn't seem to be a system with id %d\n`, id_n) +// return +// } +// conn.SendBomb(to) +// }, +// } + +// var mkBombCommand = &Command{ +// name: "mkbomb", +// help: "make a bomb. Costs 500 space duckets", +// handler: func(conn *Connection, args ...string) { +// if conn.money < 500 { +// conn.Printf("not enough money! Bombs cost 500 space duckets to build, you only have %d in the bank.\n", conn.money) +// return +// } +// conn.Withdraw(500) +// conn.bombs += 1 +// conn.Printf("built a bomb!\n") +// conn.Printf("bombs: %d\n", conn.bombs) +// conn.Printf("money: %d space duckets\n", conn.money) +// }, +// } + +var playersCommand = Command{ name: "players", help: "lists the connected players", handler: func(conn *Connection, args ...string) { @@ -249,69 +295,10 @@ var playersCommand = &Command{ }, } -var balCommand = &Command{ +var balCommand = Command{ name: "bal", help: "displays your current balance in space duckets", handler: func(conn *Connection, args ...string) { fmt.Fprintln(conn, conn.money) }, } - -func isCommand(name string) bool { - _, ok := commandRegistry[name] - return ok -} - -func runCommand(conn *Connection, name string, args ...string) { - cmd, ok := commandRegistry[name] - if !ok { - conn.Printf("no such command: %s\n", name) - return - } - - if conn.dead { - conn.Printf("you're dead.\n") - return - } - - if conn.InTransit() && !cmd.mobile { - conn.Printf("command %s can not be used while in transit\n", name) - return - } - - if conn.IsMining() { - conn.StopMining() - } - - cmd.handler(conn, args...) -} - -func registerCommand(c *Command) { - if c.debug { - if options.debug { - commandRegistry[c.name] = c - log_info("registered debug command: %s", c.name) - } - } else { - commandRegistry[c.name] = c - log_info("registered command: %s", c.name) - } -} - -func setupCommands() { - commandRegistry = make(map[string]*Command, 16) - registerCommand(bombCommand) - registerCommand(broadcastCommand) - registerCommand(colonizeCommand) - registerCommand(commandsCommand) - registerCommand(gotoCommand) - registerCommand(helpCommand) - registerCommand(infoCommand) - registerCommand(mineCommand) - registerCommand(mkBombCommand) - registerCommand(nearbyCommand) - registerCommand(playersCommand) - registerCommand(scanCommand) - registerCommand(winCommand) - registerCommand(balCommand) -} diff --git a/connection.go b/connection.go index 83b5a85..13e3592 100644 --- a/connection.go +++ b/connection.go @@ -10,37 +10,25 @@ import ( ) type Connection struct { - net.Conn *bufio.Reader - profile *Profile - location *System - dest *System - travelRemaining int64 - lastScan time.Time - lastBomb time.Time - kills int - dead bool - money int - colonies []*System - bombs int - state PlayerState // this is wrong... + net.Conn + ConnectionState + bombs int + colonies []*System + kills int + lastBomb time.Time + lastScan time.Time + money int + profile *Profile } -type PlayerState int - -const ( - idle PlayerState = iota - dead - inTransit - mining -) - func NewConnection(conn net.Conn) *Connection { c := &Connection{ Conn: conn, Reader: bufio.NewReader(conn), bombs: 1, } + c.SetState(SpawnRandomly()) currentGame.Join(c) return c } @@ -84,59 +72,59 @@ func (c *Connection) Dead() bool { } func (c *Connection) Tick(frame int64) { - // fuck - switch c.state { - case idle: - case dead: - case inTransit: - c.travelRemaining -= 1 - if c.travelRemaining == 0 { - c.land() - } - case mining: - sys := c.System() - if sys == nil { - log_error("a player is in the mining state with no system. what?") - break - } - if sys.money <= 0 { - c.Printf("system %s is all out of space duckets.\n", sys.Label()) - c.StopMining() - } else { - c.Deposit(1) - sys.money -= 1 - } - default: - log_error("connection %v has invalid state wtf", c) + if c.ConnectionState == nil { + log_error("connected client has nil state.") + c.Printf("somehow you have a nil state. I don't know what to do so I'm going to kick you off.") + c.Close() + return } + c.SetState(c.ConnectionState.Tick(c, frame)) } -func (c *Connection) TravelTo(dest *System) { - dist := c.System().DistanceTo(dest) - c.travelRemaining = int64(dist / (options.lightSpeed * options.playerSpeed)) - t := time.Duration(c.travelRemaining) * (time.Second / time.Duration(options.frameRate)) - c.Printf("traveling to: %s. ETA: %v\n", dest.Label(), t) - c.location.Leave(c) - c.location = nil - c.dest = dest - c.state = inTransit // fuck everything about this +func (c *Connection) RunCommand(name string, args ...string) { + defer func() { + if r := recover(); r != nil { + c.Printf("shit is *really* fucked up.") + log_error("recovered: %v", r) + } + }() + cmd := c.GetCommand(name) + if cmd == nil { + c.Printf("No such command: %v\n", name) + return + } + cmd.handler(c, args...) } -func (c *Connection) SendBomb(target *System) { - if c.bombs <= 0 { - fmt.Fprintln(c, "cannot send bomb: no bombs left") +func (c *Connection) SetState(s ConnectionState) { + if c.ConnectionState == s { return } - if time.Since(c.lastBomb) < 5*time.Second { - fmt.Fprintln(c, "cannod send bomb: bombs are reloading") - return + log_info("set state: %v", s) + if c.ConnectionState != nil { + log_info("exit state: %v", c.ConnectionState) + c.ConnectionState.Exit(c) } - c.bombs -= 1 - c.lastBomb = time.Now() - bomb := NewBomb(c, target) - currentGame.Register(bomb) - c.Printf("sending bomb to system %v\n", target.Label()) -} + log_info("enter state: %v", s) + s.Enter(c) + c.ConnectionState = s +} + +// func (c *Connection) SendBomb(target *System) { +// if c.bombs <= 0 { +// fmt.Fprintln(c, "cannot send bomb: no bombs left") +// return +// } +// if time.Since(c.lastBomb) < 5*time.Second { +// fmt.Fprintln(c, "cannod send bomb: bombs are reloading") +// return +// } +// c.bombs -= 1 +// c.lastBomb = time.Now() +// bomb := NewBomb(c, target) +// currentGame.Register(bomb) +// c.Printf("sending bomb to system %v\n", target) +// } func (c *Connection) ReadLines(out chan []string) { defer close(out) @@ -164,22 +152,6 @@ func (c *Connection) Printf(template string, args ...interface{}) (int, error) { return fmt.Fprintf(c, template, args...) } -func (c *Connection) land() { - c.Printf("you have arrived at %v\n", c.dest.Label()) - c.location = c.dest - c.location.Arrive(c) - c.dest = nil - c.state = idle -} - -func (c *Connection) SetSystem(s *System) { - c.location = s -} - -func (c *Connection) System() *System { - return c.location -} - func (c *Connection) Close() error { log_info("player disconnecting: %s", c.Name()) currentGame.Quit(c) @@ -196,10 +168,6 @@ func (c *Connection) Name() string { return c.profile.name } -func (c *Connection) InTransit() bool { - return c.location == nil -} - func (c *Connection) RecordScan() { fmt.Fprintln(c, "scanning known systems for signs of life") c.lastScan = time.Now() @@ -238,26 +206,6 @@ func (c *Connection) MadeKill(victim *Connection) { } } -func (c *Connection) Mine() { - switch c.state { - case idle: - c.Printf("now mining %s. %v space duckets remaining.\n", c.System().name, c.System().money) - fmt.Fprintln(c, "(press enter to stop mining)") - c.state = mining - default: - c.Printf("no\n") - } -} - -func (c *Connection) StopMining() { - c.Printf("done mining\n") - c.state = idle -} - -func (c *Connection) IsMining() bool { - return c.state == mining -} - func (c *Connection) Withdraw(n int) { c.money -= n } @@ -273,25 +221,30 @@ func (c *Connection) Win(method string) { currentGame.Win(c, method) } -func (c *Connection) Die() { - c.Printf("you were bombed. You will respawn in 1 minutes.\n") - c.dead = true - c.System().Leave(c) - time.AfterFunc(30*time.Second, func() { - c.Printf("respawn in 30 seconds.\n") - }) - time.AfterFunc(time.Minute, c.Respawn) +type ConnectionState interface { + CommandSuite + String() string + Enter(c *Connection) + Tick(c *Connection, frame int64) ConnectionState + Exit(c *Connection) } -func (c *Connection) Respawn() { - c.dead = false +// No-op enter struct, for composing connection states that have no interesitng +// Enter mechanic. +type NopEnter struct{} -WUT: - s, err := randomSystem() +func (n NopEnter) Enter(c *Connection) {} + +// No-op exit struct, for composing connection states that have no interesting +// Exit mechanic. +type NopExit struct{} + +func (n NopExit) Exit(c *Connection) {} + +func SpawnRandomly() ConnectionState { + sys, err := randomSystem() if err != nil { - log_error("error in respawn: %v", err) - goto WUT + return NewErrorState(fmt.Errorf("unable to create idle state: %v", err)) } - s.Arrive(c) - + return Idle(sys) } diff --git a/dead.go b/dead.go new file mode 100644 index 0000000..2e0fbe8 --- /dev/null +++ b/dead.go @@ -0,0 +1,31 @@ +package main + +import () + +type DeadState struct { + CommandSuite + start int64 +} + +func NewDeadState(died int64) ConnectionState { + return &DeadState{start: died} +} + +func (d *DeadState) Enter(c *Connection) { + c.Printf("You are dead.\n") +} + +func (d *DeadState) Tick(c *Connection, frame int64) ConnectionState { + if frame-d.start > options.respawnFrames { + return SpawnRandomly() + } + return d +} + +func (d *DeadState) Exit(c *Connection) { + c.Printf("You're alive again.\n") +} + +func (d *DeadState) String() string { + return "dead" +} diff --git a/errors.go b/errors.go index 33d9354..cf5a90f 100644 --- a/errors.go +++ b/errors.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "strings" ) @@ -9,6 +10,7 @@ const ( E_No_Data E_No_DB E_No_Port + E_Bad_Duration ) type errorGroup []error @@ -31,3 +33,32 @@ func (g *errorGroup) AddError(err error) { } *g = append(*g, err) } + +// ErrorState represents a valid client state indicating that the client has +// hit an error. On tick, the client will be disconnected. ErrorState is both +// a valid ConnectionState and a valid error value. +type ErrorState struct { + CommandSuite + error + NopEnter + NopExit +} + +func NewErrorState(e error) *ErrorState { + return &ErrorState{error: e} +} + +func (e *ErrorState) Tick(c *Connection, frame int64) ConnectionState { + c.Printf("something went wrong: %v", e.error) + log_error("player hit error: %v", e.error) + c.Close() + return nil +} + +func (e *ErrorState) String() string { + return fmt.Sprintf("error state: %v", e.error) +} + +func (e *ErrorState) RunCommand(c *Connection, name string, args ...string) ConnectionState { + return e +} diff --git a/idle.go b/idle.go new file mode 100644 index 0000000..c55802f --- /dev/null +++ b/idle.go @@ -0,0 +1,79 @@ +package main + +import ( + "fmt" +) + +var idleCommands = CommandSet{ + balCommand, + commandsCommand, + helpCommand, + playersCommand, +} + +type IdleState struct { + CommandSuite + *System +} + +func Idle(sys *System) ConnectionState { + return &IdleState{idleCommands, sys} +} + +func (i *IdleState) String() string { + return fmt.Sprintf("idle on %v", i.System) +} + +func (i *IdleState) Enter(c *Connection) { + c.Printf("You have landed on %v.\n", i.System) +} + +func (i *IdleState) Tick(c *Connection, frame int64) ConnectionState { + return i +} + +func (i *IdleState) Exit(c *Connection) { + c.Printf("Now leaving %v.\n", i.System) +} + +func (i *IdleState) travelTo(c *Connection, args ...string) { + dest, err := GetSystem(args[0]) + if err != nil { + c.Printf("%v\n", err) + return + } + c.SetState(NewTravel(c, i.System, dest)) +} + +func (i *IdleState) GetCommand(name string) *Command { + return idleCommands.GetCommand(name) +} + +// func (i *IdleState) RunCommand(c *Connection, name string, args ...string) ConnectionState { +// switch name { +// case "goto": +// dest, err := GetSystem(args[0]) +// if err != nil { +// c.Printf("%v\n", err) +// break +// } +// return NewTravel(c, i.System, dest) +// case "nearby": +// neighbors, err := i.Nearby(25) +// if err != nil { +// log_error("unable to get neighbors: %v", err) +// break +// } +// c.Printf("--------------------------------------------------------------------------------\n") +// c.Printf("%-4s %-20s %s\n", "id", "name", "distance") +// c.Printf("--------------------------------------------------------------------------------\n") +// for _, neighbor := range neighbors { +// other := index[neighbor.id] +// c.Printf("%-4d %-20s %v\n", other.id, other.name, neighbor.distance) +// } +// c.Printf("--------------------------------------------------------------------------------\n") +// default: +// c.Printf("No such command: %v\n", name) +// } +// return i +// } diff --git a/main.go b/main.go index 703d43d..d5eb7cc 100644 --- a/main.go +++ b/main.go @@ -11,15 +11,18 @@ import ( ) var options struct { - lightSpeed float64 - frameRate int - moneySigma float64 - moneyMean float64 - playerSpeed float64 - bombSpeed float64 - economic int - debug bool - speckPath string + bombSpeed float64 + debug bool + economic int + frameRate int + frameLength time.Duration + lightSpeed float64 + moneyMean float64 + moneySigma float64 + playerSpeed float64 + respawnTime time.Duration + respawnFrames int64 + speckPath string } var ( @@ -49,37 +52,30 @@ func handleConnection(conn *Connection) { defer conn.Close() conn.Login() - conn.Respawn() - c := make(chan []string) go conn.ReadLines(c) for parts := range c { - if isCommand(parts[0]) { - runCommand(conn, parts[0], parts[1:]...) - continue - } - - switch parts[0] { - case "quit": - return - default: - conn.Printf("hmm I'm not sure I know that one.\n") - } - + conn.RunCommand(parts[0], parts[1:]...) } } +// converts a duration in human time to a number of in-game frames +func durToFrames(dur time.Duration) int64 { + return int64(dur / options.frameLength) +} + func main() { flag.Parse() dbconnect() + options.frameLength = time.Second / time.Duration(options.frameRate) + options.respawnFrames = durToFrames(options.respawnTime) rand.Seed(time.Now().UnixNano()) info_log = log.New(os.Stdout, "[INFO] ", 0) error_log = log.New(os.Stderr, "[ERROR] ", 0) setupDb() - setupCommands() listener, err := net.Listen("tcp", ":9220") if err != nil { bail(E_No_Port, "unable to start server: %v", err) @@ -113,4 +109,5 @@ func init() { flag.Float64Var(&options.moneySigma, "money-sigma", 1500, "standard deviation in money per system") flag.BoolVar(&options.debug, "debug", false, "puts the game in debug mode") flag.StringVar(&options.speckPath, "speck-path", "/projects/exo/expl.speck", "path to exoplanet speck file") + flag.DurationVar(&options.respawnTime, "respawn-time", 60*time.Second, "time for player respawn") } diff --git a/mining.go b/mining.go new file mode 100644 index 0000000..500c18b --- /dev/null +++ b/mining.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" +) + +type MiningState struct { + CommandSuite + sys *System + mined int +} + +func Mine(sys *System) ConnectionState { + return &MiningState{sys: sys} +} + +func (m *MiningState) Enter(c *Connection) { + c.Printf("Mining %v. %v space duckets remaining.\n", m.sys, m.sys.money) +} + +func (m *MiningState) Tick(c *Connection, frame int64) ConnectionState { + if m.sys.money <= 0 { + c.Printf("system %s is all out of space duckets.\n", m.sys) + return Idle(m.sys) + } else { + c.Deposit(1) + m.mined += 1 + m.sys.money -= 1 + return m + } +} + +func (m *MiningState) Exit(c *Connection) { + if m.sys.money == 0 { + c.Printf("Done mining %v. Mined %v space duckets total. %v space duckets remain on %v, and it can be mined again.", m.sys, m.mined, m.sys.money, m.sys) + } else { + c.Printf("Done mining %v. Mined %v space duckets total. No space duckets remain on %v, and it can't be mined again.", m.sys, m.mined, m.sys) + } +} + +func (m *MiningState) String() string { + return fmt.Sprintf("mining %v", m.sys) +} diff --git a/scan.go b/scan.go index 61d388c..46413bd 100644 --- a/scan.go +++ b/scan.go @@ -70,7 +70,7 @@ func (s *scan) hits() { } func (s *scan) hitSystem(sys *System, dist float64) scanResult { - sys.NotifyInhabitants("scan detected from %s\n", s.origin.Label()) + sys.NotifyInhabitants("scan detected from %v\n", s.origin) r := scanResult{ system: sys, colonizedBy: sys.colonizedBy, @@ -95,7 +95,7 @@ func (s *scan) echos() { if res.Empty() { continue } - s.origin.NotifyInhabitants("results from scan of %s:\n", res.system.Label()) + s.origin.NotifyInhabitants("results from scan of %v:\n", res.system) s.origin.NotifyInhabitants("\tdistance: %v\n", s.origin.DistanceTo(res.system)) inhabitants := res.playerNames() if inhabitants != nil { diff --git a/system.go b/system.go index 9584874..53c558e 100644 --- a/system.go +++ b/system.go @@ -5,6 +5,7 @@ import ( "fmt" "math" "math/rand" + "strconv" "time" ) @@ -24,6 +25,22 @@ type System struct { money int64 } +func GetSystem(id string) (*System, error) { + idNum, err := strconv.Atoi(id) + if err == nil { + sys, ok := index[idNum] + if !ok { + return nil, fmt.Errorf("No such system: %v", idNum) + } + return sys, nil + } + sys, ok := nameIndex[id] + if !ok { + return nil, fmt.Errorf("No such system: %v", id) + } + return sys, nil +} + func (s *System) Tick(frame int64) { if s.colonizedBy != nil { s.colonizedBy.Deposit(1) @@ -40,22 +57,22 @@ func (s *System) Reset() { } func (s *System) Arrive(conn *Connection) { - conn.SetSystem(s) - log_info("player %s has arrived at system %s", conn.Name(), s.Label()) + // conn.SetSystem(s) + log_info("player %s has arrived at system %v", conn.Name(), s) if s.players == nil { s.players = make(map[*Connection]bool, 8) } s.players[conn] = true if s.planets == 1 { - conn.Printf("you are in the system %s. There is %d planet here.\n", s.Label(), s.planets) + conn.Printf("you are in the system %v. There is %d planet here.\n", s, s.planets) } else { - conn.Printf("you are in the system %s. There are %d planets here.\n", s.Label(), s.planets) + conn.Printf("you are in the system %v. There are %d planets here.\n", s, s.planets) } } func (s *System) Leave(p *Connection) { delete(s.players, p) - p.location = nil + // p.location = nil } func (s *System) NotifyInhabitants(template string, args ...interface{}) { @@ -146,7 +163,7 @@ func (s *System) Distances() []Ray { func (s *System) Bombed(bomber *Connection) { s.EachConn(func(conn *Connection) { - conn.Die() + // conn.Die() bomber.MadeKill(conn) }) if s.colonizedBy != nil { @@ -174,15 +191,10 @@ func bombNotice(to_id, from_id int) { }) } -// for players to read. -func (s System) Label() string { +func (s System) String() string { return fmt.Sprintf("%s (id: %v)", s.name, s.id) } -func (e System) String() string { - return fmt.Sprintf("", e.name, e.x, e.y, e.z, e.planets) -} - type Neighbor struct { id int distance float64 @@ -247,7 +259,7 @@ func indexSystems() map[int]*System { index[p.id] = &p nameIndex[p.name] = &p p.money = int64(rand.NormFloat64()*options.moneySigma + options.moneyMean) - log_info("seeded system %s with %v monies", p.Label(), p.money) + log_info("seeded system %v with %v monies", p, p.money) } return index } @@ -257,8 +269,7 @@ func randomSystem() (*System, error) { if n == 0 { return nil, fmt.Errorf("no planets are known to exist") } - pick := rand.Intn(n) - planet := index[pick] - return planet, nil + sys := index[pick] + return sys, nil } diff --git a/travel.go b/travel.go new file mode 100644 index 0000000..47325e6 --- /dev/null +++ b/travel.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" +) + +type TravelState struct { + CommandSuite + start *System + dest *System + travelled float64 + dist float64 +} + +func NewTravel(c *Connection, start, dest *System) ConnectionState { + return &TravelState{ + start: start, + dest: dest, + dist: start.DistanceTo(dest), + } +} + +func (t *TravelState) Enter(c *Connection) { + c.Printf("Leaving %v, bound for %v.\n", t.start, t.dest) +} + +func (t *TravelState) Tick(c *Connection, frame int64) ConnectionState { + t.travelled += options.playerSpeed * options.lightSpeed + if t.travelled >= t.dist { + return Idle(t.dest) + } + return t +} + +func (t *TravelState) Exit(c *Connection) { + c.Printf("You have arrived at %v.\n", t.dest) +} + +func (t *TravelState) String() string { + return fmt.Sprintf("Traveling from %v to %v", t.start, t.dest) +}