Created
April 26, 2010 18:52
-
-
Save manveru/379723 to your computer and use it in GitHub Desktop.
A simple game of Pong written in Go.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"sdl" | |
"math" | |
"time" | |
"rand" | |
"flag" | |
"fmt" | |
) | |
/************************************************ | |
A simple game of Pong. | |
Run `pong -h` for the available options. | |
Keyboard controls are: | |
j: paddle down | |
k: paddle up | |
p: pause | |
q: quit | |
On i686 you can build this with: | |
$ 8g pong.go && 8l pong.8 | |
On x86_64, it's: | |
$ 6g pong.go && 6l pong.6 | |
************************************************/ | |
var worldHeight *int = flag.Int("height", 200, "Height of Game") | |
var worldWidth *int = flag.Int("width", 200, "Width of Game") | |
var paddleSpeed *int = flag.Int("player-speed", 4, "Speed of player paddle") | |
var enemySpeed *int = flag.Int("enemy-speed", 4, "Speed of enemy paddle") | |
var ballSpeed *int = flag.Int("ball-speed", 4, "Speed of the ball") | |
var showUsage *bool = flag.Bool("help", false, "Show help") | |
func main() { | |
sdlSetup() | |
rand.Seed(time.Nanoseconds()) | |
sdl.WM_SetCaption("PonGo", "") | |
flag.Parse() | |
if *showUsage { | |
flag.PrintDefaults() | |
return | |
} | |
world := NewWorld(*worldHeight, *worldWidth) | |
go world.Run() | |
world.HandleEvents() | |
defer Quit(world) | |
} | |
type Ball struct { | |
vector *Vector2 | |
velocity *Vector2 | |
radius float64 | |
speed float64 | |
color uint32 | |
} | |
func NewBall(x, y float64) (ball *Ball) { | |
ball = &Ball{ | |
vector: &Vector2{X: x, Y: y}, | |
speed: float64(*ballSpeed), | |
radius: 2.0, | |
color: 0xffffff, | |
} | |
velocity := Vector2{X: float64(1 + rand.Intn(5)), Y: float64(rand.Intn(5))} | |
ball.velocity = velocity.Normalize().MultiplyNum(ball.speed) | |
return | |
} | |
func (self *Ball) Update(world *World) { | |
velocity := self.velocity | |
future := self.vector.Plus(velocity) | |
radius := self.radius | |
if velocity.Y < 0 && future.Y <= radius { | |
velocity.Y = -velocity.Y | |
} | |
if velocity.Y > 0 && future.Y >= (float64(world.Height)-radius) { | |
velocity.Y = -velocity.Y | |
} | |
if velocity.X < 0 { | |
paddle := world.Paddle | |
hit, _ := paddle.Hit(self.vector, future) | |
if hit { | |
velocity.X = -velocity.X | |
velocity = velocity.Normalize().MultiplyNum(self.speed) | |
} else if future.X <= radius { | |
fmt.Println("Enemy scores") | |
world.Score.Enemy++ | |
velocity.X = -velocity.X | |
} | |
} | |
if velocity.X > 0 { | |
enemy := world.Enemy | |
hit, _ := enemy.Hit(self.vector, future) | |
if hit { | |
velocity.X = -velocity.X | |
velocity = velocity.Normalize().MultiplyNum(self.speed) | |
} else if future.X >= float64(world.Width) { | |
fmt.Println("Player scores") | |
world.Score.Paddle++ | |
velocity.X = -velocity.X | |
} | |
} | |
self.velocity = velocity | |
self.vector = self.vector.Plus(velocity) | |
} | |
func (self *Ball) Draw(world *World) { | |
world.Screen.FillRect(self.Rect(), self.color) | |
} | |
func (self *Ball) Rect() *sdl.Rect { | |
size := uint16(self.radius * 2) | |
x := self.vector.X - self.radius | |
y := self.vector.Y - self.radius | |
return &sdl.Rect{X: int16(x), Y: int16(y), W: size, H: size} | |
} | |
type Paddle struct { | |
vector *Vector2 | |
target *Vector2 | |
height, width, speed float64 | |
color uint32 | |
} | |
func NewPaddle(x, y, w, h float64) *Paddle { | |
return &Paddle{ | |
vector: &Vector2{X: x, Y: y}, | |
target: &Vector2{X: x, Y: y}, | |
height: h, | |
width: w, | |
color: 0x6666ff, | |
speed: float64(*paddleSpeed), | |
} | |
} | |
func (self *Paddle) Go(x, y float64) { | |
self.target = &Vector2{X: self.vector.X, Y: y} | |
} | |
func (self *Paddle) Update(world *World) { | |
goal := self.target.Minus(self.vector) | |
if goal.Length() > self.speed { | |
goal = goal.Normalize().MultiplyNum(self.speed) | |
} | |
future := self.vector.Plus(goal) | |
if future.Y < (self.height / 2) { | |
return | |
} | |
if (future.Y + (self.height / 2)) > float64(world.Height) { | |
return | |
} | |
self.vector = future | |
} | |
func (self *Paddle) Draw(world *World) { | |
world.Screen.FillRect(self.Rect(), self.color) | |
} | |
func (self *Paddle) Rect() *sdl.Rect { | |
h := self.height | |
w := self.width | |
x := self.vector.X - float64(w/2) | |
y := self.vector.Y - float64(h/2) | |
return &sdl.Rect{X: int16(x), Y: int16(y), W: uint16(w), H: uint16(h)} | |
} | |
func (self *Paddle) Hit(past, future *Vector2) (hit bool, place *Vector2) { | |
// our front line | |
halfHeight := self.height / 2 | |
halfWidth := self.width / 2 | |
x0, y0 := (self.vector.X + halfWidth), (self.vector.Y - halfHeight) | |
x1, y1 := (self.vector.X + halfWidth), (self.vector.Y + halfHeight) | |
return self.hitCore(x0, y0, x1, y1, past, future) | |
} | |
type Enemy struct { | |
Paddle | |
} | |
func NewEnemy(x, y, w, h float64) *Enemy { | |
return &Enemy{ | |
Paddle: Paddle{ | |
width: w, | |
height: h, | |
color: 0xff6666, | |
speed: float64(*enemySpeed), | |
target: &Vector2{X: 0, Y: 0}, | |
vector: &Vector2{X: x, Y: y}, | |
}, | |
} | |
} | |
func (self *Enemy) Hit(past, future *Vector2) (hit bool, place *Vector2) { | |
// our front line | |
halfHeight := self.height / 2 | |
halfWidth := self.width / 2 | |
x0, y0 := (self.vector.X - halfWidth), (self.vector.Y - halfHeight) | |
x1, y1 := (self.vector.X - halfWidth), (self.vector.Y + halfHeight) | |
return self.hitCore(x0, y0, x1, y1, past, future) | |
} | |
func (self *Paddle) hitCore(x0, y0, x1, y1 float64, past, future *Vector2) (hit bool, place *Vector2) { | |
// line between past and future | |
x2, y2 := past.X, past.Y | |
x3, y3 := future.X, future.Y | |
d := (x1-x0)*(y3-y2) - (y1-y0)*(x3-x2) | |
if math.Fabs(d) < 0.001 { | |
return | |
} // never hit since parallel | |
ab := ((y0-y2)*(x3-x2) - (x0-x2)*(y3-y2)) / d | |
if ab > 0.0 && ab < 1.0 { | |
cd := ((y0-y2)*(x1-x0) - (x0-x2)*(y1-y0)) / d | |
if cd > 0.0 && cd < 1.0 { | |
linx := x0 + ab*(x1-x0) | |
liny := y0 + ab*(y1-y0) | |
hit = true | |
place = &Vector2{X: linx, Y: liny} | |
} | |
} | |
// no hit | |
return | |
} | |
func (self *Enemy) Update(world *World) { | |
if world.Ball.velocity.X > 0 { | |
targetY := world.Ball.vector.Y | |
targetX := self.vector.X | |
goal := (&Vector2{X: targetX, Y: targetY}).Minus(self.vector) | |
if goal.Length() > self.speed { | |
goal = goal.Normalize().MultiplyNum(self.speed) | |
} | |
self.target = goal | |
} | |
future := self.vector.Plus(self.target) | |
if future.Y < (self.height / 2) { | |
return | |
} | |
if (future.Y + (self.height / 2)) > float64(world.Height) { | |
return | |
} | |
self.vector = future | |
} | |
type World struct { | |
running bool | |
pause bool | |
Height, Width int | |
Screen *sdl.Surface | |
Ball *Ball | |
Paddle *Paddle | |
Enemy *Enemy | |
Score *Score | |
} | |
func NewWorld(height, width int) *World { | |
return &World{ | |
Height: height, | |
Width: width, | |
Screen: NewSurface(width, height), | |
Ball: NewBall(float64(width)/2, float64(height)/2), | |
Paddle: NewPaddle(5, float64(height)/2, 5, 30), | |
Enemy: NewEnemy(float64(width-5), float64(height)/2, 5, 30), | |
Score: NewScore(), | |
running: true, | |
pause: false, | |
} | |
} | |
func (self *World) HandleEvents() { | |
for self.running { | |
e := &sdl.Event{} | |
for e.Poll() { | |
switch e.Type { | |
case sdl.QUIT: | |
self.running = false | |
case sdl.KEYDOWN: | |
switch sdl.GetKeyName(sdl.Key(e.Keyboard().Keysym.Sym)) { | |
case "p": | |
self.pause = !self.pause | |
case "j": | |
self.Paddle.Go(0, self.Paddle.vector.Y+self.Paddle.speed) | |
case "k": | |
self.Paddle.Go(0, self.Paddle.vector.Y-self.Paddle.speed) | |
case "q": | |
self.running = false | |
} | |
case sdl.MOUSEMOTION: | |
motion := e.MouseMotion() | |
self.Paddle.Go(float64(motion.X), float64(motion.Y)) | |
} | |
} | |
sdl.Delay(25) | |
} | |
} | |
func (self *World) Run() { | |
for self.running { | |
if !self.pause { | |
self.Update() | |
self.Draw() | |
} | |
sdl.Delay(25) | |
} | |
} | |
func (self *World) Update() { | |
self.Ball.Update(self) | |
self.Paddle.Update(self) | |
self.Enemy.Update(self) | |
} | |
func (self *World) Draw() { | |
self.Screen.FillRect(nil, 0x0) | |
center := &sdl.Rect{X: int16(self.Width/2) - 1, Y: 0, H: 200, W: 2} | |
self.Screen.FillRect(center, 0x333333) | |
self.Paddle.Draw(self) | |
self.Enemy.Draw(self) | |
self.Ball.Draw(self) | |
self.Score.Draw(self) | |
self.Screen.Flip() | |
} | |
type Score struct { | |
Enemy int | |
Paddle int | |
color *sdl.Color | |
} | |
func NewScore() (score *Score) { | |
score = &Score{ | |
color: &sdl.Color{255, 255, 255, 0}, | |
Enemy: 0, | |
Paddle: 0, | |
} | |
return score | |
} | |
func (self *Score) Draw(world *World) { | |
pRect := &sdl.Rect{ | |
X: int16(world.Paddle.width + world.Paddle.vector.X), | |
Y: 3, W: 3, H: 3, | |
} | |
for p := self.Paddle; p > 0; p-- { | |
pRect.X += 6 | |
world.Screen.FillRect(pRect, 0x6666ff) | |
} | |
if int(pRect.X) > world.Width { | |
fmt.Println("You Win!") | |
world.running = false | |
} | |
eRect := &sdl.Rect{ | |
X: int16(world.Enemy.vector.X - world.Enemy.width), | |
Y: int16(world.Height - 6), W: 3, H: 3, | |
} | |
for e := self.Enemy; e >= 0; e-- { | |
eRect.X -= 6 | |
world.Screen.FillRect(eRect, 0xff6666) | |
} | |
if eRect.X <= 0 { | |
fmt.Println("You Lose!") | |
world.running = false | |
} | |
} | |
type Vector2 struct { | |
X, Y float64 | |
} | |
func (self *Vector2) Normalize() *Vector2 { | |
length := self.Length() | |
return &Vector2{X: (self.X / length), Y: (self.Y / length)} | |
} | |
func (self *Vector2) MultiplyNum(other float64) *Vector2 { | |
return &Vector2{X: (self.X * other), Y: (self.Y * other)} | |
} | |
func (self *Vector2) Plus(other *Vector2) *Vector2 { | |
return &Vector2{X: (self.X + other.X), Y: (self.Y + other.Y)} | |
} | |
func (self *Vector2) Minus(other *Vector2) *Vector2 { | |
return &Vector2{X: (self.X - other.X), Y: (self.Y - other.Y)} | |
} | |
func (self *Vector2) Length() float64 { | |
return math.Sqrt((self.X * self.X) + (self.Y * self.Y)) | |
} | |
func NewSurface(height int, width int) (surface *sdl.Surface) { | |
surface = sdl.SetVideoMode(height, width, 32, 0) | |
if surface == nil { | |
panic(sdl.GetError()) | |
} | |
return | |
} | |
func sdlSetup() (world *World) { | |
if sdl.Init(sdl.INIT_EVERYTHING) != 0 { | |
panic(sdl.GetError()) | |
} | |
sdl.EnableUNICODE(1) | |
sdl.EnableKeyRepeat(25, 25) | |
return | |
} | |
func Quit(world *World) { | |
sdl.Quit() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
maybe you solved but I write this comment for other who start with go and don't know how to add packages, you have to add them with the command go get, usually you find the way to install a package in the repo's readme.