Skip to content

Commit

Permalink
first working game session
Browse files Browse the repository at this point in the history
  • Loading branch information
mechmind committed Sep 30, 2013
1 parent a1627de commit 654d763
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 45 deletions.
84 changes: 78 additions & 6 deletions client/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,25 @@ package main

import (
"log"
"os"
"time"
"math/rand"

"github.com/mechmind/ttti-server/message"
"github.com/mechmind/ttti-server/client/game"
"github.com/mechmind/ttti-server/game"
)

type Gamer struct {
game *game.Game
glyph game.Glyph
}

func runGame(c *Client) error {
// init rand
rand.Seed(time.Now().UnixNano())

c.Start()
g := game.NewGame(c.glyph[0])
gamer := &Gamer{nil, game.Glyph(c.glyph[0])}
for msg := range c.connection.Read {
log.Println("game: recieved message: ", msg)

Expand All @@ -19,24 +29,86 @@ func runGame(c *Client) error {
c.connection.Write <- pong
} else if msg.GetType() == "pong" {
} else {
handleMessage(g, msg)
err := handleMessage(c, gamer, msg)
if err != nil {
log.Println("error handling message: ", err)
}
}
}
return nil
}

func handleMessage(game *game.Game, msg message.Message) error {
func handleMessage(client *Client, gamer *Gamer, msg message.Message) error {
var err error
switch msg.GetType() {
case "game-state":
// load state
state := msg.(*message.MsgGameState)
field := state.Field
turn := game.Glyph(state.Turn[0])
gamer.game, err = game.LoadGame(turn, state.TurnSquare, field)
if err != nil {
return err
}
//debug
gamer.game.DumpTo(os.Stdout)
// if our turn, make it
if turn == gamer.glyph {
makeTurn(client, gamer)
}
case "turn":
// opponent made turn
// someone made turn
turn := msg.(*message.MsgTurn)
_, _, _, err := gamer.game.MakeTurn(game.Glyph(turn.Glyph[0]), turn.Coord)
if err != nil {
log.Fatal("turn: server sent invalid turn: ", err, turn)
}

gamer.game.DumpTo(os.Stdout)

// if our turn, make it
if game.Glyph(turn.NextGlyph[0]) == gamer.glyph {
makeTurn(client, gamer)
}
case "game-over":
// game is over
won := msg.(*message.MsgGameOver)
gamer.game.DumpTo(os.Stdout)
if game.Glyph(won.Winner[0]) == gamer.glyph {
log.Fatalf("game is over! I WON!!!")
} else {
log.Fatalf("game is over! I loose...")
}
case "error":
// got error
log.Fatal("got error from server: ", msg)
default:
log.Println("Unknown message: ", msg)
log.Println("Unknown message: ", msg.GetType(), msg)
}
return nil
}

func makeTurn(client *Client, gamer *Gamer) {
// take random free cell from given square
sq := gamer.game.GetSquare(gamer.game.GetTurnId())
free := make([]int, game.BLOCK_SIZE)
var nextIdx int
for idx, cell := range sq {
if cell == game.EMPTY_GLYPH {
free[nextIdx] = idx
nextIdx++
}
}
free = free[:nextIdx]
if len(free) == 0 {
log.Fatalln("makeTurn: WTF? No free cells")
}

idx := free[rand.Intn(len(free))]
coord := gamer.game.GetTurnId() * game.BLOCK_SIZE + idx

msg := &message.MsgMakeTurn{"make-turn", coord, string(byte(gamer.glyph))}
log.Printf("Making turn: %c at %d [%d,%d]", byte(gamer.glyph), coord, gamer.game.GetTurnId(),
idx)
client.connection.Write <- msg
}
2 changes: 1 addition & 1 deletion client/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (c *Client) handshake(socket *net.TCPConn) (*connection.PlayerConnection, e
if hello.GetType() != "hello" {
switch hello.GetType() {
case "error":
errmsg := hello.(message.MsgError)
errmsg := hello.(*message.MsgError)
return nil, errors.New("server kicks us with error: " + errmsg.Message)
default:
return nil, errors.New("server going mad! Type is: " + hello.GetType())
Expand Down
176 changes: 161 additions & 15 deletions game/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package game

import (
"errors"
"fmt"
"io"
"os"
"log"
)


Expand All @@ -14,6 +18,7 @@ const (
P1_GLYPH = Glyph('X')
P2_GLYPH = Glyph('0')
EMPTY_GLYPH = Glyph(' ')
DRAW_GLYPH = Glyph('=')
)

const SIZE = 3
Expand All @@ -22,6 +27,7 @@ const TOTAL_CELLS = BLOCK_SIZE * BLOCK_SIZE

type Game struct {
turn Glyph
turnSquare int
field *Field
}

Expand All @@ -36,36 +42,52 @@ type Glyph byte

func checkWinner(g1, g2, g3 Glyph) (bool, Glyph) {
if g1 == g2 && g2 == g3 {
if g1 == EMPTY_GLYPH {
return false, g1
}
return true, g1
} else {
return false, EMPTY_GLYPH
}
}

func (b BigSquare) checkWinner() Glyph {
// check rows
func (b BigSquare) CheckWinner() Glyph {
// check columns
for i := 0; i < SIZE; i++ {
if won, winner := checkWinner(b[i], b[i + SIZE], b[i + SIZE * 2]); won {
log.Println("won on column", i)
return winner
}
}
// check columns
// check rows
for i := 0; i < SIZE; i++ {
if won, winner := checkWinner(b[i * SIZE], b[i * SIZE + 1], b[i * SIZE + 2]); won {
log.Println("won on row", i)
return winner
}
}
// check diagonals
if won, winner := checkWinner(b[0], b[5], b[9]); won {
if won, winner := checkWinner(b[0], b[4], b[8]); won {
log.Println("won on 1 diag")
return winner
}
if won, winner := checkWinner(b[3], b[5], b[6]); won {
if won, winner := checkWinner(b[2], b[4], b[6]); won {
log.Println("won on 2 diag")
return winner
}

return EMPTY_GLYPH
}

func (b BigSquare) HasEmpty() bool {
for _, cell := range b {
if cell == EMPTY_GLYPH {
return true
}
}
return false
}

func NewField() *Field {
field := Field{make([]BigSquare, BLOCK_SIZE), make([]Glyph, BLOCK_SIZE)}
for i := 0; i < BLOCK_SIZE; i++ {
Expand All @@ -74,36 +96,160 @@ func NewField() *Field {
field.squares[i][j] = EMPTY_GLYPH
}
}

for i := 0; i < BLOCK_SIZE; i++ {
field.won[i] = EMPTY_GLYPH
}

return &field
}

func parseField(state string) (*Field, error) {
field := NewField()
for i := 0; i < BLOCK_SIZE; i++ {
for j := 0; j < BLOCK_SIZE; j++ {
cell := Glyph(state[i * BLOCK_SIZE + j])
if cell != P1_GLYPH && cell != P2_GLYPH && cell != EMPTY_GLYPH {
return nil, fmt.Errorf("game: invalid field symbol at %d", i * BLOCK_SIZE + j)
}
field.squares[i][j] = cell
}
}
return field, nil
}

func NewGame() *Game {
return &Game{P1_GLYPH, NewField()}
return &Game{P1_GLYPH, 0, NewField()} // FIXME: starting square
}

func LoadGame(turn Glyph, turnSquare int, state string) (*Game, error) {
if turn != P1_GLYPH && turn != P2_GLYPH {
return nil, errors.New("game: invalid turn in LoadGame")
}
if len(state) != TOTAL_CELLS {
return nil, errors.New("game: invalid state length")
}

if turnSquare < 0 || turnSquare >= BLOCK_SIZE {
return nil, errors.New("game: invalid turn square")
}

field, err := parseField(state)
if err != nil {
return nil, err
}
return &Game{turn, turnSquare, field}, nil
}

func (g *Game) MakeTurn(gl Glyph, pos int) (winner Glyph, err error) {
winner = g.field.won.checkWinner()
func (g *Game) MakeTurn(gl Glyph, pos int) (next Glyph, nextSquare int, winner Glyph, err error) {
winner = g.field.won.CheckWinner()
if winner != EMPTY_GLYPH {
return winner, errors.New("game: game over, no turns")
err = errors.New("game: game over, no turns, winner is " + string(byte(winner)))
//err = fmt.Errorf("game: game over, no turns, winner is '%b'", winner)
return
}
if g.turn != gl {
return EMPTY_GLYPH, errors.New("game: invalid turn")
err = errors.New("game: invalid turn")
return
}

block, cell := g.splitPos(pos)
if block != g.turnSquare {
err = fmt.Errorf("game: invalud turn square, expected '%d', got '%d'", g.turnSquare, block)
//err = errors.New("game: invalid turn square")
return
}

if g.field.squares[block][cell] != EMPTY_GLYPH {
g.DumpTo(os.Stdout)
//err = errors.New("game: invalid turn - cell already occupied")
return
}

g.field.squares[block][cell] = gl
g.field.won[block] = g.field.squares[block].checkWinner()
return g.field.won.checkWinner(), nil
g.field.won[block] = g.field.squares[block].CheckWinner()

if g.field.squares[cell].HasEmpty() {
nextSquare = cell
} else {
// if no free squares, its a gameover
nextSquare = -1
// find square with free cells
for idx, sq := range g.field.squares {
if sq.HasEmpty() {
nextSquare = idx
break
}
}
}

if gl == P1_GLYPH {
next = P2_GLYPH
} else {
next = P1_GLYPH
}

winner = g.field.won.CheckWinner()
if winner != EMPTY_GLYPH {
next = EMPTY_GLYPH
nextSquare = -1
}

if winner == EMPTY_GLYPH && nextSquare == -1 {
// no winner and no available blocks -> draw
winner = DRAW_GLYPH
}

g.turnSquare = nextSquare
g.turn = next
return
}

func (g *Game) splitPos(pos int) (block int, cell int) {
return pos % BLOCK_SIZE, pos / BLOCK_SIZE
return pos / BLOCK_SIZE, pos % BLOCK_SIZE
}

func (g *Game) GetStatus() (field []Glyph, turn Glyph, win Glyph) {
func (g *Game) GetStatus() (field []Glyph, turn Glyph, turnSquare int, win Glyph) {
field = make([]Glyph, TOTAL_CELLS)
for i := 0; i < BLOCK_SIZE; i++ {
copy(field[BLOCK_SIZE * i:], g.field.squares[i])
}
return field, g.turn, g.field.won.checkWinner()
return field, g.turn, g.turnSquare, g.field.won.CheckWinner()
}

func (g *Game) GetSquare(id int) BigSquare {
square := make(BigSquare, BLOCK_SIZE)
copy(square, g.field.squares[id])
return square
}

func (g *Game) GetTurnId() int {
return g.turnSquare
}

func (g *Game) DumpTo (w io.Writer) error {
var dataBuf = []byte("| | | |\n")
var lineBuf = []byte("+---+---+---+\n")

for line := 0; line < BLOCK_SIZE + 4; line++ {
if line % 4 == 0 {
_, err := w.Write(lineBuf)
if err != nil {
return err
}
} else {
blockLevel := line / 4 * 3
rowLevel := (line - 1) % 4 * 3
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
dataBuf[i * 4 + 1 + j] = byte(g.field.squares[i + blockLevel][j + rowLevel])
}
}
_, err := w.Write(dataBuf)
if err != nil {
return err
}
}
}
return nil
}
Loading

0 comments on commit 654d763

Please sign in to comment.