diff --git a/TODO.md b/TODO.md
index 8f53db3..7c1e903 100644
--- a/TODO.md
+++ b/TODO.md
@@ -118,3 +118,13 @@ front
+- Sounds
+
+ - cancel
+ - hit stock
+ - recycle stock
+ - hover stack gap
+ - drop card 1 2 3
+ - drop foundation
+ - undo
+
diff --git a/public/audio/cancel.wav b/public/audio/cancel.wav
new file mode 100755
index 0000000..05fffd1
Binary files /dev/null and b/public/audio/cancel.wav differ
diff --git a/public/audio/drop.wav b/public/audio/drop.wav
new file mode 100755
index 0000000..b4722cf
Binary files /dev/null and b/public/audio/drop.wav differ
diff --git a/public/audio/hit.wav b/public/audio/hit.wav
new file mode 100755
index 0000000..f38cebd
Binary files /dev/null and b/public/audio/hit.wav differ
diff --git a/public/audio/recycle.wav b/public/audio/recycle.wav
new file mode 100755
index 0000000..d570fff
Binary files /dev/null and b/public/audio/recycle.wav differ
diff --git a/public/audio/undo2.wav b/public/audio/undo2.wav
new file mode 100755
index 0000000..c6eeeb2
Binary files /dev/null and b/public/audio/undo2.wav differ
diff --git a/src/showcase.ts b/src/showcase.ts
index 8bc1ee7..feef498 100644
--- a/src/showcase.ts
+++ b/src/showcase.ts
@@ -457,6 +457,57 @@ export class Card extends Play {
}
+export class CardDropTarget extends Play {
+
+
+ _on_drop?: DropHook
+ bind_drop(e?: DropHook) {
+ this._on_drop = e
+ }
+
+ _will_hover!: boolean
+ _will_hover_end!: boolean
+
+ anim!: Anim
+
+ _init() {
+
+ this.anim = this._make(Anim, Vec2.make(0, 0), { name: 'card' })
+ this.anim.origin = Vec2.make(88, 120)
+
+ let self = this
+ this.make(Clickable, Vec2.make(16, 16).sub(this.anim.origin), {
+ rect: Rect.make(0, 0, 170, 210),
+ on_hover() {
+ if (self._on_drop) {
+ self._will_hover = true
+ return true
+ }
+ return false
+ },
+ on_hover_end() {
+ self._will_hover_end = true
+ },
+ on_drop() {
+ if (self._on_drop) {
+ self._on_drop()
+ }
+ }
+ })
+ }
+
+ _update() {
+
+ if (this._will_hover) {
+ this._will_hover = false
+ }
+
+ if (this._will_hover_end) {
+ this._will_hover_end = false
+ }
+
+ }
+}
let i = 0
@@ -548,7 +599,7 @@ export class Stack extends Play {
}
remove_cards(n: number) {
- let cards = this.cards.splice(-n)
+ let cards = this.cards.splice(this.cards.length - n, this.cards.length)
this._reposition()
return cards
}
@@ -685,6 +736,7 @@ export class Tableu extends Play {
this.fronts.top_card?.bind_drop(() => {
self.data.on_front_drop()
})
+ this.open_drop_target()
}
remove_fronts(i: number) {
@@ -703,6 +755,8 @@ export class Tableu extends Play {
this.fronts.top_card?.bind_drop(() => {
self.data.on_front_drop()
})
+
+ this.open_drop_target()
return cards
}
@@ -723,6 +777,14 @@ export class Tableu extends Play {
})
}
+ get empty() {
+ return this.fronts.length === 0 && this.backs.length === 0
+ }
+
+ open_drop_target() {
+ this.drop_target.visible = this.empty
+ }
+
flip_back() {
let [card] = this.fronts.remove_cards(1)
card.flip_back()
@@ -733,10 +795,19 @@ export class Tableu extends Play {
backs!: Stack
fronts!: Stack
+ drop_target!: CardDropTarget
_init() {
+
+ this.drop_target = this.make(CardDropTarget, Vec2.make(0, 0), {})
+ this.drop_target.bind_drop(() => {
+ this.data.on_front_drop()
+ })
+
this.backs = this.make(Stack, Vec2.make(0, 0), { h: 33 })
this.fronts = this.make(Stack, Vec2.make(0, 0), {})
+
+ this.open_drop_target()
}
diff --git a/src/solitaire_game.ts b/src/solitaire_game.ts
index 16b0113..97ed60b 100644
--- a/src/solitaire_game.ts
+++ b/src/solitaire_game.ts
@@ -3,11 +3,17 @@ import { Rect, Vec2 } from 'blah'
import { Play } from './play'
import { Anim } from './anim'
import { Clickable } from './game'
-import { Stack, Card, Cards, Tableu, DragStack } from './showcase'
+import { CardDropTarget, Stack, Card, Cards, Tableu, DragStack } from './showcase'
import { Dealer } from './solitaire'
import { BackRes, make_solitaire_back } from './solitaire_back'
-import { n_seven, GamePov, Card as OCard, Solitaire, SolitairePov, IMove, IMoveType } from 'lsolitaire'
-import { HitStock, Recycle, TableuToTableu } from 'lsolitaire'
+import { n_four, n_seven, GamePov, Card as OCard, Solitaire, SolitairePov, IMove, IMoveType } from 'lsolitaire'
+import { HitStock, Recycle,
+ TableuToTableu,
+ WasteToTableu,
+ WasteToFoundation,
+ TableuToFoundation,
+ FoundationToTableu,
+} from 'lsolitaire'
export type TableuDrag = {
tableu: number,
@@ -15,10 +21,16 @@ export type TableuDrag = {
}
export type WasteDrag = 'waste'
-export type FoundationDrag = 'foundation'
+export type FoundationDrag = {
+ foundation: number
+}
export type DragSource = TableuDrag | WasteDrag | FoundationDrag
+const isFoundationDragSource = (_: DragSource): _ is FoundationDrag => {
+ return (typeof _ === 'object' && (_ as FoundationDrag).foundation !== undefined)
+}
+
type RecycleData = {
on_recycle: () => void
@@ -50,6 +62,7 @@ class RecycleView extends Play {
this.make(Clickable, Vec2.make(20, 20), {
rect: Rect.make(0, 0, 140, 160),
on_hover() {
+
if (anim._animation !== 'disabled') {
anim.play('hover')
}
@@ -76,7 +89,8 @@ const reverse_forEach = (a: Array, f: (_: A) => void) => {
type StockData = {
on_hit: () => void,
- on_recycle: () => void
+ on_recycle: () => void,
+ on_front_drag: (e: Vec2) => void
}
class Stock extends Play {
@@ -105,8 +119,13 @@ class Stock extends Play {
add_waste(cards: Array) {
+ cards.forEach(_ => {
+ _.bind_drop(undefined)
+ _.bind_drag(undefined)
+ })
this.waste.add_cards(cards)
cards.forEach((card, i) => card.flip_front())
+ this.bind_new_front()
}
@@ -115,6 +134,18 @@ class Stock extends Play {
this.stock.add_cards(cards)
}
+ remove_waste(n: number) {
+ return this.waste.remove_cards(n)
+ }
+
+
+ bind_new_front() {
+ this.waste.cards.forEach(_ => _.bind_drag(undefined))
+ this.waste.top_card?.bind_drag((e: Vec2) => {
+ this.data.on_front_drag(e)
+ })
+ }
+
undo_hit(ocards: Array, owaste: Array) {
let waste_to_stock = this.waste.remove_cards(ocards.length)
@@ -127,6 +158,7 @@ class Stock extends Play {
})
waste_to_stock.forEach(card => card.flip_back())
this.stock.add_cards(waste_to_stock)
+ this.bind_new_front()
}
hit(ocards: Array) {
@@ -137,10 +169,18 @@ class Stock extends Play {
let waste = this.waste.remove_cards(this.waste.cards.length)
+ waste.forEach(_ => {
+ _.flip_back()
+ _.bind_drag(undefined)
+ })
this.waste_hidden.add_cards(waste)
this.waste.add_cards(cards)
reverse_forEach(this.waste_hidden.cards, _ => _.send_back())
+
+ this.bind_new_front()
+
+ Sound.play('hit')
}
recycle() {
@@ -154,6 +194,7 @@ class Stock extends Play {
cards.forEach(card => card.send_front())
this.stock.add_cards(cards)
+ Sound.play('recycle')
}
@@ -195,6 +236,40 @@ class Stock extends Play {
}
+type FoundationData = {
+ on_front_drop: () => void,
+ on_front_drag: (e: Vec2) => void
+}
+class Foundation extends Play {
+
+ get data() {
+ return this._data as FoundationData
+ }
+
+ foundation!: Stack
+ drop_target!: CardDropTarget
+
+
+ add_cards(cards: Array) {
+ this.foundation.add_cards(cards)
+ this.foundation.top_card?.bind_drag(e => this.data.on_front_drag(e))
+ }
+
+ remove_cards(n: number) {
+ return this.foundation.remove_cards(n)
+ }
+
+ _init() {
+
+ this.drop_target = this.make(CardDropTarget, Vec2.make(0, 0), {})
+ this.drop_target.bind_drop(() => {
+ this.data.on_front_drop()
+ })
+
+ this.foundation = this.make(Stack, Vec2.make(0, 0), { h: 0 })
+
+ }
+}
export class SolitaireGame extends Play {
@@ -205,6 +280,7 @@ export class SolitaireGame extends Play {
dealer!: Dealer
stock!: Stock
tableus!: Array
+ foundations!: Array
dragging?: DragStack
drag_source?: DragSource
@@ -234,23 +310,7 @@ export class SolitaireGame extends Play {
rect: Rect.make(0, 0, 0, 0),
on_up() {
if (self.dragging && !self.dragging.waiting) {
- let cards = self.dragging.lerp_release()
-
- if (self.drag_source === 'waste') {
- } else if (self.drag_source === 'foundation') {
- } else {
-
- let { tableu, i } = self.drag_source!
-
- self.tableus[tableu].add_fronts(cards)
- }
-
- self.dragging.dispose()
- self.dragging = undefined
- Sound.play('ding')
- cards[0].after_ease(() => {
- self.cards.shadow_group = undefined
- })
+ self._release_cancel_drag()
}
}
})
@@ -273,6 +333,25 @@ export class SolitaireGame extends Play {
this.stock = this.make(Stock, Vec2.make(stock_x, stock_y), {
on_hit() {
self.cmd(HitStock)
+ },
+ on_front_drag(v: Vec2) {
+ if (self.dragging) {
+ self.dragging.drag(v)
+ } else {
+ if (self.pov.can_drag_waste) {
+ let cards = self.stock.remove_waste(1)
+
+ self.dragging = self.make(DragStack, Vec2.zero, {})
+ self.dragging.cards = cards
+ self.cards.shadow_group = cards
+
+ self.drag_source = 'waste'
+
+ Sound.play(`drag1`)
+
+ } else {
+ }
+ }
}
})
@@ -300,17 +379,33 @@ export class SolitaireGame extends Play {
i: e
}
+ let drag_123 = Math.min(3, Math.floor(e/3) + 1)
+ Sound.play(`drag${drag_123}`)
+
} else {
}
}
},
on_front_drop() {
if (self.drag_source === 'waste') {
- } else if (self.drag_source === 'foundation') {
- } else {
+ self.dragging!.wait_drop()
+ self.cmd(WasteToTableu, {
+ to: i
+ })
+ } else if (isFoundationDragSource(self.drag_source!)) {
+ let { foundation } = self.drag_source!
+
+ self.dragging!.wait_drop()
+
+ self.cmd(FoundationToTableu, {
+ from: foundation,
+ to: i,
+ })
+ } else if (self.drag_source) {
let { tableu, i: _i } = self.drag_source!
+ self.dragging!.wait_drop()
self.cmd(TableuToTableu, {
from: tableu,
@@ -318,10 +413,57 @@ export class SolitaireGame extends Play {
i: _i
})
}
- self.dragging!.wait_drop()
}
}))
+ let foundation_x = 1790,
+ foundation_y = 166,
+ foundation_h = 240
+
+ this.foundations = n_four.map(i =>
+ this.make(Foundation, Vec2.make(foundation_x, foundation_y + foundation_h * i), {
+ on_front_drag(v: Vec2) {
+ if (self.dragging) {
+ self.dragging.drag(v)
+ } else {
+ if (self.pov.can_drag_foundation({ from: i })) {
+ let cards = self.foundations[i].remove_cards(1)
+
+ self.dragging = self.make(DragStack, Vec2.zero, {})
+ self.dragging.cards = cards
+ self.cards.shadow_group = cards
+
+ self.drag_source = { foundation: i }
+
+ Sound.play(`drag1`)
+
+ } else {
+ }
+ }
+ },
+ on_front_drop() {
+ if (self.drag_source === 'waste') {
+ self.dragging!.wait_drop()
+ self.cmd(WasteToFoundation, {
+ to: i
+ })
+ } else if (isFoundationDragSource(self.drag_source!)) {
+ } else if (self.drag_source) {
+ let { tableu, i: _i } = self.drag_source!
+
+ if (_i === 1) {
+ self.dragging!.wait_drop()
+ self.cmd(TableuToFoundation, {
+ from: tableu,
+ to: i,
+ i: 1
+ })
+ }
+ }
+ }
+ }))
+
+
this.dealer = this.make(Dealer, Vec2.zero, {
on_shuffle() {
self.dealer.cards.forEach(_ => self.cards.release(_))
@@ -335,6 +477,30 @@ export class SolitaireGame extends Play {
})
}
+ _release_cancel_drag() {
+ let cards = this.dragging!.lerp_release()
+
+ if (this.drag_source === 'waste') {
+ this.stock.add_waste(cards)
+ } else if (isFoundationDragSource(this.drag_source!)) {
+ let { foundation } = this.drag_source
+ this.foundations[foundation].add_cards(cards)
+ } else {
+
+ let { tableu, i } = this.drag_source!
+
+ this.tableus[tableu].add_fronts(cards)
+ }
+
+ this.dragging!.dispose()
+ this.dragging = undefined
+ this.drag_source = undefined
+ Sound.play('cancel')
+ cards[0].after_ease(() => {
+ this.cards.shadow_group = undefined
+ })
+ }
+
_collect_pov() {
this._init_pov()
@@ -381,6 +547,7 @@ export class SolitaireGame extends Play {
}
undo(res: IMove) {
+ Sound.play('undo2')
if (res instanceof HitStock) {
this.stock.undo_hit(res.data.cards, res.data.waste)
this._refresh_recycle()
@@ -395,10 +562,34 @@ export class SolitaireGame extends Play {
}
let cards = this.tableus[to].remove_fronts(i)
this.tableus[from].add_fronts(cards)
+ } else if (res instanceof WasteToTableu) {
+
+ let { to } = res.data
+
+ let cards = this.tableus[to].remove_fronts(1)
+
+ this.stock.add_waste(cards)
+ } else if (res instanceof TableuToFoundation) {
+ let { flip } = res.res
+ let { from, to } = res.data
+ if (flip) {
+ this.tableus[from].flip_back()
+ }
+ let cards = this.foundations[to].remove_cards(1)
+ this.tableus[from].add_fronts(cards)
+ } else if (res instanceof WasteToFoundation) {
+ let { to } = res.data
+ let cards = this.foundations[to].remove_cards(1)
+ this.stock.add_waste(cards)
+ } else if (res instanceof FoundationToTableu) {
+ let { from, to } = res.data
+ let cards = this.tableus[to].remove_fronts(1)
+ this.foundations[from].add_cards(cards)
}
}
apply(res: IMove) {
+ let dispose_drag_cards
if (res instanceof HitStock) {
this.stock.hit(res.data.cards)
this._refresh_recycle()
@@ -417,9 +608,53 @@ export class SolitaireGame extends Play {
this.tableus[from].flip_front(flip)
}
+ dispose_drag_cards = cards
+ } else if (res instanceof WasteToTableu) {
+ let { to } = res.data
+ let cards = this.dragging!.lerp_release()
+ this.tableus[to].add_fronts(cards)
+
+ this.stock.bind_new_front()
+
+ dispose_drag_cards = cards
+ } else if (res instanceof TableuToFoundation) {
+ let { flip } = res.res
+ let { from, to } = res.data
+
+ let cards = this.dragging!.lerp_release()
+ this.foundations[to].add_cards(cards)
+
+ if (flip) {
+ this.tableus[from].flip_front(flip)
+ }
+
+ dispose_drag_cards = cards
+
+ } else if (res instanceof WasteToFoundation) {
+ let { to } = res.data
+
+ let cards = this.dragging!.lerp_release()
+ this.foundations[to].add_cards(cards)
+
+ this.stock.bind_new_front()
+
+ dispose_drag_cards = cards
+
+ } else if (res instanceof FoundationToTableu) {
+ let { from, to } = res.data
+ let cards = this.dragging!.lerp_release()
+ this.tableus[to].add_fronts(cards)
+
+ this.stock.bind_new_front()
+
+ dispose_drag_cards = cards
+ }
+
+ if (dispose_drag_cards) {
this.dragging!.dispose()
this.dragging = undefined
- cards[0].after_ease(() => {
+ this.drag_source = undefined
+ dispose_drag_cards[0].after_ease(() => {
this.cards.shadow_group = undefined
})
}
@@ -431,7 +666,13 @@ export class SolitaireGame extends Play {
cant(cmd: IMoveType, data: any) {
- console.log('cant', cmd)
+ if (cmd === TableuToFoundation) {
+ this._release_cancel_drag()
+ } else if (cmd === WasteToFoundation) {
+ this._release_cancel_drag()
+ } else {
+ console.log('cant', cmd)
+ }
}
cant_undo() {
diff --git a/src/sound.ts b/src/sound.ts
index 994be49..1d2e03e 100644
--- a/src/sound.ts
+++ b/src/sound.ts
@@ -5,7 +5,10 @@ function load_audio(path: string): HTMLMediaElement {
return res
}
-let names = ['ding', 'drag', 'drag1', 'drag2', 'drag3']
+let names = [
+ 'ding', 'drag', 'drag1', 'drag2', 'drag3',
+ 'cancel', 'drop', 'hit', 'recycle', 'undo2'
+]
class Sound {