commit 611c3e25cac0a1d061398675bf2cc61072916b12
Author: Steve <stryan@saintnet.tech>
Date:   Fri Oct 1 13:40:46 2021 -0400

    initial migration

diff --git a/board.go b/board.go
new file mode 100644
index 0000000..e61ff5e
--- /dev/null
+++ b/board.go
@@ -0,0 +1,10 @@
+package tome_game
+
+import . "git.saintnet.tech/tomecraft/tome_lib"
+
+func NewBoard() *Board {
+	return &Board{
+		Sentinal: [4]*Card{NewEmpty(0), NewEmpty(1), NewEmpty(2), NewEmpty(3)},
+		Scourge:  [4]*Card{NewEmpty(0), NewEmpty(1), NewEmpty(2), NewEmpty(3)},
+	}
+}
diff --git a/card.go b/card.go
new file mode 100644
index 0000000..4388234
--- /dev/null
+++ b/card.go
@@ -0,0 +1,24 @@
+package tome_game
+
+import (
+	. "git.saintnet.tech/tomecraft/tome_lib"
+	"github.com/google/uuid"
+)
+
+func NewCard(v, o, p int, id uuid.UUID) *Card {
+	if id == uuid.Nil {
+		id = uuid.New()
+	}
+	return &Card{
+		Type:      CardType(v),
+		BasePower: OraclePower(CardType(v), nil),
+		Power:     OraclePower(CardType(v), nil),
+		Id:        id,
+		Sick:      false,
+		Counters:  0,
+		Owner:     o,
+		Position:  p,
+		Spell:     OracleSpell(CardType(v), nil),
+		Effects:   []*Effect{},
+	}
+}
diff --git a/deck.go b/deck.go
new file mode 100644
index 0000000..eafa9bd
--- /dev/null
+++ b/deck.go
@@ -0,0 +1,19 @@
+package tome_game
+
+import (
+	. "git.saintnet.tech/tomecraft/tome_lib"
+	"github.com/google/uuid"
+)
+
+func NewDeck(owner int) *Deck {
+	cards := []*Card{}
+	for i := 0; i < 3; i++ {
+		for j := 1; j < 9; j++ {
+			cards = append(cards, NewCard(j, owner, -1, uuid.Nil))
+		}
+	}
+	cards = append(cards, NewCard(0, owner, -1, uuid.Nil))
+	return &Deck{
+		Cards: cards,
+	}
+}
diff --git a/game.go b/game.go
new file mode 100644
index 0000000..f3d5340
--- /dev/null
+++ b/game.go
@@ -0,0 +1,398 @@
+package tome_game
+
+import (
+	"fmt"
+	"log"
+	"strconv"
+	"strings"
+
+	. "git.saintnet.tech/tomecraft/tome_lib"
+)
+
+type Game struct {
+	GameBoard      *Board
+	SentinalPlayer *Player
+	ScourgePlayer  *Player
+	SentinalDeck   *Deck
+	ScourgeDeck    *Deck
+	CurrentTurn    int
+	CardBuffer     *Deck
+	CanDraw        bool
+	HasDrawn       bool
+	Status         GameStatus
+}
+
+func NewGame() *Game {
+	deckA := NewDeck(SentinalID)
+	deckB := NewDeck(ScourgeID)
+	deckA.Shuffle()
+	deckB.Shuffle()
+	return &Game{
+		GameBoard:      NewBoard(),
+		SentinalPlayer: NewPlayer("Sentinal", SentinalID),
+		ScourgePlayer:  NewPlayer("Scourge", ScourgeID),
+		SentinalDeck:   deckA,
+		ScourgeDeck:    deckB,
+		CurrentTurn:    0, //start with no turn
+		CardBuffer:     DeckFromCards([]*Card{}),
+		CanDraw:        false,
+		HasDrawn:       false,
+		Status:         StatusLobby,
+	}
+}
+
+func (g *Game) String() string {
+	return fmt.Sprintf("Sen(%v): %v\n\n%v\n\nSco(%v): %v\nStatus:%v Draw:%v Turn:%v\n%v\n", g.SentinalPlayer.Life, g.SentinalPlayer.Hand, g.GameBoard, g.ScourgePlayer.Life, g.ScourgePlayer.Hand, g.Status, g.CanDraw, g.CurrentTurn, g.CardBuffer)
+}
+
+func (g *Game) Parse(cmd *Command) *CommandResult {
+
+	if g.Status == StatusDraw || g.Status == StatusScourgeWin || g.Status == StatusSentinalWin || g.Status == StatusStop {
+		return &CommandResult{
+			PlayerID:     cmd.PlayerID,
+			ResultType:   cmd.Type,
+			StateResult:  NewView(cmd.PlayerID, g),
+			ActionResult: nil,
+			DebugResult:  g,
+		}
+	}
+
+	var state_res *GameView
+	var action_res *Deck
+	var debug_res *Game
+	var res_type CmdType
+
+	if cmd.Type == ActCmd && g.Status == StatusPlaying {
+		action_res = g.PlayerAct(cmd.PlayerID, cmd.Cmd)
+		state_res = nil
+		debug_res = nil
+		res_type = ActCmd
+	} else if cmd.Type == StateCmd {
+		state_res = g.PlayerStateAct(cmd.PlayerID, cmd.Cmd)
+		action_res = nil
+		debug_res = nil
+		res_type = StateCmd
+	} else if cmd.Type == DebugCmd {
+		state_res = nil
+		action_res = nil
+		debug_res = g
+		res_type = DebugCmd
+	} else {
+		state_res = nil
+		action_res = nil
+		debug_res = nil
+		res_type = InvalidCmd
+	}
+	g.StateChanges()
+	return &CommandResult{
+		PlayerID:     cmd.PlayerID,
+		ResultType:   res_type,
+		StateResult:  state_res,
+		ActionResult: action_res,
+		DebugResult:  debug_res,
+	}
+}
+
+func (g *Game) StateChanges() {
+	//check for no actions first
+	if len(g.SentinalPlayer.Hand) == 0 && g.SentinalDeck.Size() == 0 {
+		if g.GameBoard.Empty(SentinalID) {
+			g.Status = StatusScourgeWin
+		}
+	}
+
+	if len(g.ScourgePlayer.Hand) == 0 && g.ScourgeDeck.Size() == 0 {
+		if g.GameBoard.Empty(ScourgeID) {
+			g.Status = StatusSentinalWin
+		}
+	}
+	//reapply card effects
+	g.GameBoard.ResetCards()
+	for i := 0; i < len(g.GameBoard.Sentinal); i++ {
+		OracleTick(g.GameBoard.Sentinal[i], g) //apply effect stacks first
+		OracleTick(g.GameBoard.Scourge[i], g)
+	}
+	for i := 0; i < len(g.GameBoard.Sentinal); i++ {
+		OracleEffect(g.GameBoard.Sentinal[i], g) //activate effects
+		OracleEffect(g.GameBoard.Scourge[i], g)
+	}
+	//check life
+	//this is second on purpose so that it shadows deck out win conditions
+	//if you use your last action to win the game, you should actually win
+	if g.SentinalPlayer.Life < 0 {
+		g.Status = StatusScourgeWin
+	}
+	if g.ScourgePlayer.Life < 0 {
+		g.Status = StatusSentinalWin
+	}
+	if g.ScourgePlayer.Life < 0 && g.SentinalPlayer.Life < 0 {
+		g.Status = StatusDraw
+	}
+}
+
+func (g *Game) PlayerStateAct(id int, cmd string) *GameView {
+	switch cmd {
+	case "d":
+		Debug(g)
+	case "g":
+		//game state
+		return NewView(id, g)
+	case "b":
+		//begin game
+		if id == SentinalID {
+			g.SentinalPlayer.Ready = true
+		} else if id == ScourgeID {
+			g.ScourgePlayer.Ready = true
+		}
+		if g.SentinalPlayer.Ready && g.ScourgePlayer.Ready && g.Status == StatusLobby {
+			g.Status = StatusReady
+			if id == SentinalID {
+				g.SentinalPlayer.Hand = g.SentinalDeck.Cards[len(g.SentinalDeck.Cards)-5 : len(g.SentinalDeck.Cards)]
+				g.SentinalDeck.Cards = g.SentinalDeck.Cards[0 : len(g.SentinalDeck.Cards)-5]
+
+				g.ScourgePlayer.Hand = g.ScourgeDeck.Cards[len(g.ScourgeDeck.Cards)-6 : len(g.ScourgeDeck.Cards)]
+				g.ScourgeDeck.Cards = g.ScourgeDeck.Cards[0 : len(g.ScourgeDeck.Cards)-6]
+				g.CurrentTurn = SentinalID
+			} else {
+				g.SentinalPlayer.Hand = g.SentinalDeck.Cards[len(g.SentinalDeck.Cards)-6 : len(g.SentinalDeck.Cards)]
+				g.SentinalDeck.Cards = g.SentinalDeck.Cards[0 : len(g.SentinalDeck.Cards)-6]
+
+				g.ScourgePlayer.Hand = g.ScourgeDeck.Cards[len(g.ScourgeDeck.Cards)-5 : len(g.ScourgeDeck.Cards)]
+				g.ScourgeDeck.Cards = g.ScourgeDeck.Cards[0 : len(g.ScourgeDeck.Cards)-5]
+				g.CurrentTurn = ScourgeID
+			}
+		}
+	case "s":
+		//start turn
+		if g.Status == StatusReady { //first turn
+			g.Status = StatusPlaying
+		}
+		//skip draw step when 0 life
+		if id == SentinalID && g.SentinalPlayer.Life <= 0 {
+			g.CanDraw = false
+			g.HasDrawn = true
+		} else if id == ScourgeID && g.ScourgePlayer.Life <= 0 {
+			g.CanDraw = false
+			g.HasDrawn = true
+		} else {
+			g.CanDraw = true
+			g.HasDrawn = false
+		}
+		if id != g.CurrentTurn {
+			return nil
+		}
+		if id == SentinalID {
+			for _, v := range g.GameBoard.Sentinal {
+				OracleUpkeep(v, g)
+			}
+		} else {
+			for _, v := range g.GameBoard.Scourge {
+				OracleUpkeep(v, g)
+			}
+		}
+	case "e":
+		//end turn and clean up
+		if id != g.CurrentTurn {
+			return nil
+		}
+		g.CardBuffer = DeckFromCards([]*Card{})
+		if id == SentinalID {
+			for _, v := range g.GameBoard.Sentinal {
+				OracleEndstep(v, g)
+			}
+		} else {
+			for _, v := range g.GameBoard.Scourge {
+				OracleEndstep(v, g)
+			}
+		}
+		if g.CurrentTurn == SentinalID {
+			g.CurrentTurn = ScourgeID
+		} else {
+			g.CurrentTurn = SentinalID
+		}
+	}
+	return NewView(id, g)
+}
+
+func (g *Game) PlayerAct(id int, cmd string) *Deck {
+	if id != g.CurrentTurn {
+		return nil
+	}
+	var curr *Player
+	var opp *Player
+	var currD *Deck
+	if id == g.SentinalPlayer.Id {
+		curr = g.SentinalPlayer
+		opp = g.ScourgePlayer
+		currD = g.SentinalDeck
+	} else {
+		curr = g.ScourgePlayer
+		opp = g.SentinalPlayer
+		currD = g.ScourgeDeck
+	}
+	cmd_s := strings.Split(cmd, " ")
+	if len(cmd_s) < 1 {
+		return nil
+	}
+	switch cmd_s[0] {
+	case "s":
+		//scry: return scry options off top of deck
+		if !g.CanDraw {
+			return nil
+		}
+		if g.CardBuffer.Size() <= 0 {
+			g.CardBuffer = DeckFromCards(currD.Scry(curr.Life))
+		}
+		return g.CardBuffer
+	case "d":
+		//draw: return player hand
+		if !g.CanDraw {
+			return nil
+		}
+		if len(cmd_s) != 2 || !g.CanDraw {
+			return nil
+		}
+		if curr.Life == 0 {
+			return DeckFromCards(curr.Hand)
+		}
+		x_i, err := strconv.Atoi(cmd_s[1])
+		if err != nil {
+			panic(err)
+		}
+		if x_i > 2 || x_i < 0 {
+			return nil
+		}
+		x := g.CardBuffer.Cards[x_i]
+		if x.Owner != g.CurrentTurn {
+			log.Println("drew a card from our deck that isn't our own")
+		}
+		g.CardBuffer.Cards = append(g.CardBuffer.Cards[:x_i], g.CardBuffer.Cards[x_i+1:]...)
+		currD.Bottom(g.CardBuffer.Cards)
+		curr.Hand = append(curr.Hand, x)
+		g.CanDraw = false
+		g.HasDrawn = true
+		return DeckFromCards(curr.Hand)
+
+	case "m":
+		//move: return player board or [] if invalid
+		if len(cmd_s) != 3 {
+			return nil
+		}
+		if !g.HasDrawn {
+			return nil
+		}
+		x_i, err := strconv.Atoi(cmd_s[1])
+		if err != nil {
+			log.Println(err)
+			return nil
+		}
+		y_i, err := strconv.Atoi(cmd_s[2])
+		if err != nil {
+			log.Println(err)
+		}
+		if g.GameBoard.CanMove(g.CurrentTurn, x_i, y_i) {
+			OracleMove(g.GameBoard.GetCard(g.CurrentTurn, x_i), x_i, y_i, g)
+			g.GameBoard.Move(g.CurrentTurn, x_i, y_i)
+			return DeckFromCards(g.GameBoard.GetRow(g.CurrentTurn))
+		} else {
+			return DeckFromCards([]*Card{})
+		}
+	case "a":
+		//attack
+		if len(cmd_s) != 3 {
+			return nil
+		}
+		if !g.HasDrawn {
+			return nil
+		}
+		x_i, err := strconv.Atoi(cmd_s[1])
+		if err != nil {
+			log.Println(err)
+			return nil
+		}
+		y_i, err := strconv.Atoi(cmd_s[2])
+		if err != nil {
+			log.Println(err)
+			return nil
+		}
+		if g.GameBoard.CanAttack(g.CurrentTurn, x_i, y_i) {
+			OracleAttack(g.GameBoard.GetCard(g.CurrentTurn, x_i), g)
+			res := g.GameBoard.Attack(g.CurrentTurn, x_i, y_i)
+			var aid, did int
+			if id == SentinalID {
+				aid = SentinalID
+				did = ScourgeID
+			} else {
+				aid = ScourgeID
+				did = SentinalID
+			}
+			attacker := g.GameBoard.GetRow(aid)[x_i]
+			defender := g.GameBoard.GetRow(did)[y_i]
+			switch res {
+			case 1:
+				opp.Life = opp.Life - 1
+			case 2:
+				OracleLeaves(defender, g)
+				g.GameBoard.Remove(defender)
+			case 3:
+				OracleLeaves(attacker, g)
+				g.GameBoard.Remove(attacker)
+			case 4:
+				OracleLeaves(attacker, g)
+				OracleLeaves(defender, g)
+				g.GameBoard.Remove(attacker)
+				g.GameBoard.Remove(defender)
+
+			}
+			return DeckFromCards(g.GameBoard.GetRow(g.CurrentTurn))
+
+		} else {
+			log.Println("can't attack")
+			return DeckFromCards([]*Card{})
+		}
+	case "p":
+		//play: return player boad or [] if invalid
+		if len(cmd_s) != 3 {
+			fmt.Println("not enough arguments")
+			return nil
+		}
+		if !g.HasDrawn {
+			return nil
+		}
+		x_i, err := strconv.Atoi(cmd_s[1])
+		if err != nil {
+			log.Println(err)
+			return nil
+		}
+		y_i, err := strconv.Atoi(cmd_s[2])
+		if err != nil {
+			log.Println(err)
+			return nil
+		}
+		if x_i < 0 || x_i > len(curr.Hand) {
+			return nil
+		}
+		card := curr.Hand[x_i]
+		shouldPlace := true
+		if g.GameBoard.CanPlay(g.CurrentTurn, card, y_i) {
+			shouldPlace = OracleCast(card, g)
+			placed := g.GameBoard.Play(g.CurrentTurn, card, y_i, shouldPlace)
+			if placed {
+				OracleEnters(card, g)
+			}
+			curr.Hand = append(curr.Hand[:x_i], curr.Hand[x_i+1:]...)
+			return DeckFromCards(g.GameBoard.GetRow(g.CurrentTurn))
+		} else {
+			fmt.Println("couldn't play")
+			return DeckFromCards([]*Card{})
+		}
+	default:
+		fmt.Println("Invalid act command")
+		return nil
+	}
+	return nil
+}
+
+func Debug(g *Game) {
+	return
+}
diff --git a/game_view.go b/game_view.go
new file mode 100644
index 0000000..b29abbf
--- /dev/null
+++ b/game_view.go
@@ -0,0 +1,36 @@
+package tome_game
+
+import (
+	. "git.saintnet.tech/tomecraft/tome_lib"
+)
+
+func NewView(id int, g *Game) *GameView {
+	if id == SentinalID {
+		return &GameView{
+			Board:         g.GameBoard,
+			Player:        g.SentinalPlayer,
+			DeckSize:      g.SentinalDeck.Size(),
+			EnemyLife:     g.ScourgePlayer.Life,
+			EnemyDeckSize: g.ScourgeDeck.Size(),
+			EnemyHandSize: len(g.ScourgePlayer.Hand),
+			CurrentTurn:   g.CurrentTurn,
+			CanDraw:       g.CanDraw,
+			HasDrawn:      g.HasDrawn,
+			Status:        g.Status,
+		}
+	} else {
+		return &GameView{
+			Board:         g.GameBoard,
+			Player:        g.ScourgePlayer,
+			DeckSize:      g.ScourgeDeck.Size(),
+			EnemyLife:     g.SentinalPlayer.Life,
+			EnemyDeckSize: g.SentinalDeck.Size(),
+			EnemyHandSize: len(g.SentinalPlayer.Hand),
+			CurrentTurn:   g.CurrentTurn,
+			CanDraw:       g.CanDraw,
+			HasDrawn:      g.HasDrawn,
+			Status:        g.Status,
+		}
+
+	}
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..8620fab
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,10 @@
+module tome_game
+
+go 1.16
+replace git.saintnet.tech/tomecraft/tome_lib => ../tome_lib
+
+require (
+	git.saintnet.tech/tomecraft/tome_lib v0.1.0 // indirect
+	github.com/google/uuid v1.3.0 // indirect
+)
+
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..be95d08
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,4 @@
+git.saintnet.tech/tomecraft/tome_lib v0.1.0 h1:c+AMtXgLKUFErrYFx7g8M2zVcrHUwLRY18v/+N8nn2I=
+git.saintnet.tech/tomecraft/tome_lib v0.1.0/go.mod h1:Jekqa9ojNDOrcO1aL0IWKuhCQSE5+MNHVcYtTWA6uko=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
diff --git a/oracle.go b/oracle.go
new file mode 100644
index 0000000..43506c2
--- /dev/null
+++ b/oracle.go
@@ -0,0 +1,158 @@
+package tome_game
+
+import (
+	"log"
+
+	. "git.saintnet.tech/tomecraft/tome_lib"
+)
+
+func OracleUpkeep(c *Card, g *Game) {
+	switch c.Type {
+	case Eight:
+		c.Sick = true
+		if c.Counters >= 3 {
+			g.GameBoard.Remove(c)
+		}
+	default:
+		c.Sick = false
+	}
+	return
+}
+
+func OracleSpell(c CardType, g *Game) bool {
+	switch c {
+	case Valk:
+		return true
+	default:
+		return false
+	}
+}
+
+func OracleCast(c *Card, g *Game) bool {
+	switch c.Type {
+	case Valk:
+		g.GameBoard = NewBoard()
+		return false
+	default:
+		return true
+	}
+}
+
+func OracleEnters(c *Card, g *Game) {
+	c.Sick = true
+	switch c.Type {
+	case Ace:
+		c.Sick = false
+		return
+	case Four:
+		AddEffect(c, &Effect{c.Id, c.Id, 2})
+	case Eight:
+		c.Counters = 0
+	}
+}
+
+func OracleTick(c *Card, g *Game) {
+	if c.Type == EmptyValue {
+		return
+	}
+	row := g.GameBoard.GetRow(c.Owner)
+
+	switch c.Type {
+	case Two:
+		//+1 to all
+		for _, v := range row {
+			if v.Id != c.Id {
+				AddEffect(v, &Effect{c.Id, v.Id, 1})
+			}
+		}
+	case Three:
+		//+1 around it
+		if c.Position-1 >= 0 {
+			AddEffect(row[c.Position-1], &Effect{c.Id, row[c.Position-1].Id, 1})
+		}
+		if c.Position+1 <= 3 {
+			AddEffect(row[c.Position+1], &Effect{c.Id, row[c.Position+1].Id, 1})
+		}
+	}
+}
+
+func OracleLeaves(c *Card, g *Game) {
+	if c.Empty() {
+		return
+	}
+	row := g.GameBoard.GetRow(c.Owner)
+	switch c.Type {
+	case Two:
+		//remove +1 to all
+		for _, v := range row {
+			RemoveEffect(c.Id, v)
+		}
+	case Three:
+		//+1 around it
+		if c.Position-1 >= 0 {
+			RemoveEffect(c.Id, row[c.Position-1])
+		}
+		if c.Position+1 <= 3 {
+			RemoveEffect(c.Id, row[c.Position+1])
+		}
+
+	}
+	return
+}
+
+func OracleEndstep(c *Card, g *Game) {
+	switch c.Type {
+	case Eight:
+		c.Counters = c.Counters + 1
+	}
+	return
+}
+
+func OraclePower(c CardType, g *Game) int {
+	return int(c)
+}
+
+func OracleMove(c *Card, src, dest int, g *Game) {
+	c.Sick = true
+	switch c.Type {
+	case Three:
+		row := g.GameBoard.GetRow(c.Owner)
+		if src-1 >= 0 {
+			RemoveEffect(c.Id, row[src-1])
+		}
+		if src+1 <= 3 {
+			RemoveEffect(c.Id, row[src-1])
+		}
+	}
+}
+
+func OracleAttack(c *Card, g *Game) {
+	c.Sick = true
+}
+
+func OracleEffect(c *Card, g *Game) {
+	if c.Empty() {
+		return
+	}
+	for _, e := range c.Effects {
+		switch e.ID {
+		case 0:
+			log.Println("dummy effect applied. probably a bug")
+		case 1:
+			c.Power = c.Power + 1
+		case 2:
+			if c.Owner == SentinalID {
+				g.CardBuffer = DeckFromCards(g.SentinalDeck.Scry(1))
+			} else if c.Owner == ScourgeID {
+				g.CardBuffer = DeckFromCards(g.SentinalDeck.Scry(1))
+			} else {
+				log.Println("card draw effect was played but with no owner?")
+			}
+			g.CanDraw = true
+			g.HasDrawn = false
+			RemoveEffect(e.Owner, c)
+		default:
+			log.Println("wrong effect type")
+		}
+	}
+}
diff --git a/player.go b/player.go
new file mode 100644
index 0000000..3d095b7
--- /dev/null
+++ b/player.go
@@ -0,0 +1,15 @@
+package tome_game
+
+import (
+	. "git.saintnet.tech/tomecraft/tome_lib"
+)
+
+func NewPlayer(name string, id int) *Player {
+	return &Player{
+		Name:  name,
+		Id:    id,
+		Hand:  []*Card{},
+		Life:  3,
+		Ready: false,
+	}
+}