blob: 5f13480aadd82eb1c138cb0c8dc20616f37d8b04 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"image"
"log"
"sort"
"strconv"
"time"
_ "image/jpeg"
_ "image/png"
"hearts/img"
"hearts/logic/card"
"hearts/logic/player"
"hearts/logic/table"
"golang.org/x/mobile/app"
"golang.org/x/mobile/asset"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/event/touch"
"golang.org/x/mobile/exp/f32"
"golang.org/x/mobile/exp/sprite"
"golang.org/x/mobile/exp/sprite/clock"
"golang.org/x/mobile/exp/sprite/glsprite"
"golang.org/x/mobile/gl"
)
var (
startTime = time.Now()
eng = glsprite.Engine()
scene *sprite.Node
cards []*card.Card
backgroundImgs []*img.StaticImg
emptySuitImgs []*img.StaticImg
dropTargets []*img.StaticImg
buttons []*img.StaticImg
curCard *card.Card
numPlayers = 4
// lastMouseXY is in Px: divide by ppp to get Pt
lastMouseXY = []float32{-1, -1}
cardSize = 35
cardWidth = float32(cardSize)
cardHeight = float32(cardSize)
// windowSize is in Pt
windowSize = []float32{-1, -1}
padding = float32(5)
topPadding = float32(7)
bottomPadding = float32(5)
// ppp stands for pixels per pt
ppp float32
// to-do: test and fine-tune these thresholds
swipeMoveThreshold = 70
swipeTimeThreshold = .5
swipeStart time.Time
animSpeedScaler = .1
animRotationScaler = .15
d Direction
)
type Direction string
const (
right Direction = "R"
across Direction = "A"
left Direction = "L"
)
func main() {
app.Main(func(a app.App) {
var sz size.Event
d = right
for e := range a.Events() {
switch e := app.Filter(e).(type) {
case size.Event:
sz = e
oldWidth := windowSize[0]
oldHeight := windowSize[1]
windowSize[0] = float32(sz.WidthPt)
windowSize[1] = float32(sz.HeightPt)
pixelsX := float32(sz.WidthPx)
ppp = pixelsX / windowSize[0]
adjustImgs(oldWidth, oldHeight)
case touch.Event:
onTouch(e, d)
case paint.Event:
onPaint(sz, d)
a.EndPaint(e)
}
}
})
}
func onTouch(t touch.Event, dir Direction) {
switch t.Type.String() {
case "begin":
// i goes from the end backwards so that it checks cards displayed on top of other cards first
for i := len(cards) - 1; i >= 0; i-- {
curCard = cards[i]
if t.X/ppp >= curCard.GetX() &&
t.Y/ppp >= curCard.GetY() &&
t.X/ppp <= curCard.GetWidth()+curCard.GetX() &&
t.Y/ppp <= curCard.GetHeight()+curCard.GetY() {
swipeStart = time.Now()
node := curCard.GetNode()
node.Arranger = nil
lastMouseXY[0] = t.X
lastMouseXY[1] = t.Y
return
} else {
curCard = nil
}
}
for _, b := range buttons {
if t.X/ppp >= b.GetX() &&
t.Y/ppp >= b.GetY() &&
t.X/ppp <= b.GetWidth()+b.GetX() &&
t.Y/ppp <= b.GetHeight()+b.GetY() {
eng.SetSubTex(b.GetNode(), b.GetAlt())
for _, d := range dropTargets {
passCard := d.GetCardHere()
if passCard != nil {
animate(passCard, dir, t)
d.SetCardHere(nil)
}
}
}
}
case "move":
if curCard != nil {
newX := curCard.GetX() + (t.X-lastMouseXY[0])/ppp
newY := curCard.GetY() + (t.Y-lastMouseXY[1])/ppp
width := curCard.GetWidth()
height := curCard.GetHeight()
eng.SetTransform(curCard.GetNode(), f32.Affine{
{width, 0, newX},
{0, height, newY},
})
curCard.SetPos(newX, newY, width, height)
lastMouseXY[0] = t.X
lastMouseXY[1] = t.Y
}
case "end":
if curCard != nil {
successfulDrop := false
for _, d := range dropTargets {
// checking to see if card was dropped onto a drop target
if t.X/ppp >= d.GetX() &&
t.Y/ppp >= d.GetY() &&
t.X/ppp <= d.GetWidth()+d.GetX() &&
t.Y/ppp <= d.GetHeight()+d.GetY() {
lastDroppedCard := d.GetCardHere()
if lastDroppedCard != nil {
resetCardPosition(lastDroppedCard)
}
oldY := curCard.GetInitialY()
suit := curCard.GetSuit()
newX := d.GetX()
newY := d.GetY()
width := curCard.GetWidth()
height := curCard.GetHeight()
eng.SetTransform(curCard.GetNode(), f32.Affine{
{width, 0, newX},
{0, height, newY},
})
curCard.SetPos(newX, newY, width, height)
d.SetCardHere(curCard)
successfulDrop = true
// realign suit the card just left
realignSuit(suit, oldY)
} else {
// checking to see if card was removed from a dop target
if d.GetCardHere() == curCard {
d.SetCardHere(nil)
}
}
}
if !successfulDrop {
resetCardPosition(curCard)
}
}
for _, b := range buttons {
eng.SetSubTex(b.GetNode(), b.GetImage())
}
curCard = nil
}
}
func resetCardPosition(c *card.Card) {
newX := c.GetInitialX()
newY := c.GetInitialY()
eng.SetTransform(c.GetNode(), f32.Affine{
{c.GetWidth(), 0, newX},
{0, c.GetHeight(), newY},
})
c.SetPos(newX, newY, c.GetWidth(), c.GetHeight())
realignSuit(c.GetSuit(), newY)
}
// returns coordinates for images with same width and height but in new positions proportional to the screen
func adjustKeepDimensions(oldX, oldY, oldInitialX, oldInitialY, oldImgWidth, oldImgHeight, oldWindowWidth, oldWindowHeight float32) (float32, float32, float32, float32, float32, float32) {
newX := (oldX+oldImgWidth/2)/oldWindowWidth*windowSize[0] - oldImgWidth/2
newY := (oldY+oldImgHeight/2)/oldWindowHeight*windowSize[1] - oldImgHeight/2
newInitialX := (oldInitialX+oldImgWidth/2)/oldWindowWidth*windowSize[0] - oldImgWidth/2
newInitialY := (oldInitialY+oldImgHeight/2)/oldWindowHeight*windowSize[1] - oldImgHeight/2
return newX, newY, newInitialX, newInitialY, oldImgWidth, oldImgHeight
}
// returns coordinates for images with position, width and height scaled proportional to the screen
func adjustScaleDimensions(oldX, oldY, oldInitialX, oldInitialY, oldImgWidth, oldImgHeight, oldWindowWidth, oldWindowHeight float32) (float32, float32, float32, float32, float32, float32) {
newImgWidth := oldImgWidth / oldWindowWidth * windowSize[0]
newImgHeight := oldImgHeight / oldWindowHeight * windowSize[1]
newX := oldX / oldWindowWidth * windowSize[0]
newY := oldY / oldWindowHeight * windowSize[1]
newInitialX := oldInitialX / oldWindowWidth * windowSize[0]
newInitialY := oldInitialY / oldWindowHeight * windowSize[1]
return newX, newY, newInitialX, newInitialY, newImgWidth, newImgHeight
}
func adjustImgArray(imgs []*img.StaticImg, oldWindowWidth, oldWindowHeight float32) {
for _, s := range imgs {
node := s.GetNode()
oldImgWidth := s.GetWidth()
oldImgHeight := s.GetHeight()
oldX := s.GetX()
oldY := s.GetY()
oldInitialX := s.GetInitialX()
oldInitialY := s.GetInitialY()
newX, newY, newInitialX, newInitialY, newImgWidth, newImgHeight := adjustScaleDimensions(oldX, oldY,
oldInitialX, oldInitialY, oldImgWidth, oldImgHeight, oldWindowWidth, oldWindowHeight)
eng.SetTransform(node, f32.Affine{
{newImgWidth, 0, newX},
{0, newImgHeight, newY},
})
s.SetPos(newX, newY, newImgWidth, newImgHeight)
s.SetInitialPos(newInitialX, newInitialY)
}
}
func adjustImgs(oldWindowWidth, oldWindowHeight float32) {
if windowSize[0] > -1 && oldWindowWidth > -1 {
padding = padding * windowSize[0] / oldWindowWidth
}
for _, c := range cards {
node := c.GetNode()
oldCardWidth := c.GetWidth()
oldCardHeight := c.GetHeight()
oldX := c.GetX()
oldInitialX := c.GetInitialX()
oldY := c.GetY()
oldInitialY := c.GetInitialY()
newX, newY, newInitialX, newInitialY, newCardWidth, newCardHeight := adjustScaleDimensions(oldX, oldY,
oldInitialX, oldInitialY, oldCardWidth, oldCardHeight, oldWindowWidth, oldWindowHeight)
eng.SetTransform(node, f32.Affine{
{newCardWidth, 0, newX},
{0, newCardHeight, newY},
})
c.SetPos(newX, newY, newCardWidth, newCardHeight)
c.SetInitialPos(newInitialX, newInitialY)
}
adjustImgArray(dropTargets, oldWindowWidth, oldWindowHeight)
adjustImgArray(backgroundImgs, oldWindowWidth, oldWindowHeight)
adjustImgArray(buttons, oldWindowWidth, oldWindowHeight)
adjustImgArray(emptySuitImgs, oldWindowWidth, oldWindowHeight)
}
func animate(animCard *card.Card, dir Direction, touch touch.Event) {
node := animCard.GetNode()
startTime := -1
node.Arranger = arrangerFunc(func(eng sprite.Engine, node *sprite.Node, t clock.Time) {
if startTime == -1 {
startTime = int(t)
}
moveSpeed := float32(int(t)-startTime) * float32(animSpeedScaler)
x := animCard.GetX()
y := animCard.GetY()
width := animCard.GetWidth()
height := animCard.GetHeight()
switch dir {
case right:
x = x + moveSpeed
case left:
x = x - moveSpeed
case across:
y = y - moveSpeed
}
animCard.SetPos(x, y, width, height)
position := f32.Affine{
{width, 0, x + width/2},
{0, height, y + height/2},
}
position.Rotate(&position, float32(t)*float32(animRotationScaler))
position.Translate(&position, -.5, -.5)
eng.SetTransform(node, position)
})
}
func onPaint(sz size.Event, dir Direction) {
if scene == nil {
loadPassScreen(dir)
}
gl.ClearColor(1, 1, 1, 1)
gl.Clear(gl.COLOR_BUFFER_BIT)
now := clock.Time(time.Since(startTime) * 60 / time.Second)
eng.Render(scene, now, sz)
}
func newNode() *sprite.Node {
n := &sprite.Node{}
eng.Register(n)
scene.AppendChild(n)
return n
}
func initializeGame() *table.Table {
players := make([]*player.Player, 0)
for i := 0; i < numPlayers; i++ {
players = append(players, player.NewPlayer(i))
}
return table.NewTable(players)
}
func realignSuit(suitName card.Suit, oldY float32) {
cardsToAlign := make([]*card.Card, 0)
for _, c := range cards {
if c.GetSuit() == suitName && c.GetY() == oldY {
cardsToAlign = append(cardsToAlign, c)
}
}
var emptySuitImg *img.StaticImg
switch suitName {
case card.Club:
emptySuitImg = emptySuitImgs[0]
case card.Diamond:
emptySuitImg = emptySuitImgs[1]
case card.Spade:
emptySuitImg = emptySuitImgs[2]
case card.Heart:
emptySuitImg = emptySuitImgs[3]
}
if len(cardsToAlign) == 0 {
eng.SetSubTex(emptySuitImg.GetNode(), emptySuitImg.GetImage())
} else {
eng.SetSubTex(emptySuitImg.GetNode(), emptySuitImg.GetAlt())
}
for i, c := range cardsToAlign {
width := c.GetWidth()
height := c.GetHeight()
diff := float32(len(cardsToAlign))*(padding+width) - (windowSize[0] - padding)
x := padding + float32(i)*(padding+width)
if diff > 0 && i > 0 {
x -= diff * float32(i) / float32(len(cardsToAlign)-1)
}
y := oldY
c.SetPos(x, y, width, height)
c.SetInitialPos(x, y)
eng.SetTransform(c.GetNode(), f32.Affine{
{width, 0, x},
{0, height, y},
})
}
}
func addCard(c *card.Card, texs map[string]sprite.SubTex, numInSuit, clubCount, diamondCount, spadeCount, heartCount int) {
var texKey string
var suitCount float32
var heightScaler float32
switch c.GetSuit() {
case card.Club:
texKey = "Clubs-"
suitCount = float32(clubCount)
heightScaler = 4
case card.Diamond:
texKey = "Diamonds-"
suitCount = float32(diamondCount)
heightScaler = 3
case card.Spade:
texKey = "Spades-"
suitCount = float32(spadeCount)
heightScaler = 2
case card.Heart:
texKey = "Hearts-"
suitCount = float32(heartCount)
heightScaler = 1
}
log.Println(c.GetFace())
log.Println(card.Two)
switch c.GetFace() {
case card.Jack:
texKey += "Jack"
case card.Queen:
texKey += "Queen"
case card.King:
texKey += "King"
case card.Ace:
texKey += "Ace"
default:
texKey += strconv.Itoa(int(c.GetFace()))
}
texKey += ".png"
log.Println(texKey)
n := newNode()
eng.SetSubTex(n, texs[texKey])
c.SetNode(n)
diff := suitCount*(padding+cardWidth) - (windowSize[0] - padding)
x := padding + float32(numInSuit)*(padding+cardWidth)
if diff > 0 && numInSuit > 0 {
x -= diff * float32(numInSuit) / (suitCount - 1)
}
y := windowSize[1] - heightScaler*(cardHeight+padding) - bottomPadding
width := cardWidth
height := cardHeight
c.SetPos(x, y, width, height)
c.SetInitialPos(x, y)
eng.SetTransform(c.GetNode(), f32.Affine{
{width, 0, x},
{0, height, y},
})
}
func addImgWithoutAlt(t sprite.SubTex, x, y, width, height float32) *img.StaticImg {
n := newNode()
eng.SetSubTex(n, t)
eng.SetTransform(n, f32.Affine{
{width, 0, x},
{0, height, y},
})
s := img.NewStaticImg()
s.SetNode(n)
s.SetImage(t)
s.SetPos(x, y, width, height)
s.SetInitialPos(x, y)
return s
}
func addImgWithAlt(t sprite.SubTex, alt sprite.SubTex, x, y, width, height float32, displayImage bool) *img.StaticImg {
n := newNode()
if displayImage {
eng.SetSubTex(n, t)
}
eng.SetTransform(n, f32.Affine{
{width, 0, x},
{0, height, y},
})
s := img.NewStaticImg()
s.SetNode(n)
s.SetImage(t)
s.SetAlt(alt)
s.SetPos(x, y, width, height)
s.SetInitialPos(x, y)
return s
}
func loadPassScreen(dir Direction) {
texs := loadTextures()
scene = &sprite.Node{}
eng.Register(scene)
eng.SetTransform(scene, f32.Affine{
{1, 0, 0},
{0, 1, 0},
})
t := initializeGame()
t.Deal()
cards = t.GetPlayers()[1].GetHand()
dropTargets = make([]*img.StaticImg, 0)
backgroundImgs = make([]*img.StaticImg, 0)
emptySuitImgs = make([]*img.StaticImg, 0)
buttons = make([]*img.StaticImg, 0)
sort.Sort(cardSorter(cards))
clubCount := 0
diamondCount := 0
spadeCount := 0
heartCount := 0
for i := 0; i < len(cards); i++ {
switch cards[i].GetSuit() {
case card.Club:
clubCount++
case card.Diamond:
diamondCount++
case card.Spade:
spadeCount++
case card.Heart:
heartCount++
}
}
suitCounts := []int{clubCount, diamondCount, spadeCount, heartCount}
numSuits := 4
numDropTargets := 3
// adding blue banner for croupier header
image := texs["blue.png"]
headerX := float32(0)
headerY := float32(0)
headerWidth := windowSize[0]
var headerHeight float32
if 2*cardHeight < headerWidth/4 {
headerHeight = 2 * cardHeight
} else {
headerHeight = headerWidth / 4
}
header := addImgWithoutAlt(image, headerX, headerY, headerWidth, headerHeight)
backgroundImgs = append(backgroundImgs, header)
// adding croupier name on top of banner
image = texs["croupierName.png"]
var width float32
var height float32
if headerHeight-topPadding > headerWidth/6 {
width = headerWidth / 2
height = width / 3
} else {
height = 2 * headerHeight / 3
width = height * 3
}
x := headerX + (headerWidth-width)/2
y := headerY + (headerHeight-height+topPadding)/2
headerText := addImgWithoutAlt(image, x, y, width, height)
backgroundImgs = append(backgroundImgs, headerText)
// adding blue background banner for drop targets
topOfHand := windowSize[1] - 5*(cardHeight+padding) - (2 * padding / 5) - bottomPadding
image = texs["blue.png"]
x = float32(0)
passBannerY := topOfHand - (2 * padding)
width = windowSize[0]
height = cardHeight + (4 * padding / 5)
newImg := addImgWithoutAlt(image, x, passBannerY, width, height)
backgroundImgs = append(backgroundImgs, newImg)
// adding drop targets
for i := 0; i < numDropTargets; i++ {
image := texs["white.png"]
width := cardWidth
height := cardHeight
x := windowSize[0]/2 - (width+float32(numDropTargets)*(padding+width))/2 + float32(i)*(padding+width)
y := passBannerY + (2 * padding / 5)
newTarget := addImgWithoutAlt(image, x, y, width, height)
dropTargets = append(dropTargets, newTarget)
}
// adding pass button
pressedImg := texs["passPressed.png"]
unpressedImg := texs["passUnpressed.png"]
width = cardWidth
height = cardHeight / 2
x = windowSize[0]/2 + (float32(numDropTargets)*(padding+width)-width)/2
passY := passBannerY + (2 * padding / 5)
newButton := addImgWithAlt(unpressedImg, pressedImg, x, passY, width, height, true)
buttons = append(buttons, newButton)
// adding arrow below pass button
var a *img.StaticImg
if dir == right {
image := texs["rightArrow.png"]
width := cardWidth
height := cardHeight / 2
x := windowSize[0]/2 + (float32(numDropTargets)*(padding+cardWidth)-width)/2
y := passY + cardHeight/2
a = addImgWithoutAlt(image, x, y, width, height)
} else if dir == left {
image := texs["leftArrow.png"]
width := cardWidth
height := cardHeight / 2
x := windowSize[0]/2 + (float32(numDropTargets)*(padding+cardWidth)-width)/2
y := passY + cardHeight/2
a = addImgWithoutAlt(image, x, y, width, height)
} else {
image := texs["acrossArrow.png"]
width := cardWidth / 4
height := cardHeight / 2
x := windowSize[0]/2 + (float32(numDropTargets)*(padding+cardWidth)-width)/2
y := passY + cardHeight/2
a = addImgWithoutAlt(image, x, y, width, height)
}
backgroundImgs = append(backgroundImgs, a)
// adding gray background banners for each suit
for i := 0; i < numSuits; i++ {
image := texs["gray.jpeg"]
x := float32(0)
y := windowSize[1] - float32(i+1)*(cardHeight+padding) - (2 * padding / 5) - bottomPadding
width := windowSize[0]
height := cardHeight + (4 * padding / 5)
newImg := addImgWithoutAlt(image, x, y, width, height)
backgroundImgs = append(backgroundImgs, newImg)
}
// adding suit image to any empty suit in hand
for i, c := range suitCounts {
var texKey string
switch i {
case 0:
texKey = "Club.png"
case 1:
texKey = "Diamond.png"
case 2:
texKey = "Spade.png"
case 3:
texKey = "Heart.png"
}
image := texs[texKey]
alt := texs["gray.png"]
x := windowSize[0]/2 - cardWidth/3
y := windowSize[1] - float32(4-i)*(cardHeight+padding) + cardHeight/6 - bottomPadding
width := 2 * cardWidth / 3
height := 2 * cardHeight / 3
display := c == 0
newSuitImg := addImgWithAlt(image, alt, x, y, width, height, display)
emptySuitImgs = append(emptySuitImgs, newSuitImg)
}
// adding clubs
for i := 0; i < clubCount; i++ {
addCard(cards[i], texs, i, clubCount, diamondCount, spadeCount, heartCount)
}
// adding diamonds
for i := clubCount; i < clubCount+diamondCount; i++ {
addCard(cards[i], texs, i-clubCount, clubCount, diamondCount, spadeCount, heartCount)
}
// adding spades
for i := clubCount + diamondCount; i < clubCount+diamondCount+spadeCount; i++ {
addCard(cards[i], texs, i-clubCount-diamondCount, clubCount, diamondCount, spadeCount, heartCount)
}
// adding hearts
for i := clubCount + diamondCount + spadeCount; i < clubCount+diamondCount+spadeCount+heartCount; i++ {
addCard(cards[i], texs, i-clubCount-diamondCount-spadeCount, clubCount, diamondCount, spadeCount, heartCount)
}
}
func loadTextures() map[string]sprite.SubTex {
allTexs := make(map[string]sprite.SubTex)
files := []string{"Clubs-2.png", "Clubs-3.png", "Clubs-4.png", "Clubs-5.png", "Clubs-6.png", "Clubs-7.png", "Clubs-8.png",
"Clubs-9.png", "Clubs-10.png", "Clubs-Jack.png", "Clubs-Queen.png", "Clubs-King.png", "Clubs-Ace.png",
"Diamonds-2.png", "Diamonds-3.png", "Diamonds-4.png", "Diamonds-5.png", "Diamonds-6.png", "Diamonds-7.png", "Diamonds-8.png",
"Diamonds-9.png", "Diamonds-10.png", "Diamonds-Jack.png", "Diamonds-Queen.png", "Diamonds-King.png", "Diamonds-Ace.png",
"Spades-2.png", "Spades-3.png", "Spades-4.png", "Spades-5.png", "Spades-6.png", "Spades-7.png", "Spades-8.png",
"Spades-9.png", "Spades-10.png", "Spades-Jack.png", "Spades-Queen.png", "Spades-King.png", "Spades-Ace.png",
"Hearts-2.png", "Hearts-3.png", "Hearts-4.png", "Hearts-5.png", "Hearts-6.png", "Hearts-7.png", "Hearts-8.png",
"Hearts-9.png", "Hearts-10.png", "Hearts-Jack.png", "Hearts-Queen.png", "Hearts-King.png", "Hearts-Ace.png",
"Club.png", "Diamond.png", "Spade.png", "Heart.png", "gray.jpeg", "blue.png", "white.png", "passPressed.png",
"passUnpressed.png", "leftArrow.png", "rightArrow.png", "acrossArrow.png", "croupierName.png"}
for _, f := range files {
a, err := asset.Open(f)
if err != nil {
log.Fatal(err)
}
defer a.Close()
img, _, err := image.Decode(a)
if err != nil {
log.Fatal(err)
}
t, err := eng.LoadTexture(img)
if err != nil {
log.Fatal(err)
}
imgWidth, imgHeight := t.Bounds()
if f == "Club.png" || f == "Diamond.png" || f == "Spade.png" || f == "Heart.png" || f == "rightArrow.png" ||
f == "leftArrow.png" || f == "acrossArrow.png" || f == "passUnpressed.png" || f == "passPressed.png" ||
f == "croupierName.png" {
allTexs[f] = sprite.SubTex{t, image.Rect(1, 1, imgWidth-1, imgHeight-1)}
} else {
allTexs[f] = sprite.SubTex{t, image.Rect(0, 0, imgWidth, imgHeight)}
}
}
return allTexs
}
type arrangerFunc func(e sprite.Engine, n *sprite.Node, t clock.Time)
func (a arrangerFunc) Arrange(e sprite.Engine, n *sprite.Node, t clock.Time) { a(e, n, t) }
type cardSorter []*card.Card
func (cs cardSorter) Len() int {
return len(cs)
}
func (cs cardSorter) Swap(i, j int) {
cs[i], cs[j] = cs[j], cs[i]
}
func (cs cardSorter) Less(i, j int) bool {
if cs[i].GetSuit() == cs[j].GetSuit() {
return cs[i].GetFace() < cs[j].GetFace()
} else {
switch cs[i].GetSuit() {
case card.Club:
return true
case card.Diamond:
return cs[j].GetSuit() != card.Club
case card.Spade:
return cs[j].GetSuit() == card.Heart
case card.Heart:
return false
}
}
return true
}