Skip to content

Instantly share code, notes, and snippets.

@jordanorelli
Created December 19, 2014 22:26
Show Gist options
  • Save jordanorelli/768a30f723656f971af3 to your computer and use it in GitHub Desktop.
Save jordanorelli/768a30f723656f971af3 to your computer and use it in GitHub Desktop.
package main
import (
"fmt"
"time"
)
type Bomb struct {
profile *Connection
origin *System
target *System
start time.Time
done bool
fti int64 // frames to impact
}
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: %v to: %v ETA: %v", from, to, eta)
return &Bomb{
profile: conn,
origin: from,
target: to,
fti: fti,
start: time.Now(),
}
}
func (b *Bomb) Dead() bool {
return b.done
}
func (b *Bomb) Tick(frame int64) {
b.fti -= 1
if b.fti <= 0 {
b.target.Bombed(b.profile, frame)
b.done = true
log_info("bomb went off on %v", b.target)
}
}
func (b *Bomb) String() string {
return fmt.Sprintf("[bomb from: %v to: %v lived: %s]", b.origin, b.target, time.Since(b.start))
}
type MakeBombState struct {
CommandSuite
*System
start int64
}
func MakeBomb(s *System) ConnectionState {
m := &MakeBombState{System: s}
m.CommandSuite = CommandSet{
balCommand,
BroadcastCommand(s),
helpCommand,
NearbyCommand(s),
playersCommand,
}
return m
}
func (m *MakeBombState) Enter(c *Connection) {
c.Printf("Making a bomb...\n")
c.money -= options.bombCost
}
func (m *MakeBombState) Tick(c *Connection, frame int64) ConnectionState {
if m.start == 0 {
m.start = frame
}
if framesToDur(frame-m.start) >= options.makeBombTime {
return Idle(m.System)
}
return m
}
func (m *MakeBombState) Exit(c *Connection) {
c.bombs += 1
c.Printf("Done! You now have %v bombs.\n", c.bombs)
}
package main
import (
"fmt"
"time"
)
type broadcast struct {
start time.Time
origin *System
dist float64
nextHitIndex int
message string
}
func NewBroadcast(from *System, template string, args ...interface{}) *broadcast {
return &broadcast{
start: time.Now(),
origin: from,
message: fmt.Sprintf(template, args...),
}
}
func (b *broadcast) Tick(frame int64) {
b.dist += options.lightSpeed
for ; b.nextHitIndex < len(b.origin.Distances()); b.nextHitIndex += 1 {
candidate := b.origin.Distances()[b.nextHitIndex]
if b.dist < candidate.dist {
break
}
candidate.s.NotifyInhabitants("message received from system %v:\n\t%s\n", b.origin, b.message)
}
}
func (b *broadcast) Dead() bool {
return b.dist > b.origin.Distances()[len(b.origin.Distances())-1].dist
}
func (b *broadcast) String() string {
return fmt.Sprintf("[broadcast origin: %v message: %s]", b.origin, b.message)
}
package main
import ()
func MakeColony(c *Connection, sys *System) {
if c.money < options.colonyCost {
c.Printf("Not enough money! Colonies cost %v but you only have %v space duckets. Mine more space duckets!\n", options.colonyCost, c.money)
return
}
if sys.colonizedBy == c {
c.Printf("You've already colonized this system.\n")
return
}
c.money -= options.colonyCost
m := &MakeColonyState{
System: sys,
CommandSuite: CommandSet{
balCommand,
BroadcastCommand(sys),
helpCommand,
NearbyCommand(sys),
playersCommand,
},
}
c.SetState(m)
}
type MakeColonyState struct {
CommandSuite
*System
start int64
}
func (m *MakeColonyState) Enter(c *Connection) {
c.Printf("Making colony on %v...\n", m.System)
}
func (m *MakeColonyState) Tick(c *Connection, frame int64) ConnectionState {
if m.start == 0 {
m.start = frame
}
if framesToDur(frame-m.start) >= options.makeColonyTime {
return Idle(m.System)
}
return m
}
func (m *MakeColonyState) Exit(c *Connection) {
m.System.colonizedBy = c
c.Printf("Established colony on %v.\n", m.System)
}
package main
import (
"fmt"
"sort"
// "strconv"
"strings"
)
var commandRegistry map[string]*Command
type Command struct {
name string
help string
arity int
variadic bool
handler func(*Connection, ...string)
debug bool // marks command as a debug mode command
}
type CommandSuite interface {
GetCommand(name string) *Command
Commands() []Command
}
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
}
}
return nil
}
func (c CommandSet) Commands() []Command {
return []Command(c)
}
var helpCommand = Command{
name: "help",
help: "helpful things to help you",
handler: func(conn *Connection, args ...string) {
msg := `
Star Dragons is a stupid name, but it's the name that Brian suggested. It has
nothing to do with Dragons.
Anyway, Star Dragons is a game of cunning text-based, real-time strategy. You
play as some kind of space-faring entity, faring space in your inspecific
space-faring vessel. If you want a big one, it's big; if you want a small one,
it's small. If you want a pink one, it's pink, if you want a black one, it's
black. And so on, and so forth. It is the space craft of your dreams. Or
perhaps you are one of those insect-like alien races and you play as the queen.
Yeah, that's the ticket! You're the biggest baddest queen bug in space.
In Star Dragons, you issue your spacecraft (which is *not* called a Dragon)
textual commands to control it. The objective of the game is to be the first
person or alien or bug or magical space ponycorn to eradicate three enemy
species. Right now that is the only win condition.
All of the systems present in Star Dragons are named and positioned after known
exoplanet systems. When attempting to communicate from one star system to
another, it takes time for the light of your message to reach the other star
systems. Star systems that are farther away take longer to communicate with.
`
msg = strings.TrimSpace(msg)
fmt.Fprintln(conn, msg)
if len(args) == 0 {
fmt.Fprintln(conn, `use the "commands" command for a list of commands.`)
fmt.Fprintln(conn, `use "help [command-name]" to get info for a specific command.`)
return
}
for _, cmdName := range args {
cmd, ok := commandRegistry[cmdName]
if !ok {
conn.Printf("no such command: %v\n", cmdName)
continue
}
conn.Printf("%v: %v\n", cmdName, cmd.help)
}
},
}
var commandsCommand = Command{
name: "commands",
help: "gives you a handy list of commands",
handler: func(conn *Connection, args ...string) {
names := make([]string, 0, len(commandRegistry))
for name, _ := range commandRegistry {
names = append(names, name)
}
sort.Strings(names)
fmt.Fprintln(conn, "--------------------------------------------------------------------------------")
for _, name := range names {
cmd := commandRegistry[name]
conn.Printf("%-16s %s\n", name, cmd.help)
}
fmt.Fprintln(conn, "--------------------------------------------------------------------------------")
},
}
func BroadcastCommand(sys *System) Command {
return Command{
name: "broadcast",
help: "broadcast a message for all systems to hear",
handler: func(c *Connection, args ...string) {
msg := strings.Join(args, " ")
b := NewBroadcast(sys, msg)
log_info("player %s send broadcast from system %v: %v\n", c.Name(), sys, msg)
currentGame.Register(b)
},
}
}
func NearbyCommand(sys *System) Command {
handler := func(c *Connection, args ...string) {
neighbors, err := sys.Nearby(25)
if err != nil {
log_error("unable to get neighbors: %v", err)
return
}
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 %-5.6v\n", other.id, other.name, neighbor.distance)
}
c.Printf("--------------------------------------------------------------------------------\n")
}
return Command{
name: "nearby",
help: "list nearby star systems",
arity: 0,
handler: handler,
}
}
var winCommand = Command{
name: "win",
help: "win the game.",
debug: true,
handler: func(conn *Connection, args ...string) {
conn.Win("win-command")
},
}
var playersCommand = Command{
name: "players",
help: "lists the connected players",
handler: func(conn *Connection, args ...string) {
for other, _ := range currentGame.connections {
conn.Printf("%v\n", other.Name())
}
},
}
var balCommand = Command{
name: "bal",
help: "displays your current balance in space duckets",
handler: func(conn *Connection, args ...string) {
fmt.Fprintln(conn, conn.money)
},
}
package main
import (
"bufio"
"fmt"
"io"
"net"
"sort"
"strings"
"time"
)
type Connection struct {
*bufio.Reader
net.Conn
ConnectionState
bombs int
colonies []*System
kills int
lastBomb time.Time
lastScan time.Time
money int
profile *Profile
}
func NewConnection(conn net.Conn) *Connection {
c := &Connection{
Conn: conn,
Reader: bufio.NewReader(conn),
bombs: options.startBombs,
money: options.startMoney,
}
c.SetState(SpawnRandomly())
currentGame.Join(c)
return c
}
func (c *Connection) Login() {
for {
c.Printf("what is your name, adventurer?\n")
nick, err := c.ReadString('\n')
if err == nil {
nick = strings.TrimSpace(nick)
} else {
log_error("player failed to connect: %v", err)
return
}
if !ValidName(nick) {
c.Printf("that name is illegal.\n")
continue
}
log_info("player connected: %v", nick)
profile, err := loadProfile(nick)
if err != nil {
log_error("could not read profile: %v", err)
profile = &Profile{nick: nick}
if err := profile.Create(); err != nil {
log_error("unable to create profile record: %v", err)
}
c.Printf("you look new around these parts, %s.\n", profile.nick)
c.Printf(`if you'd like a description of how to play, type the "help" command\n`)
c.profile = profile
} else {
c.profile = profile
c.Printf("welcome back, %s.\n", profile.nick)
}
break
}
currentGame.Register(c)
}
func (c *Connection) Dead() bool {
return false
}
func (c *Connection) Tick(frame int64) {
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) RunCommand(name string, args ...string) {
defer func() {
if r := recover(); r != nil {
c.Printf("something is broken. Log this as a ticket!\n")
c.Printf("recovered: %v\n", r)
log_error("recovered: %v", r)
}
}()
switch name {
case "commands":
c.Line()
commands := c.Commands()
names := make([]string, len(commands))
for i := range commands {
names[i] = commands[i].name
}
sort.Strings(names)
for _, name := range names {
cmd := c.GetCommand(name)
c.Printf("%-20s%s\n", name, cmd.help)
}
c.Line()
return
}
cmd := c.GetCommand(name)
if cmd == nil {
c.Printf("No such command: %v\n", name)
return
}
cmd.handler(c, args...)
}
func (c *Connection) SetState(s ConnectionState) {
if c.ConnectionState == s {
return
}
log_info("set state: %v", s)
if c.ConnectionState != nil {
log_info("exit state: %v", c.ConnectionState)
c.ConnectionState.Exit(c)
}
log_info("enter state: %v", s)
s.Enter(c)
c.ConnectionState = s
}
func (c *Connection) ReadLines(out chan []string) {
defer close(out)
for {
line, err := c.ReadString('\n')
switch err {
case io.EOF:
return
case nil:
break
default:
log_error("unable to read line on connection: %v", err)
return
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
out <- strings.Split(line, " ")
}
}
func (c *Connection) Line() {
c.Printf("--------------------------------------------------------------------------------\n")
}
func (c *Connection) Printf(template string, args ...interface{}) (int, error) {
return fmt.Fprintf(c, template, args...)
}
func (c *Connection) Close() error {
log_info("player disconnecting: %s", c.Name())
currentGame.Quit(c)
if c.Conn != nil {
return c.Conn.Close()
}
return nil
}
func (c *Connection) Name() string {
if c.profile == nil {
return ""
}
return c.profile.nick
}
func (c *Connection) RecordScan() {
c.Printf("scanning known systems for signs of life\n")
c.lastScan = time.Now()
time.AfterFunc(options.scanTime, func() {
c.Printf("scanner ready\n")
})
}
func (c *Connection) RecordBomb() {
c.lastBomb = time.Now()
time.AfterFunc(15*time.Second, func() {
fmt.Fprintln(c, "bomb arsenal reloaded")
})
}
func (c *Connection) CanScan() bool {
return time.Since(c.lastScan) > options.scanTime
}
func (c *Connection) NextScan() time.Duration {
return -time.Since(c.lastScan.Add(options.scanTime))
}
func (c *Connection) NextBomb() time.Duration {
return -time.Since(c.lastBomb.Add(15 * time.Second))
}
func (c *Connection) MadeKill(victim *Connection) {
if c == victim {
log_info("player %s commited suicide.", c.Name())
return
}
c.kills += 1
if c.kills == 3 {
c.Win("military")
}
}
func (c *Connection) Withdraw(n int) {
c.money -= n
}
func (c *Connection) Deposit(n int) {
c.money += n
if c.money >= options.economic {
c.Win("economic")
}
}
func (c *Connection) Win(method string) {
currentGame.Win(c, method)
}
func (c *Connection) Die(frame int64) {
c.SetState(NewDeadState(frame))
}
type ConnectionState interface {
CommandSuite
String() string
Enter(c *Connection)
Tick(c *Connection, frame int64) ConnectionState
Exit(c *Connection)
}
// No-op enter struct, for composing connection states that have no interesitng
// Enter mechanic.
type NopEnter struct{}
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 {
return NewErrorState(fmt.Errorf("unable to create idle state: %v", err))
}
return Idle(sys)
}
package main
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
"os"
)
var (
db *sql.DB
)
func dbconnect() {
var err error
db, err = sql.Open("sqlite3", "./exo.db")
if err != nil {
bail(E_No_DB, "couldn't connect to db: %v", err)
}
}
func planetsTable() {
stmnt := `create table if not exists planets (
id integer not null primary key autoincrement,
name text,
x integer,
y integer,
z integer,
planets integer
);`
if _, err := db.Exec(stmnt); err != nil {
log_error("couldn't create planets table: %v", err)
}
}
func planetsData() {
n, err := countSystems()
if err != nil {
log_error("couldn't count planets: %v", err)
return
}
if n == 0 {
fi, err := os.Open(options.speckPath)
if err != nil {
bail(E_No_Data, "unable to open data path: %v", err)
}
c := make(chan System)
go speckStream(fi, c)
for planet := range c {
planet.Store(db)
}
}
indexSystems()
}
func edgesTable() {
stmnt := `create table if not exists edges (
id_1 integer,
id_2 integer,
distance real
);`
if _, err := db.Exec(stmnt); err != nil {
log_error("couldn't create distance table: %v", err)
}
}
func setupDb() {
planetsTable()
planetsData()
edgesTable()
profilesTable()
gamesTable()
fillEdges()
}
func fillEdges() {
row := db.QueryRow(`select count(*) from edges;`)
var n int
if err := row.Scan(&n); err != nil {
log_error("couldn't get number of edges: %v", err)
return
}
if n > 0 {
return
}
for i := 0; i < len(index); i++ {
for j := 0; j < len(index); j++ {
if i == j {
continue
}
if index[i] == nil {
log_error("wtf there's nil shit in here for id %d", i)
continue
}
if index[j] == nil {
log_error("wtf there's nil shit in here 2 for id %d", j)
continue
}
dist := index[i].DistanceTo(index[j])
log_info("distance from %s to %s: %v", index[i].name, index[j].name, dist)
_, err := db.Exec(`
insert into edges
(id_1, id_2, distance)
values
(?, ?, ?)
;`, i, j, dist)
if err != nil {
log_error("unable to write edge to db: %v", err)
}
}
}
}
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"
}
package main
import (
"fmt"
"strings"
)
const (
E_Ok int = iota
E_No_Data
E_No_DB
E_No_Port
E_Bad_Duration
E_Bad_HTTP
E_BadPrivateKey
E_ListenError
)
type errorGroup []error
func (e errorGroup) Error() string {
messages := make([]string, 0, len(e))
for i, _ := range e {
messages[i] = e[i].Error()
}
return strings.Join(messages, " && ")
}
func (g *errorGroup) AddError(err error) {
if err == nil {
return
}
if g == nil {
panic("fart")
*g = make([]error, 0, 4)
}
*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
}
package main
import (
"fmt"
"time"
)
type Game struct {
id Id
start time.Time
end time.Time
done chan interface{}
winner string
winMethod string
connections map[*Connection]bool
frame int64
elems map[GameElement]bool
}
func gamesTable() {
stmnt := `create table if not exists games (
id text not null,
start text not null,
end text,
winner text,
win_method text
);`
if _, err := db.Exec(stmnt); err != nil {
log_error("couldn't create games table: %v", err)
}
}
func NewGame() *Game {
game := &Game{
id: NewId(),
start: time.Now(),
done: make(chan interface{}),
connections: make(map[*Connection]bool, 32),
elems: make(map[GameElement]bool, 32),
}
if err := game.Create(); err != nil {
log_error("unable to create game: %v", err)
}
for _, system := range index {
game.Register(system)
}
if currentGame != nil {
log_info("passing %d connections...", len(currentGame.connections))
for conn, _ := range currentGame.connections {
log_info("moving player %s to new game", conn.Name())
currentGame.Quit(conn)
game.Join(conn)
}
}
return game
}
func (g *Game) Create() error {
_, err := db.Exec(`
insert into games
(id, start)
values
(?, ?)
;`, g.id.String(), g.start)
if err != nil {
return fmt.Errorf("error writing sqlite insert statement to create game: %v")
}
return nil
}
func (g *Game) Store() error {
_, err := db.Exec(`
update games
set end = ?, winner = ?, win_method = ?
where id = ?
;`, g.end, g.winner, g.winMethod, g.id)
return err
}
func (g *Game) Join(conn *Connection) {
g.connections[conn] = true
}
func (g *Game) Quit(conn *Connection) {
delete(g.connections, conn)
}
func (g *Game) Win(winner *Connection, method string) {
defer close(g.done)
g.end = time.Now()
g.winner = winner.Name()
g.winMethod = method
g.Store()
log_info("player %s has won by %s victory", winner.Name(), method)
for conn, _ := range g.connections {
conn.Printf("player %s has won by %s victory.\n", winner.Name(), method)
}
}
func (g *Game) Reset() {
connections := g.connections
fresh := NewGame()
*g = *fresh
g.connections = connections
}
func (g *Game) Run() {
ticker := time.Tick(time.Second / time.Duration(options.frameRate))
for {
select {
case <-ticker:
g.tick()
case <-g.done:
for conn, _ := range g.connections {
conn.Close()
}
return
}
}
}
func (g *Game) Register(elem GameElement) {
g.elems[elem] = true
}
func (g *Game) tick() {
g.frame += 1
for elem := range g.elems {
elem.Tick(g.frame)
}
for elem := range g.elems {
if elem.Dead() {
log_info("delete game object: %v", elem)
delete(g.elems, elem)
}
}
}
type GameElement interface {
Tick(frame int64)
Dead() bool
}
package main
import (
"crypto/md5"
"os"
)
var global struct {
hostname string
machineId []byte
pid int
idCounter uint32
}
func init() {
global.pid = os.Getpid()
var err error
global.hostname, err = os.Hostname()
if err != nil {
panic("failed to get hostname: " + err.Error())
}
hw := md5.New()
if _, err := hw.Write([]byte(global.hostname)); err != nil {
panic("unable to md5 hostname: " + err.Error())
}
global.machineId = make([]byte, 3)
copy(global.machineId, hw.Sum(nil))
}
package main
import (
"bufio"
"code.google.com/p/go.crypto/ssh"
"fmt"
"io"
"net/http"
"os"
"regexp"
"strings"
)
var (
templates = new(templateCache)
httpRoot = &rootHandler{
routes: make(map[*regexp.Regexp]http.Handler, 16),
handlers: map[string]http.Handler{
"Home": http.HandlerFunc(homeHandler),
"Register": http.HandlerFunc(registerHandler),
},
}
)
type rootHandler struct {
handlers map[string]http.Handler
routes map[*regexp.Regexp]http.Handler
}
func writeHTTPError(w http.ResponseWriter, code int, template string, args ...interface{}) {
w.WriteHeader(code)
fmt.Fprintf(w, template, args...)
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
tpl, err := templates.Get("home.html.tpl")
if err != nil {
writeHTTPError(w, 505, "server error: %v", err)
}
tpl.Execute(w, nil)
}
func registerHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "POST":
if err := r.ParseForm(); err != nil {
log_error("unable to parse form: %v", err)
}
email := r.FormValue("email")
nick := r.FormValue("nick")
publicKey := r.FormValue("public-key")
log_info("raw email: %s", email)
log_info("raw public key: %s", publicKey)
key, comment, options, rest, err := ssh.ParseAuthorizedKey([]byte(publicKey))
if err != nil {
writeHTTPError(w, http.StatusNotAcceptable, "bad public key: %v", err)
return
}
log_info("email: %s key: %v comment: %v options: %v rest: %s", email, key, comment, options, rest)
profile := &Profile{
nick: nick,
email: email,
key: publicKey,
}
log_info("creating profile %v", profile)
if err := profile.Create(); err != nil {
writeHTTPError(w, http.StatusNotAcceptable, "unable to create profile: %v", err)
return
}
case "GET":
tpl, err := templates.Get("register.html.tpl")
if err != nil {
writeHTTPError(w, 505, "server error: %v", err)
}
tpl.Execute(w, nil)
}
}
func (h *rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log_info("%s %s", r.Method, r.URL.String())
handler := h.Route(r)
handler.ServeHTTP(w, r)
}
func (h *rootHandler) NotFound(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintln(w, "not found, sorry")
}
func (h *rootHandler) addRoute(route string, handler http.Handler) error {
r, err := regexp.Compile(route)
if err != nil {
return fmt.Errorf("unable to parse path: %v", err)
}
if h.routes == nil {
h.routes = make(map[*regexp.Regexp]http.Handler, 16)
}
h.routes[r] = handler
return nil
}
// registers a handler in the list of known http handlers
func (h *rootHandler) RegisterHandler(name string, handler http.Handler) error {
if h.handlers == nil {
h.handlers = make(map[string]http.Handler, 16)
}
h.handlers[name] = handler
return nil
}
func (h *rootHandler) Route(r *http.Request) http.Handler {
for route, handler := range h.routes {
if route.MatchString(r.URL.Path) {
log_info("path %s matches route %v", r.URL.Path, route)
return handler
}
}
return http.HandlerFunc(h.NotFound)
}
func (h *rootHandler) ReadRoutesFile(path string) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("unable to read routes file at path %s: %v", path, err)
}
defer f.Close()
return h.ReadRoutes(f)
}
func (h *rootHandler) ReadRoutes(f io.Reader) error {
r := bufio.NewReader(f)
for lineno, done := 1, false; !done; lineno++ {
line, err := r.ReadString('\n')
switch err {
case io.EOF:
done = true
case nil:
if err := h.parseRouteLine(lineno, line); err != nil {
return fmt.Errorf("unable to parse routes file: %v", err)
}
default:
return fmt.Errorf("unable to parse routes file: %v", err)
}
}
return nil
}
func (h *rootHandler) parseRouteLine(lineno int, line string) error {
// comment lines
if match, err := regexp.MatchString(`^\s+\#`, line); match {
return nil
} else if err != nil {
return fmt.Errorf("parse routes is fucked: %v", err)
}
// blank lines
if match, err := regexp.MatchString(`^\s+$`, line); match {
return nil
} else if err != nil {
return fmt.Errorf("parse routes is fucked: %v", err)
}
parts := strings.Split(line, " ")
if len(parts) != 2 {
return fmt.Errorf("routes parse error on line %d: expected exactly %d line parts, saw %d", lineno, 2, len(parts))
}
path, name := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
handler, ok := h.handlers[name]
if !ok {
log_info("%v", h.handlers)
return fmt.Errorf("routes parse error on line %d: no such handler: %s", lineno, name)
}
if err := h.addRoute(path, handler); err != nil {
return fmt.Errorf("routes parse error on line %d: %v", lineno, err)
}
return nil
}
func runHTTPServer() {
templates.root = options.httpTemplates
if err := httpRoot.ReadRoutesFile(options.httpRoutes); err != nil {
bail(E_Bad_HTTP, "unable to start http server: %v", err)
}
log_info("starting http server on port %v", options.http)
if err := http.ListenAndServe(options.http, httpRoot); err != nil {
bail(E_Bad_HTTP, "unable to start http server: %v", err)
}
}
package main
import (
"encoding/binary"
"fmt"
"sync/atomic"
"time"
)
// NewObjectId returns a new unique ObjectId.
// This function causes a runtime error if it fails to get the hostname
// of the current machine.
func NewId() Id {
b := make([]byte, 12)
// Timestamp, 4 bytes, big endian
binary.BigEndian.PutUint32(b, uint32(time.Now().Unix()))
b[4] = global.machineId[0]
b[5] = global.machineId[1]
b[6] = global.machineId[2]
// Pid, 2 bytes, specs don't specify endianness, but we use big endian.
b[7] = byte(global.pid >> 8)
b[8] = byte(global.pid)
// Increment, 3 bytes, big endian
i := atomic.AddUint32(&global.idCounter, 1)
b[9] = byte(i >> 16)
b[10] = byte(i >> 8)
b[11] = byte(i)
return Id(b)
}
// Id is used for tagging each incoming http request for logging
// purposes. The actual implementation is just the ObjectId implementation
// found in launchpad.net/mgo/bson. This will most likely change and evolve
// into its own format.
type Id string
func (id Id) String() string {
return fmt.Sprintf("%x", string(id))
}
// Time returns the timestamp part of the id.
// It's a runtime error to call this method with an invalid id.
func (id Id) Time() time.Time {
secs := int64(binary.BigEndian.Uint32(id.byteSlice(0, 4)))
return time.Unix(secs, 0)
}
// byteSlice returns byte slice of id from start to end.
// Calling this function with an invalid id will cause a runtime panic.
func (id Id) byteSlice(start, end int) []byte {
if len(id) != 12 {
panic(fmt.Sprintf("Invalid Id: %q", string(id)))
}
return []byte(string(id)[start:end])
}
package main
import (
"fmt"
"time"
)
type IdleState struct {
CommandSuite
NopExit
*System
}
func Idle(sys *System) ConnectionState {
i := &IdleState{System: sys}
i.CommandSuite = CommandSet{
balCommand,
helpCommand,
playersCommand,
BroadcastCommand(sys),
NearbyCommand(sys),
Command{
name: "goto",
help: "travel between star systems",
arity: 1,
handler: i.travelTo,
},
Command{
name: "bomb",
help: "bomb another star system",
arity: 1,
handler: i.bomb,
},
Command{
name: "mine",
help: "mine the current system for resources",
arity: 0,
handler: i.mine,
},
Command{
name: "info",
help: "gives you information about the current star system",
arity: 0,
handler: i.info,
},
Command{
name: "scan",
help: "scans the galaxy for signs of life",
arity: 0,
handler: i.scan,
},
Command{
name: "make",
help: "makes things",
handler: i.maek,
},
}
return i
}
func (i *IdleState) Enter(c *Connection) {
i.System.Arrive(c)
}
func (i *IdleState) String() string {
return fmt.Sprintf("idle on %v", i.System)
}
func (i *IdleState) Tick(c *Connection, frame int64) ConnectionState {
return i
}
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) bomb(c *Connection, args ...string) {
if c.bombs <= 0 {
c.Printf("Cannot send bomb: no bombs left! Build more bombs!\n")
return
}
if time.Since(c.lastBomb) < 5*time.Second {
c.Printf("Cannot send bomb: bombs are reloading\n")
return
}
target, err := GetSystem(args[0])
if err != nil {
c.Printf("Cannot send bomb: %v\n", err)
return
}
c.bombs -= 1
c.lastBomb = time.Now()
bomb := NewBomb(c, i.System, target)
currentGame.Register(bomb)
}
func (i *IdleState) mine(c *Connection, args ...string) {
c.SetState(Mine(i.System))
}
func (i *IdleState) info(c *Connection, args ...string) {
c.Printf("Currently idle on system %v\n", i.System)
c.Printf("Space duckets available: %v\n", i.money)
}
func (i *IdleState) scan(c *Connection, args ...string) {
if time.Since(c.lastScan) < 1*time.Minute {
return
}
c.Printf("Scanning the galaxy for signs of life...\n")
currentGame.Register(NewScan(i.System))
}
// "make" is already a keyword
func (i *IdleState) maek(c *Connection, args ...string) {
switch args[0] {
case "bomb":
if c.money < options.bombCost {
c.Printf("Not enough money! Bombs costs %v but you only have %v space duckets. Mine more space duckets!\n", options.bombCost, c.money)
return
}
c.SetState(MakeBomb(i.System))
case "colony":
MakeColony(c, i.System)
return
case "shield":
MakeShield(c, i.System)
default:
c.Printf("I don't know how to make a %v.\n", args[0])
}
}
package main
import ()
package main
import (
"flag"
"fmt"
"log"
"math/rand"
"net"
"os"
"time"
)
var options struct {
bombCost int
bombSpeed float64
debug bool
economic int
frameLength time.Duration
colonyCost int
frameRate int
http string
httpRoutes string
httpTemplates string
lightSpeed float64
makeBombTime time.Duration
makeColonyTime time.Duration
makeShieldTime time.Duration
moneyMean float64
moneySigma float64
playerSpeed float64
respawnFrames int64
respawnTime time.Duration
scanTime time.Duration
speckPath string
ssh string
sshKey string
startBombs int
startMoney int
}
var (
info_log *log.Logger
error_log *log.Logger
currentGame *Game
)
func log_error(template string, args ...interface{}) {
error_log.Printf(template, args...)
}
func log_info(template string, args ...interface{}) {
info_log.Printf(template, args...)
}
func bail(status int, template string, args ...interface{}) {
if status == 0 {
fmt.Fprintf(os.Stdout, template, args...)
} else {
fmt.Fprintf(os.Stderr, template, args...)
}
os.Exit(status)
}
func handleConnection(conn *Connection) {
defer conn.Close()
conn.Login()
c := make(chan []string)
go conn.ReadLines(c)
for parts := range c {
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 framesToDur(frames int64) time.Duration {
return options.frameLength * time.Duration(frames)
}
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()
listener, err := net.Listen("tcp", ":9219")
if err != nil {
bail(E_No_Port, "unable to start server: %v", err)
}
go runHTTPServer()
go runSSHServer()
go func() {
for {
log_info("starting new game")
currentGame = NewGame()
currentGame.Run()
}
}()
for {
conn, err := listener.Accept()
if err != nil {
log_error("error accepting connection: %v", err)
continue
}
go handleConnection(NewConnection(conn))
}
}
func init() {
flag.Float64Var(&options.lightSpeed, "light-speed", 0.01, "speed of light in parsecs per frame")
flag.IntVar(&options.frameRate, "frame-rate", 100, "frame rate, in frames per second")
flag.Float64Var(&options.playerSpeed, "player-speed", 0.8, "player travel speed, relative to C, the speed of light")
flag.Float64Var(&options.bombSpeed, "bomb-speed", 0.9, "bomb travel speed, relattive to C, the speed of light")
flag.IntVar(&options.economic, "economic", 25000, "amount of money needed to win economic victory")
flag.Float64Var(&options.moneyMean, "money-mean", 10000, "mean amount of money on a system")
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")
flag.DurationVar(&options.makeBombTime, "bomb-time", 5*time.Second, "time it takes to make a bomb")
flag.IntVar(&options.bombCost, "bomb-cost", 500, "price of a bomb")
flag.IntVar(&options.colonyCost, "colony-cost", 2000, "price of a colony")
flag.DurationVar(&options.makeColonyTime, "colony-time", 15*time.Second, "time it takes to make a colony")
flag.IntVar(&options.startBombs, "start-bombs", 0, "number of bombs a player has at game start")
flag.IntVar(&options.startMoney, "start-money", 1000, "amount of money a player has to start")
flag.DurationVar(&options.makeShieldTime, "shield-time", 15*time.Second, "time it takes to make a shield")
flag.DurationVar(&options.scanTime, "scan-recharge", 1*time.Minute, "time it takes for scanners to recharge")
flag.StringVar(&options.http, "http", ":8000", "http hostname:port")
flag.StringVar(&options.httpRoutes, "http-routes", "./routes", "path to a file containing http routes")
flag.StringVar(&options.httpTemplates, "http-templates", "./templates", "path to a file containing http templates")
flag.StringVar(&options.sshKey, "ssh-key", "./exo_rsa", "path to an ssh private key for running the ssh server")
flag.StringVar(&options.ssh, "ssh", ":9220", "ssh host:port to open")
}
package main
import (
"fmt"
)
type MiningState struct {
CommandSuite
*System
mined int
}
func Mine(sys *System) ConnectionState {
m := &MiningState{System: sys}
m.CommandSuite = CommandSet{
balCommand,
helpCommand,
playersCommand,
BroadcastCommand(sys),
NearbyCommand(sys),
Command{
name: "stop",
help: "stops mining",
arity: 0,
handler: m.stop,
},
Command{
name: "info",
help: "gives you information about the current mining operation",
arity: 0,
handler: m.info,
},
}
return m
}
func (m *MiningState) Enter(c *Connection) {
c.Printf("Mining %v. %v space duckets remaining.\n", m.System, m.money)
}
func (m *MiningState) Tick(c *Connection, frame int64) ConnectionState {
if m.money <= 0 {
c.Printf("system %s is all out of space duckets.\n", m.System)
return Idle(m.System)
} else {
c.Deposit(1)
m.mined += 1
m.money -= 1
return m
}
}
func (m *MiningState) Exit(c *Connection) {
if m.money == 0 {
c.Printf("Done mining %v.\nMined %v space duckets total.\nNo space duckets remain on %v, and it can't be mined again.\n", m.System, m.mined, m.System)
} else {
c.Printf("Done mining %v.\nMined %v space duckets total.\n%v space duckets remain on %v, and it can be mined again.\n", m.System, m.mined, m.money, m.System)
}
}
func (m *MiningState) String() string {
return fmt.Sprintf("mining %v", m.System)
}
func (m *MiningState) stop(c *Connection, args ...string) {
c.SetState(Idle(m.System))
}
func (m *MiningState) info(c *Connection, args ...string) {
c.Printf("Currently mining system %v\n", m.System)
c.Printf("Mined so far: %v\n", m.mined)
c.Printf("Remaining space duckets on %v: %v\n", m.System, m.money)
}
package main
import (
"code.google.com/p/go.crypto/ssh"
"fmt"
"regexp"
)
var namePattern = regexp.MustCompile(`^[[:alpha:]][[:alnum:]-_]{0,19}$`)
func ValidName(name string) bool {
return namePattern.MatchString(name)
}
type Profile struct {
id int
nick string
email string
key string
}
func (p *Profile) Create() error {
_, err := db.Exec(`
insert into profiles
(nick, email, key)
values
(?, ?, ?)
;`, p.nick, p.email, p.key)
if err != nil {
return fmt.Errorf("unable to create profile: %v", err)
}
return nil
}
func (p *Profile) PublicKey() (ssh.PublicKey, error) {
key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(p.key))
return key, err
}
func profilesTable() {
stmnt := `create table if not exists profiles (
id integer not null primary key autoincrement,
nick text unique not null,
email text unique not null,
key text unique not null
);`
if _, err := db.Exec(stmnt); err != nil {
log_error("couldn't create profiles table: %v", err)
}
}
func loadProfile(nick string) (*Profile, error) {
row := db.QueryRow(`select * from profiles where nick = ?`, nick)
var p Profile
if err := row.Scan(&p.id, &p.nick, &p.email, &p.key); err != nil {
return nil, fmt.Errorf("unable to fetch profile from database: %v", err)
}
return &p, nil
}
package main
import (
"fmt"
"time"
)
type scan struct {
start time.Time
origin *System
dist float64
nextHitIndex int
nextEchoIndex int
results []scanResult
}
type scanResult struct {
system *System
dist float64
players map[*Connection]bool
colonizedBy *Connection
shielded bool
shieldEnergy float64
}
func (r *scanResult) Empty() bool {
return (r.players == nil || len(r.players) == 0) && r.colonizedBy == nil
}
func (r *scanResult) playerNames() []string {
if r.players == nil || len(r.players) == 0 {
return nil
}
names := make([]string, 0, len(r.players))
for conn := range r.players {
names = append(names, conn.Name())
}
return names
}
func NewScan(origin *System) *scan {
return &scan{
origin: origin,
start: time.Now(),
results: make([]scanResult, 0, len(origin.Distances())),
}
}
func (s *scan) Tick(frame int64) {
s.dist += options.lightSpeed
s.hits()
s.echos()
}
func (s *scan) Dead() bool {
return s.nextEchoIndex >= len(s.origin.Distances())
}
func (s *scan) String() string {
return fmt.Sprintf("[scan origin: %s start_time: %v]", s.origin.name, s.start)
}
func (s *scan) hits() {
for ; s.nextHitIndex < len(s.origin.Distances()); s.nextHitIndex += 1 {
candidate := s.origin.Distances()[s.nextHitIndex]
if s.dist < candidate.dist {
break
}
s.results = append(s.results, s.hitSystem(candidate.s, candidate.dist))
log_info("scan hit %v. Traveled %v in %v", candidate.s.name, candidate.dist, time.Since(s.start))
}
}
func (s *scan) hitSystem(sys *System, dist float64) scanResult {
sys.NotifyInhabitants("scan detected from %v\n", s.origin)
r := scanResult{
system: sys,
colonizedBy: sys.colonizedBy,
dist: dist * 2.0,
shielded: sys.Shield != nil,
}
if sys.Shield != nil {
r.shieldEnergy = sys.Shield.energy
}
if sys.players != nil {
r.players = make(map[*Connection]bool, len(sys.players))
for k, v := range sys.players {
r.players[k] = v
}
}
return r
}
func (s *scan) echos() {
for ; s.nextEchoIndex < len(s.results); s.nextEchoIndex += 1 {
res := s.results[s.nextEchoIndex]
if s.dist < res.dist {
break
}
log_info("echo from %v reached origin %v after %v", res.system.name, s.origin.name, time.Since(s.start))
if res.Empty() {
continue
}
s.origin.NotifyInhabitants("results from scan of %v:\n", res.system)
s.origin.NotifyInhabitants("\tdistance: %v\n", s.origin.DistanceTo(res.system))
s.origin.NotifyInhabitants("\tshielded: %v\n", res.shielded)
if res.shielded {
s.origin.NotifyInhabitants("\tshield energy: %v\n", res.shieldEnergy)
}
inhabitants := res.playerNames()
if inhabitants != nil {
s.origin.NotifyInhabitants("\tinhabitants: %v\n", inhabitants)
}
if res.colonizedBy != nil {
s.origin.NotifyInhabitants("\tcolonized by: %v\n", res.colonizedBy.Name())
}
}
}
package main
import (
"fmt"
)
func MakeShield(c *Connection, s *System) {
m := &MakeShieldState{
System: s,
CommandSuite: CommandSet{
balCommand,
BroadcastCommand(s),
helpCommand,
NearbyCommand(s),
playersCommand,
},
}
c.SetState(m)
}
type MakeShieldState struct {
CommandSuite
*System
start int64
}
func (m *MakeShieldState) Enter(c *Connection) {
c.Printf("Making shield on %v...\n", m.System)
}
func (m *MakeShieldState) Tick(c *Connection, frame int64) ConnectionState {
if m.start == 0 {
m.start = frame
}
if framesToDur(frame-m.start) >= options.makeShieldTime {
return Idle(m.System)
}
return m
}
func (m *MakeShieldState) Exit(c *Connection) {
c.Printf("Done! System %v is now shielded.\n", m.System)
m.System.Shield = new(Shield)
}
func (m *MakeShieldState) String() string {
return fmt.Sprintf("Making shield on %v", m.System)
}
type Shield struct {
energy float64
}
func (s *Shield) Tick(frame int64) {
if s.energy < 1000 {
s.energy += (1000 - s.energy) * 0.0005
}
}
func (s *Shield) Hit() bool {
if s.energy > 750 {
s.energy -= 750
return true
}
return false
}
func (s *Shield) Dead() bool {
return false
}
package main
import (
"bufio"
"io"
"regexp"
"strconv"
"strings"
)
func speckStream(r io.ReadCloser, c chan System) {
defer close(c)
defer r.Close()
keep := regexp.MustCompile(`^\s*[\d-]`)
br := bufio.NewReader(r)
for {
line, err := br.ReadBytes('\n')
switch err {
case io.EOF:
return
case nil:
break
default:
log_error("unable to stream speck file: %v", err)
return
}
if !keep.Match(line) {
continue
}
planet := parseSpeckLine(line)
c <- *planet
}
}
func parseSpeckLine(line []byte) *System {
parts := strings.Split(string(line), " ")
var err error
var g errorGroup
s := new(System)
s.x, err = strconv.ParseFloat(parts[0], 64)
g.AddError(err)
s.y, err = strconv.ParseFloat(parts[1], 64)
g.AddError(err)
s.z, err = strconv.ParseFloat(parts[2], 64)
g.AddError(err)
s.planets, err = strconv.Atoi(parts[3])
g.AddError(err)
s.name = strings.TrimSpace(strings.Join(parts[7:], " "))
if g != nil {
log_error("unable to parse speck line: %v", g)
}
return s
}
package main
import (
"bufio"
"code.google.com/p/go.crypto/ssh"
"fmt"
"io"
"io/ioutil"
"net"
"time"
"unicode"
)
func handlePublicKey(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
profile, err := loadProfile(conn.User())
if err != nil {
log_info("client saw error when attempting to establish ssh connection: %v", err)
return nil, fmt.Errorf("unable to find profile: %v", err)
}
storedKey, err := profile.PublicKey()
if err != nil {
log_error("unable to parse stored public key for user %s: %v", conn.User(), err)
return nil, fmt.Errorf("we have a bad key: %v", err)
}
if string(storedKey.Marshal()) != string(key.Marshal()) {
log_info("client sent the wrong key")
return nil, fmt.Errorf("wrong key")
}
log_info("player %s connected", conn.User())
var perms ssh.Permissions
return &perms, nil
}
func streamSSHRunes(c ssh.Channel, out chan rune) {
defer close(out)
r := bufio.NewReader(c)
for {
roon, _, err := r.ReadRune()
switch err {
case io.EOF:
return
case nil:
out <- roon
default:
log_error("error on ssh byte stream: %v", err)
return
}
}
}
func handleSSHSessionChan(conn *ssh.ServerConn, c ssh.Channel) {
t := time.Tick(250 * time.Millisecond)
runes := make(chan rune)
cmd := make([]rune, 0, 32)
go streamSSHRunes(c, runes)
for {
select {
case now := <-t:
fmt.Fprintf(c, "\r%v: %s", now.Unix(), string(cmd))
case r, ok := <-runes:
if !ok {
return
}
switch r {
case 0x4: // this is what ctrl+d sends
c.Close()
return
case 0xc: // ctrl+l
fmt.Fprint(c, "c")
case 0xd: // enter
log_info("%x (enter)", r)
fmt.Fprintf(c, "\r\n%v: %s", time.Now().Unix(), string(cmd))
case 0x41:
log_info("up")
case 0x42:
log_info("down")
case 0x43:
log_info("right")
case 0x44:
log_info("left")
case 0x7f: // backspace
if len(cmd) > 0 {
cmd = cmd[:len(cmd)-1]
fmt.Fprint(c, " ")
}
case 0x15: // ctrl+u
cmd = cmd[:0]
fmt.Fprintf(c, "\r%v: %s", time.Now().Unix(), string(cmd))
default:
if unicode.IsGraphic(r) {
cmd = append(cmd, r)
fmt.Fprintf(c, "\r%v: %s", time.Now().Unix(), string(cmd))
} else {
// a 0x61
// z 0x7a
// 0 0x30
// 9 0x39
// space 0x20
// esc 0x1b
log_info("%x %c", r, r)
}
}
}
}
}
func handleNewSSHChannel(conn *ssh.ServerConn, req ssh.NewChannel) {
logSSHChan(req)
switch req.ChannelType() {
case "session":
c, requests, err := req.Accept()
if err != nil {
log_error("unable to handle ssh session channel request: %v", err)
return
}
go handleSSHRequests(conn, requests, "session")
go handleSSHSessionChan(conn, c)
default:
log_info("refusing ssh channel request for a channel of type %s", req.ChannelType())
if err := req.Reject(ssh.UnknownChannelType, "I don't know how to handle that channel type"); err != nil {
log_error("hit error rejecting ssh channel request: %v", err)
}
}
}
func handleSSHChannels(conn *ssh.ServerConn, c <-chan ssh.NewChannel) {
for req := range c {
handleNewSSHChannel(conn, req)
}
}
func logSSHChan(req ssh.NewChannel) {
log_info("ssh channel request:\n\ttype:%s\n\textra: %s", req.ChannelType(), req.ExtraData())
}
func logSSHRequest(req *ssh.Request, chanType string) {
log_info("ssh request on %s channel:\n\ttype:%s\n\tresponse requested: %v\n\t payload: %s", chanType, req.Type, req.WantReply, req.Payload)
}
func handleSSHRequests(conn *ssh.ServerConn, c <-chan *ssh.Request, chanType string) {
for request := range c {
logSSHRequest(request, chanType)
}
}
func handleSSHConnection(conn net.Conn, config *ssh.ServerConfig) {
log_info("incoming ssh connection from %s", conn.RemoteAddr())
sshConn, channels, requests, err := ssh.NewServerConn(conn, config)
if err != nil {
log_error("error in client handshake: %v", err)
return
}
go handleSSHRequests(sshConn, requests, "root")
go handleSSHChannels(sshConn, channels)
if err := sshConn.Wait(); err != nil {
log_error("server conn exited with error: %v", err)
}
}
func runSSHServer() {
// load up private key file
pemBytes, err := ioutil.ReadFile(options.sshKey)
if err != nil {
bail(E_BadPrivateKey, "unable to read private key file: %v", err)
}
log_info("read %d pem bytes\n", len(pemBytes))
// parse private key file data
signer, err := ssh.ParsePrivateKey(pemBytes)
if err != nil {
bail(E_BadPrivateKey, "unable to parse private key data: %v", err)
}
log_info("we have a signer: %v", signer)
config := &ssh.ServerConfig{
NoClientAuth: false,
PublicKeyCallback: handlePublicKey,
}
config.AddHostKey(signer)
listener, err := net.Listen("tcp", options.ssh)
if err != nil {
bail(E_ListenError, "unable to open tcp listener: %v", err)
}
log_info("opened tcp port %s for ssh", options.ssh)
for {
conn, err := listener.Accept()
if err != nil {
log_error("client failed to establish tcp connection: %v", err)
continue
}
go handleSSHConnection(conn, config)
}
}
package main
import (
"database/sql"
"fmt"
"math"
"math/rand"
"strconv"
"time"
)
var (
index map[int]*System
nameIndex map[string]*System
)
type System struct {
*Shield
id int
x, y, z float64
planets int
name string
players map[*Connection]bool
colonizedBy *Connection
distances []Ray
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.money > 0 {
s.colonizedBy.Deposit(1)
s.money -= 1
}
if s.Shield != nil {
s.Shield.Tick(frame)
}
}
func (s *System) Dead() bool {
return false
}
func (s *System) Reset() {
s.players = make(map[*Connection]bool, 32)
s.colonizedBy = nil
}
func (s *System) Arrive(conn *Connection) {
// conn.SetSystem(s)
if s.players[conn] {
return
}
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 %v. There is %d planet here.\n", s, s.planets)
} else {
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
}
func (s *System) NotifyInhabitants(template string, args ...interface{}) {
s.EachConn(func(conn *Connection) {
conn.Printf(template, args...)
})
}
func (s *System) EachConn(fn func(*Connection)) {
if s.players == nil {
return
}
for conn, _ := range s.players {
fn(conn)
}
}
func (s *System) NumInhabitants() int {
if s.players == nil {
return 0
}
return len(s.players)
}
func (e System) Store(db *sql.DB) {
_, err := db.Exec(`
insert into planets
(name, x, y, z, planets)
values
(?, ?, ?, ?, ?)
;`, e.name, e.x, e.y, e.z, e.planets)
if err != nil {
log_error("unable to store system: %v", err)
}
}
func (s *System) DistanceTo(other *System) float64 {
return dist3d(s.x, s.y, s.z, other.x, other.y, other.z)
}
func (s *System) LightTimeTo(other *System) time.Duration {
return time.Duration(int64(s.DistanceTo(other) * 100000000))
}
func (s *System) BombTimeTo(other *System) time.Duration {
return time.Duration(int64(s.DistanceTo(other) * 110000000))
}
func (s *System) TravelTimeTo(other *System) time.Duration {
return time.Duration(int64(s.DistanceTo(other) * 125000000))
}
type Ray struct {
s *System
dist float64 // distance in parsecs
}
func (s *System) Distances() []Ray {
if s.distances == nil {
s.distances = make([]Ray, 0, 551)
rows, err := db.Query(`
select edges.id_2, edges.distance
from edges
where edges.id_1 = ?
order by distance
;`, s.id)
if err != nil {
log_error("unable to query for system distances: %v", err)
return nil
}
for rows.Next() {
var (
r Ray
id int
dist float64
)
if err := rows.Scan(&id, &dist); err != nil {
log_error("unable to unpack Ray from sql result: %v", err)
continue
}
r.s = index[id]
r.dist = dist
s.distances = append(s.distances, r)
}
}
return s.distances
}
func (s *System) Bombed(bomber *Connection, frame int64) {
if s.Shield != nil {
if s.Shield.Hit() {
s.EachConn(func(conn *Connection) {
conn.Printf("A bomb has hit %v but it was stopped by the system's shield.\n", s)
conn.Printf("Shield remaining: %v.\n", s.energy)
conn.Printf("Shield is recharing....\n")
})
return
}
}
s.EachConn(func(conn *Connection) {
conn.Die(frame)
s.Leave(conn)
bomber.MadeKill(conn)
})
if s.colonizedBy != nil {
s.colonizedBy.Printf("your mining colony on %s has been destroyed!\n", s.name)
s.colonizedBy = nil
}
for id, _ := range index {
if id == s.id {
continue
}
delay := s.LightTimeTo(index[id])
id2 := id
time.AfterFunc(delay, func() {
bombNotice(id2, s.id)
})
}
}
func bombNotice(to_id, from_id int) {
to := index[to_id]
from := index[from_id]
to.EachConn(func(conn *Connection) {
conn.Printf("a bombing has been observed on %s\n", from.name)
})
}
func (s System) String() string {
return fmt.Sprintf("%s (id: %v)", s.name, s.id)
}
type Neighbor struct {
id int
distance float64
}
func (e *System) Nearby(n int) ([]Neighbor, error) {
rows, err := db.Query(`
select planets.id, edges.distance
from edges
join planets on edges.id_2 = planets.id
where edges.id_1 = ?
order by distance
limit ?
;`, e.id, n)
if err != nil {
log_error("unable to get nearby systems for %s: %v", e.name, err)
return nil, err
}
neighbors := make([]Neighbor, 0, n)
for rows.Next() {
var neighbor Neighbor
if err := rows.Scan(&neighbor.id, &neighbor.distance); err != nil {
log_error("error unpacking row from nearby neighbors query: %v", err)
continue
}
neighbors = append(neighbors, neighbor)
}
return neighbors, nil
}
func countSystems() (int, error) {
row := db.QueryRow(`select count(*) from planets`)
var n int
err := row.Scan(&n)
return n, err
}
func sq(x float64) float64 {
return x * x
}
func dist3d(x1, y1, z1, x2, y2, z2 float64) float64 {
return math.Sqrt(sq(x1-x2) + sq(y1-y2) + sq(z1-z2))
}
func indexSystems() map[int]*System {
rows, err := db.Query(`select * from planets`)
if err != nil {
log_error("unable to select all planets: %v", err)
return nil
}
defer rows.Close()
index = make(map[int]*System, 551)
nameIndex = make(map[string]*System, 551)
for rows.Next() {
p := System{}
if err := rows.Scan(&p.id, &p.name, &p.x, &p.y, &p.z, &p.planets); err != nil {
log_info("unable to scan planet row: %v", err)
continue
}
index[p.id] = &p
nameIndex[p.name] = &p
p.money = int64(rand.NormFloat64()*options.moneySigma + options.moneyMean)
log_info("seeded system %v with %v monies", p, p.money)
}
return index
}
func randomSystem() (*System, error) {
n := len(index)
if n == 0 {
return nil, fmt.Errorf("no planets are known to exist")
}
pick := rand.Intn(n)
sys := index[pick]
return sys, nil
}
package main
import (
"fmt"
"html/template"
"path/filepath"
)
type templateCache struct {
root string
}
func (t *templateCache) Get(relpath string) (*template.Template, error) {
path := filepath.Join(t.root, relpath)
tpl, err := template.ParseFiles(path)
if err != nil {
return nil, fmt.Errorf("unable to fetch template at %s: %v", relpath, err)
}
return tpl, nil
}
package main
import (
"fmt"
"time"
)
type TravelState struct {
CommandSuite
start *System
dest *System
travelled float64 // distance traveled so far in parsecs
dist float64 // distance between start and end in parsecs
}
func NewTravel(c *Connection, start, dest *System) ConnectionState {
t := &TravelState{
start: start,
dest: dest,
dist: start.DistanceTo(dest),
}
t.CommandSuite = CommandSet{
helpCommand,
playersCommand,
balCommand,
Command{
name: "progress",
help: "displays how far you are along your travel",
arity: 0,
handler: t.progress,
},
Command{
name: "eta",
help: "displays estimated time of arrival",
arity: 0,
handler: func(c *Connection, args ...string) {
c.Printf("Remaining: %v\n", t.remaining())
c.Printf("Current time: %v\n", time.Now())
c.Printf("ETA: %v\n", t.eta())
},
},
}
return t
}
func (t *TravelState) Enter(c *Connection) {
c.Printf("Leaving %v, bound for %v.\n", t.start, t.dest)
c.Printf("Trip duration: %v\n", t.tripTime())
c.Printf("Current time: %v\n", time.Now())
c.Printf("ETA: %v\n", t.eta())
t.start.Leave(c)
}
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)
t.dest.Arrive(c)
}
func (t *TravelState) String() string {
return fmt.Sprintf("Traveling from %v to %v", t.start, t.dest)
}
func (t *TravelState) progress(c *Connection, args ...string) {
c.Printf("%v\n", t.travelled/t.dist)
}
func (t *TravelState) remaining() time.Duration {
remaining := t.dist - t.travelled
frames := remaining / (options.playerSpeed * options.lightSpeed)
return framesToDur(int64(frames))
}
func (t *TravelState) eta() time.Time {
// distance remaining in parsecs
return time.Now().Add(t.remaining())
}
func (t *TravelState) tripTime() time.Duration {
frames := t.dist / (options.playerSpeed * options.lightSpeed)
return framesToDur(int64(frames))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment