Skip to content

Instantly share code, notes, and snippets.

@holgersindbaek
Last active July 19, 2017 01:11
Show Gist options
  • Save holgersindbaek/b8f134306148d12995dadf5e28d2d5b7 to your computer and use it in GitHub Desktop.
Save holgersindbaek/b8f134306148d12995dadf5e28d2d5b7 to your computer and use it in GitHub Desktop.
import { playSound } from '~/utils/playSound'
import { TweenMax, TimelineMax, Power2, Sine } from 'gsap'
export function handleNewGameInitialized() {
// Create initial variables
const animationTimeline = new TimelineMax({
paused: true,
onComplete: () => {
// // Call action on complete
// this.props.handleResetCardPositions()
// dispatch(gameActions.setCardPositions({ test: "test" }))
// // Set new state
// this.setState({ deck: this.props.deck, board: this.props.board })
}
})
// Move cards to stock
if (is.not.equal(this.state.board.stock.count(), 52)) {
// Initial variables
const stockPosition = this.containerPosition('#stock')
// Animate cards to stock
animationTimeline.add([TweenMax.to('.card_component', Number.toSeconds(480), {
top: stockPosition.top,
left: stockPosition.left,
ease: Power2.easeOut,
onStart: () => {
// Set shadow on front and back
$('.card_component').find('.front, .back').css({ 'box-shadow': 'inset 0px 0px 0px 1px rgba(0,0,0,0.65), 0px 0px 8px 0px rgba(0,0,0,0)' })
}
}), TweenMax.to('.card_component.face_up .back', Number.toSeconds(80), {
rotationY: -90,
ease: Sine.easeOut
}), TweenMax.to('.card_component.face_up .front', Number.toSeconds(80), {
rotationY: 0,
delay: Number.toSeconds(80),
ease: Sine.easeIn,
onComplete: () => {
// Loop through face up cards
$('.card_component.face_up').each((index, element) => {
// // Set face up state on card
// this.refs[`card_${$(element).data('suit')}_${$(element).data('rank')}`].setState({ faceUp: false })
})
}
})])
}
// Move cards to tableau
Object.forEach(this.tableaus(), (tableau, tableauKey) => {
// Initial variables
const tableauPosition = this.containerPosition(`#${tableauKey.underscore()}`)
const tableauCardMargin = this.tableauCardMargin(tableau.count())
const tableauIndex = Object.keys(this.tableaus()).findIndex(tableauKey)
const targets = tableau.map((card) => `#card_${card.suit}_${card.rank}`)
const duration = Number.toSeconds(200 - (20 * (6 - tableauIndex)))
const delay = -Number.toSeconds(20 * tableauIndex)
animationTimeline.to(targets, duration, {
top: (index) => tableauPosition.top + (index * tableauCardMargin),
left: () => tableauPosition.left,
delay: delay,
ease: Power2.easeOut,
onStart: () => {
// Loop through cards
tableau.forEach((card, cardIndex) => {
// Set z index on card
$(`#card_${card.suit}_${card.rank}`).css({ 'z-index': 2000 + cardIndex })
// Set shadow on front and back
$(`#card_${card.suit}_${card.rank}`).find('.front, .back').css({ 'box-shadow': 'inset 0px 0px 0px 1px rgba(0,0,0,0.65), 0px 0px 8px 0px rgba(0,0,0,0.1)' })
})
// Play deal sound
if (is.equal(tableauIndex, 0)) { playSound('deal') }
},
onComplete: () => {
// Loop through cards
tableau.forEach((card, cardIndex) => {
// Set z index on card
$(`#card_${card.suit}_${card.rank}`).css({ 'z-index': 1000 + cardIndex })
})
}
})
})
// Turn first card in tableau
Object.forEach(this.tableaus(), (tableau) => {
// Initial variables
const card = tableau.last()
// Add turn to animation
animationTimeline.to(`#card_${card.suit}_${card.rank} .front`, Number.toSeconds(80), {
rotationY: 90,
delay: -Number.toSeconds(80),
ease: Sine.easeOut
}).to(`#card_${card.suit}_${card.rank} .back`, Number.toSeconds(80), {
rotationY: 0,
ease: Sine.easeIn,
onComplete: () => {
// // Set face up state on card
// this.refs[`card_${card.suit}_${card.rank}`].setState({ faceUp: true })
}
})
})
// Run animations
animationTimeline.play()
}
import Interact from 'interactjs'
import { React } from '~/utils/import'
import { playSound } from '~/utils/playSound'
import { TweenMax, TimelineMax, Power2, Sine } from 'gsap'
import PropTypes from 'prop-types'
import CardComponent from '../../components/CardComponent'
import { handleNewGameInitialized } from './animationHelper'
import './index.scss'
class BoardComponent extends React.Component {
static propTypes = {
deck: PropTypes.arrayOf(React.PropTypes.shape).isRequired,
board: PropTypes.shape({
stock: PropTypes.arrayOf(React.PropTypes.shape).isRequired,
waste: PropTypes.arrayOf(React.PropTypes.shape).isRequired,
foundationClover: PropTypes.arrayOf(React.PropTypes.shape).isRequired,
foundationHeart: PropTypes.arrayOf(React.PropTypes.shape).isRequired,
foundationSpade: PropTypes.arrayOf(React.PropTypes.shape).isRequired,
foundationDiamond: PropTypes.arrayOf(React.PropTypes.shape).isRequired,
tableauOne: PropTypes.arrayOf(React.PropTypes.shape).isRequired,
tableauTwo: PropTypes.arrayOf(React.PropTypes.shape).isRequired,
tableauThree: PropTypes.arrayOf(React.PropTypes.shape).isRequired,
tableauFour: PropTypes.arrayOf(React.PropTypes.shape).isRequired,
tableauFive: PropTypes.arrayOf(React.PropTypes.shape).isRequired,
tableauSix: PropTypes.arrayOf(React.PropTypes.shape).isRequired,
tableauSeven: PropTypes.arrayOf(React.PropTypes.shape).isRequired
})
}
constructor(props) {
// Set props
super(props)
// Set initial state
this.state = {
deck: props.board.deck,
board: {
stock: props.board.stock,
waste: props.board.waste,
foundationClover: props.board.foundationClover,
foundationHeart: props.board.foundationHeart,
foundationSpade: props.board.foundationSpade,
foundationDiamond: props.board.foundationDiamond,
tableauOne: props.board.tableauOne,
tableauTwo: props.board.tableauTwo,
tableauThree: props.board.tableauThree,
tableauFour: props.board.tableauFour,
tableauFive: props.board.tableauFive,
tableauSix: props.board.tableauSix,
tableauSeven: props.board.tableauSeven
}
}
// Bind functions
this.handleNewGameInitialized = handleNewGameInitialized.bind(this)
}
/////////////////////////////////////////////////////////////////////////
// Lifecycle
/////////////////////////////////////////////////////////////////////////
componentDidMount() {
// Position initial cards
this.positionCardsOnBoard()
// Initialize drag and drop
this.initializeInteract()
// Listen for when new game is initialized
emitter.on('initializeNewGame', this.handleNewGameInitialized)
// Listen for height change
let handleHeightChangeTimer
$(window).resize(() => {
if (is.existy(handleHeightChangeTimer)) { handleHeightChangeTimer.cancel() }
handleHeightChangeTimer = this.updateTableauCardMargins.delay(1000)
})
// Listen for tap on stock
$('#card_container').on('click', '.card_component[data-container="stock"]', this.handleStockTap)
}
shouldComponentUpdate() {
return false
}
/////////////////////////////////////////////////////////////////////////
// Main
/////////////////////////////////////////////////////////////////////////
handleStockTap = () => {
log('handleStockTap')
}
initializeInteract = () => {
// Enable dragging on card
Interact('.card_component.face_up')
.draggable({ max: Infinity })
.on('dragstart', (event) => {
// Save values on interaction
event.interaction.x = parseInt(event.target.getAttribute('data-x'), 10) || parseInt($(event.target).css('left'), 10) || 0
event.interaction.y = parseInt(event.target.getAttribute('data-y'), 10) || parseInt($(event.target).css('top'), 10) || 0
})
.on('dragmove', (event) => {
// Set new values on interaction
event.interaction.x += event.dx
event.interaction.y += event.dy
// Move card
event.target.style.left = `${event.interaction.x}px`
event.target.style.top = `${event.interaction.y}px`
})
.on('dragend', (event) => {
// Save values on target
event.target.setAttribute('data-x', event.interaction.x)
event.target.setAttribute('data-y', event.interaction.y)
})
// Enable dropping of cards
Interact('.tableau', '.card_component')
.dropzone({ overlap: 0.60 })
.on('dropactivate', (event) => {
// Set class on drop areas
$(event.target).addClass('drop_active')
})
.on('dragenter', (event) => {
// Set class on drop area and card
$(event.target).addClass('drop_target')
$(event.relatedTarget).addClass('can_drop')
})
.on('dragleave', (event) => {
// Remove class on drop area and card
$(event.target).removeClass('drop_target')
$(event.relatedTarget).removeClass('can_drop')
})
.on('drop', (event) => {
})
.on('dropdeactivate', (event) => {
// Remove class on drop area and card
$(event.target).removeClass('drop_target')
$(event.relatedTarget).removeClass('can_drop')
})
}
/////////////////////////////////////////////////////////////////////////
// Helpers
/////////////////////////////////////////////////////////////////////////
positionCardsOnBoard = () => {
// Loop through stock
this.props.board.stock.forEach((card, cardIndex) => {
// Set position of card
$(`#card_${card.suit}_${card.rank}`).css({ 'z-index': 1000 + cardIndex, top: this.containerPosition('#stock').top, left: this.containerPosition('#stock').left })
// Set data on card
$(`#card_${card.suit}_${card.rank}`).attr('data-container', 'stock')
// Remove shadow from card
$(`#card_${card.suit}_${card.rank}`).find('.front, .back').css({ 'box-shadow': 'inset 0px 0px 0px 1px rgba(0,0,0,0.65), 0px 0px 8px 0px rgba(0,0,0,0)' })
})
// Set shadow on stock
$('.stock_shadow').css({ 'box-shadow': `0px 0px 16px 0px rgba(0, 0, 0, ${(this.props.board.stock.count() / 52) * 0.6})` })
// Loop through waste
this.props.board.waste.forEach((card, cardIndex) => {
// Set position of card
$(`#card_${card.suit}_${card.rank}`).css({ 'z-index': 1000 + cardIndex, top: this.containerPosition('#waste').top, left: this.containerPosition('#waste').left + (32 * cardIndex) })
// Set data on card
$(`#card_${card.suit}_${card.rank}`).attr('data-container', 'waste')
// Add shadow to card
$(`#card_${card.suit}_${card.rank}`).find('.front, .back').css({ 'box-shadow': 'inset 0px 0px 0px 1px rgba(0,0,0,0.65), 0px 0px 8px 0px rgba(0,0,0,0.1)' })
})
// Loop through foundation
Object.forEach(this.foundations(), (foundation, foundationKey) => {
// Initial variables
const foundationPosition = this.containerPosition(`#${foundationKey.underscore()}`)
// Loop through cards and move them to position
foundation.forEach((card, cardIndex) => {
// Set position on card
$(`#card_${card.suit}_${card.rank}`).css({ 'z-index': 1000 + cardIndex, top: foundationPosition.top, left: foundationPosition.left })
// Set data on card
$(`#card_${card.suit}_${card.rank}`).attr('data-container', foundationKey.underscore())
// Add shadow to card
$(`#card_${card.suit}_${card.rank}`).find('.front, .back').css({ 'box-shadow': 'inset 0px 0px 0px 1px rgba(0,0,0,0.65), 0px 0px 8px 0px rgba(0,0,0,0.1)' })
})
})
// Loop through tableau
Object.forEach(this.tableaus(), (tableau, tableauKey) => {
// Initial variables
const tableauPosition = this.containerPosition(`#${tableauKey.underscore()}`)
const tableauCardMargin = this.tableauCardMargin(tableau.count())
// Loop through cards and move them to position
tableau.forEach((card, cardIndex) => {
// Set position on card
$(`#card_${card.suit}_${card.rank}`).css({ 'z-index': 1000 + cardIndex, top: tableauPosition.top + (tableauCardMargin * cardIndex), left: tableauPosition.left })
// Set data on card
$(`#card_${card.suit}_${card.rank}`).attr('data-container', tableauKey.underscore())
// Add shadow to card
$(`#card_${card.suit}_${card.rank}`).find('.front, .back').css({ 'box-shadow': 'inset 0px 0px 0px 1px rgba(0,0,0,0.65), 0px 0px 8px 0px rgba(0,0,0,0.1)' })
})
})
// Change state to props
this.state.deck = this.props.deck
this.state.board = this.props.board
}
containerPosition = (container) => {
return { top: $(container).position().top + $(container).parent().position().top, left: $(container).position().left + $(container).parent().position().left }
}
tableauCardMargin = (cardAmount) => {
// Get height of tableau
const cardHeight = 142
const calculatedHeight = (margin) => cardHeight + ((cardAmount - 1) * margin)
const tableauHeight = $("#bottom_row").height()
// Calculate correct margin
if (calculatedHeight(32) < tableauHeight) {
return 32
} else if (calculatedHeight(24) < tableauHeight) {
return 24
} else if (calculatedHeight(16) < tableauHeight) {
return 16
} else if (calculatedHeight(8) < tableauHeight) {
return 8
} else {
return 8
}
}
tableaus = () => {
return { tableauOne: this.props.board.tableauOne, tableauTwo: this.props.board.tableauTwo, tableauThree: this.props.board.tableauThree, tableauFour: this.props.board.tableauFour, tableauFive: this.props.board.tableauFive, tableauSix: this.props.board.tableauSix, tableauSeven: this.props.board.tableauSeven }
}
foundations = () => {
return { foundationClover: this.props.board.foundationClover, foundationHeart: this.props.board.foundationHeart, foundationSpade: this.props.board.foundationSpade, foundationDiamond: this.props.board.foundationDiamond }
}
/////////////////////////////////////////////////////////////////////////
// Render method
/////////////////////////////////////////////////////////////////////////
render() {
return (
<div className="center-container board_component">
<div className="center-row">
<div className="container">
<div className="row" id="bottom_row">
<div className="col-1">
<div className="tableau one" id="tableau_one" />
</div>
<div className="col-1">
<div className="tableau two" id="tableau_two" />
</div>
<div className="col-1">
<div className="tableau three" id="tableau_three" />
</div>
<div className="col-1">
<div className="tableau four" id="tableau_four" />
</div>
<div className="col-1">
<div className="tableau five" id="tableau_five" />
</div>
<div className="col-1">
<div className="tableau six" id="tableau_six" />
</div>
<div className="col-1">
<div className="tableau seven" id="tableau_seven" />
</div>
</div>
<div className="row" id="top_row">
<div className="col-1">
<div className="stock_shadow" />
<div className="stock" id="stock" />
</div>
<div className="col-2">
<div className="waste" id="waste" />
</div>
<div className="col-1">
<div className="foundation_background clover" style={{ backgroundImage: `url(${assetPath('medium_clover_foundation', 'png')})` }} />
<div className="foundation clover" id="foundation_clover" />
</div>
<div className="col-1">
<div className="foundation_background heart" style={{ backgroundImage: `url(${assetPath('medium_heart_foundation', 'png')})` }} />
<div className="foundation heart" id="foundation_heart" />
</div>
<div className="col-1">
<div className="foundation_background spade" style={{ backgroundImage: `url(${assetPath('medium_spade_foundation', 'png')})` }} />
<div className="foundation spade" id="foundation_spade" />
</div>
<div className="col-1">
<div className="foundation_background diamond" style={{ backgroundImage: `url(${assetPath('medium_diamond_foundation', 'png')})` }} />
<div className="foundation diamond" id="foundation_diamond" />
</div>
</div>
<div className="card_container" id="card_container">
<For each="card" of={this.props.board.stock}>
<CardComponent card={card} key={`${card.suit}_${card.rank}`} ref={`card_${card.suit}_${card.rank}`} />
</For>
</div>
</div>
</div>
</div>
)
}
}
export default BoardComponent
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment