More games!
1
.gitignore
vendored
@@ -63,3 +63,4 @@ target/
|
||||
bower_components/
|
||||
static/*
|
||||
!static/.keep
|
||||
!static/images
|
||||
|
||||
60
app.py
@@ -15,45 +15,61 @@ env.load_path = [
|
||||
os.path.join(os.path.dirname(__file__), 'bower_components'),
|
||||
]
|
||||
|
||||
bower_files = [
|
||||
bower_js = [
|
||||
'jquery/dist/jquery.min.js',
|
||||
# 'angularjs/angular.min.js',
|
||||
'bootstrap/dist/js/bootstrap.min.js',
|
||||
'underscore/underscore-min.js',
|
||||
]
|
||||
|
||||
receiver_js_files = glob.glob(os.path.join(
|
||||
os.path.dirname(__file__), 'coffee', 'receiver', '*.coffee'))
|
||||
sender_js_files = glob.glob(os.path.join(
|
||||
os.path.dirname(__file__), 'coffee', 'sender', '*.coffee'))
|
||||
receiver_css_files = glob.glob(os.path.join(
|
||||
os.path.dirname(__file__), 'sass', 'receiver', '*.scss'))
|
||||
sender_css_files = glob.glob(os.path.join(
|
||||
os.path.dirname(__file__), 'sass', 'sender', '*.scss'))
|
||||
bower_css = [
|
||||
'bootstrap/dist/css/bootstrap.min.css',
|
||||
'bootstrap/dist/css/bootstrap-theme.min.css',
|
||||
]
|
||||
|
||||
receiver_js_files = map(lambda x: x[x.find(os.path.sep)+1:], receiver_js_files)
|
||||
sender_js_files = map(lambda x: x[x.find(os.path.sep)+1:], sender_js_files)
|
||||
receiver_css_files = map(lambda x: x[x.find(os.path.sep)+1:], receiver_css_files)
|
||||
sender_css_files = map(lambda x: x[x.find(os.path.sep)+1:], sender_css_files)
|
||||
receiver_js_files = [os.path.join(dirpath, f)
|
||||
for dirpath, dirnames, files in os.walk(os.path.join('coffee', 'receiver'))
|
||||
for f in files if f.endswith('.coffee')]
|
||||
sender_js_files = [os.path.join(dirpath, f)
|
||||
for dirpath, dirnames, files in os.walk(os.path.join('coffee', 'sender'))
|
||||
for f in files if f.endswith('.coffee')]
|
||||
receiver_css_files = [os.path.join(dirpath, f)
|
||||
for dirpath, dirnames, files in os.walk(os.path.join('sass', 'receiver'))
|
||||
for f in files if f.endswith('.scss')]
|
||||
sender_css_files = [os.path.join(dirpath, f)
|
||||
for dirpath, dirnames, files in os.walk(os.path.join('sass', 'sender'))
|
||||
for f in files if f.endswith('.scss')]
|
||||
|
||||
receiver_js_bundle = bower_files + \
|
||||
receiver_js_files = map(
|
||||
lambda x: x[x.find(os.path.sep) + 1:], receiver_js_files)
|
||||
sender_js_files = map(lambda x: x[x.find(os.path.sep) + 1:], sender_js_files)
|
||||
receiver_css_files = map(
|
||||
lambda x: x[x.find(os.path.sep) + 1:], receiver_css_files)
|
||||
sender_css_files = map(lambda x: x[x.find(os.path.sep) + 1:], sender_css_files)
|
||||
|
||||
receiver_js_bundle = bower_js + \
|
||||
[assets.Bundle(*receiver_js_files, filters='coffeescript',
|
||||
output='receiver.js')]
|
||||
sender_js_bundle = bower_files + \
|
||||
sender_js_bundle = bower_js + \
|
||||
[assets.Bundle(*sender_js_files, filters='coffeescript',
|
||||
output='sender.js')]
|
||||
receiver_css_bundle = bower_css + \
|
||||
[assets.Bundle(*receiver_css_files, filters='scss',
|
||||
output='receiver.css')]
|
||||
sender_css_bundle = bower_css + \
|
||||
[assets.Bundle(*sender_css_files, filters='scss',
|
||||
output='sender.css')]
|
||||
|
||||
bundles = {
|
||||
'receiver_js': assets.Bundle(*receiver_js_bundle),
|
||||
'sender_js': assets.Bundle(*sender_js_bundle),
|
||||
'receiver_css': assets.Bundle(*receiver_css_files,
|
||||
filters='scss',
|
||||
output='receiver.css'),
|
||||
'sender_css': assets.Bundle(*sender_css_files,
|
||||
filters='scss',
|
||||
output='sender.css'),
|
||||
'receiver_css': assets.Bundle(*receiver_css_bundle),
|
||||
'sender_css': assets.Bundle(*sender_css_bundle),
|
||||
}
|
||||
|
||||
env.register(bundles)
|
||||
|
||||
print receiver_js_files
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
|
||||
@@ -9,6 +9,7 @@ window.addEventListener 'load', ->
|
||||
cast.receiver.logger.setLevelValue 0
|
||||
window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance()
|
||||
console.log 'Starting Receiver Manager'
|
||||
window.stateHandler = new StateHandler(new RootState())
|
||||
# handler for the 'ready' event
|
||||
|
||||
castReceiverManager.onReady = (event) ->
|
||||
@@ -43,16 +44,12 @@ window.addEventListener 'load', ->
|
||||
# display the message from the sender
|
||||
displayText JSON.stringify(data)
|
||||
|
||||
if 'spinWheel' of data
|
||||
spinWheel(data.spinWheel)
|
||||
stateHandler.sendMessage(data)
|
||||
|
||||
# inform all senders on the CastMessageBus of the incoming message event
|
||||
# sender message listener will be invoked
|
||||
window.messageBus.send event.senderId, event.data
|
||||
|
||||
window.spinWheel = (velocity) ->
|
||||
console.debug 'spinWheel', {wheelSpinning, wheelStopped}
|
||||
wheel.spin(velocity)
|
||||
|
||||
# initialize the CastReceiverManager with an application status message
|
||||
window.castReceiverManager.start statusText: 'Application is starting'
|
||||
console.log 'Receiver Manager started'
|
||||
|
||||
169
coffee/receiver/state.coffee
Normal file
@@ -0,0 +1,169 @@
|
||||
class StateHandler
|
||||
constructor: (@current) ->
|
||||
@initialState = @current
|
||||
|
||||
sendMessage: (message) ->
|
||||
@current.onMessage(message)
|
||||
|
||||
resetState: ->
|
||||
@current = @initialState
|
||||
|
||||
setState: (@current) ->
|
||||
|
||||
class StateDefinition
|
||||
constructor: ({@handler = window.stateHandler} = {}) ->
|
||||
|
||||
onMessage: (message) ->
|
||||
@handler.resetState()
|
||||
|
||||
class RootState extends StateDefinition
|
||||
onMessage: (message) ->
|
||||
if 'spinWheel' of message and not wheelSpinning
|
||||
wheel.spin(message.spinWheel)
|
||||
@setMarker(message.spinWheel)
|
||||
|
||||
setMarker: (velocity) ->
|
||||
y = Math.abs(velocity) / 15 * 100
|
||||
$('#marker').css bottom: (y / 2) + 'vh'
|
||||
style = if 15 > y
|
||||
'blue'
|
||||
else if 15 < y < 50
|
||||
'green'
|
||||
else if 50 < y < 80
|
||||
'yellow'
|
||||
else
|
||||
'red'
|
||||
$('#marker-line').removeClass 'blue green yellow red'
|
||||
$('#marker-line').addClass style
|
||||
|
||||
setTimeout ->
|
||||
$('#marker-line').removeClass 'blue green yellow red'
|
||||
$('#marker-line').addClass 'blue'
|
||||
$('#marker').css bottom: '0vh'
|
||||
, 4000
|
||||
|
||||
class DialogState extends StateDefinition
|
||||
id: null
|
||||
constructor: ({@content, @size = 'md', @classes, @data} = {}) ->
|
||||
throw Error "Must provide id in prototype" unless @id?
|
||||
displayText JSON.stringify {@content, @size, @classes}
|
||||
super
|
||||
modal = document.createElement('div')
|
||||
modal.className = 'modal fade in'
|
||||
modal.id = @id
|
||||
modal.setAttribute 'role', 'dialog'
|
||||
modalDialog = document.createElement('div')
|
||||
modalDialog.className = "modal-dialog modal-#{@size} #{@classes}"
|
||||
modalContent = document.createElement('div')
|
||||
modalContent.className = 'modal-content'
|
||||
modal.setAttribute 'role', 'document'
|
||||
modalContent.innerHTML = "<h1>#{@_getContent()}</h1>"
|
||||
|
||||
modalDialog.appendChild(modalContent)
|
||||
modal.appendChild(modalDialog)
|
||||
document.body.appendChild(modal)
|
||||
|
||||
@modal = $("##{@id}")
|
||||
|
||||
@modal.modal('show')
|
||||
|
||||
_getContent: ->
|
||||
@content
|
||||
|
||||
_refreshContent: ->
|
||||
@modal.find('.modal-content').html @_getContent()
|
||||
|
||||
onMessage: ->
|
||||
@_closeModal()
|
||||
super
|
||||
|
||||
_closeModal: ->
|
||||
@modal.modal('hide')
|
||||
setTimeout =>
|
||||
@modal.remove()
|
||||
, 1000
|
||||
|
||||
class DrinkState extends DialogState
|
||||
id: 'drink-modal'
|
||||
|
||||
class YouTubeState extends DialogState
|
||||
id: 'youtube-modal'
|
||||
constructor: ->
|
||||
super
|
||||
@playing = no
|
||||
|
||||
_getContent: ->
|
||||
"""
|
||||
<iframe id="ytplayer-#{@id}" type="text/html" width="640" height="390"
|
||||
src="http://www.youtube.com/embed/#{@content}"
|
||||
frameborder="0"/>
|
||||
"""
|
||||
|
||||
onMessage: ->
|
||||
if not @playing
|
||||
$("#ytplayer-#{@id}")[0].src += "?autoplay=1"
|
||||
@playing = yes
|
||||
else
|
||||
$("#ytplayer-#{@id}")[0].src = ''
|
||||
super
|
||||
|
||||
class TimeoutState extends DialogState
|
||||
id: 'timeout'
|
||||
step: 0
|
||||
constructor: (options) ->
|
||||
super
|
||||
|
||||
_getContent: ->
|
||||
switch @step
|
||||
when 0
|
||||
"""
|
||||
<h1>#{@_competitorsImages()}</h1>
|
||||
<h2>You have #{@data.seconds} to drink as much beer as you can!</h2>
|
||||
"""
|
||||
when 1
|
||||
"""
|
||||
<h2>#{@_competitorsImages()}</h2>
|
||||
<h1>#{@_toTimeStr(@data.seconds)}</h1>
|
||||
"""
|
||||
else
|
||||
"<h1>Time's up!</h1>"
|
||||
|
||||
_competitorsImages: ->
|
||||
@data.competitors = @data.competitorsFunc?() unless @data.competitors?
|
||||
@data.competitors
|
||||
.map (c) -> "<img src=\"/static/images/#{c.toLowerCase()}.png\" />"
|
||||
.join(' vs ')
|
||||
|
||||
_startTimeout: ->
|
||||
@interval = window.setInterval =>
|
||||
@data.seconds -= 1
|
||||
@_refreshContent()
|
||||
if @data.seconds < 0
|
||||
clearInterval @interval
|
||||
@step++
|
||||
@_refreshContent()
|
||||
, 1000
|
||||
|
||||
onMessage: ->
|
||||
return if @step is 1
|
||||
|
||||
switch @step
|
||||
when 0
|
||||
@_startTimeout()
|
||||
@step++
|
||||
when 2
|
||||
super
|
||||
return
|
||||
|
||||
_contentTemplate: (competitors, time) ->
|
||||
|
||||
_toTimeStr: (secs) ->
|
||||
hours = Math.floor secs / 3600
|
||||
minutes = Math.floor (secs - (hours * 3600)) / 60
|
||||
seconds = secs - (hours * 3600) - (minutes * 60)
|
||||
|
||||
# hours = "0#{hours}" if hours < 10
|
||||
minutes = "0#{minutes}" if minutes < 10
|
||||
seconds = "0#{seconds}" if seconds < 10
|
||||
|
||||
"#{minutes}:#{seconds}"
|
||||
@@ -30,44 +30,57 @@ particles = []
|
||||
statusLabel = document.getElementById('status_label')
|
||||
segConfig = [
|
||||
{
|
||||
text: 'Drink with a buddy 1'
|
||||
onWin: ->
|
||||
displayText "You need to drink with someone else 1"
|
||||
text: 'Drink with Zeevi'
|
||||
winState: YouTubeState
|
||||
stateOptions:
|
||||
content: 'dQw4w9WgXcQ'
|
||||
size: 'lg'
|
||||
}
|
||||
{
|
||||
text: 'Drink with a buddy 2'
|
||||
onWin: ->
|
||||
displayText "You need to drink with someone else 2"
|
||||
text: 'Beer Match!'
|
||||
winState: TimeoutState
|
||||
stateOptions:
|
||||
data:
|
||||
competitorsFunc: ->
|
||||
amount = Math.round(Math.random())
|
||||
names = 'Amit Avihad Chen Dor Eran Lior Michael Tom Zeevi'.split(' ')
|
||||
_.shuffle(names)[0...amount + 2]
|
||||
}
|
||||
{
|
||||
text: 'Drink with a buddy 3'
|
||||
onWin: ->
|
||||
displayText "You need to drink with someone else 3"
|
||||
text: 'Drink with Chen'
|
||||
winState: DrinkState
|
||||
stateOptions:
|
||||
content: 'Drink with Chen'
|
||||
}
|
||||
{
|
||||
text: 'Drink with a buddy 4'
|
||||
onWin: ->
|
||||
displayText "You need to drink with someone else 4"
|
||||
text: 'Drink with Dor'
|
||||
winState: DrinkState
|
||||
stateOptions:
|
||||
content: 'Drink with Dor'
|
||||
}
|
||||
{
|
||||
text: 'Drink with a buddy 5'
|
||||
onWin: ->
|
||||
displayText "You need to drink with someone else 5"
|
||||
text: 'Drink with Amit'
|
||||
winState: DrinkState
|
||||
stateOptions:
|
||||
content: 'Drink with Amit'
|
||||
}
|
||||
{
|
||||
text: 'Drink with a buddy 6'
|
||||
onWin: ->
|
||||
displayText "You need to drink with someone else 6"
|
||||
text: 'Drink with Alon'
|
||||
winState: DrinkState
|
||||
stateOptions:
|
||||
content: 'Drink with Alon'
|
||||
}
|
||||
{
|
||||
text: 'Drink with a buddy 7'
|
||||
onWin: ->
|
||||
displayText "You need to drink with someone else 7"
|
||||
text: 'Drink with Eran'
|
||||
winState: DrinkState
|
||||
stateOptions:
|
||||
content: 'Drink with Eran'
|
||||
}
|
||||
{
|
||||
text: 'Drink with a buddy 8'
|
||||
onWin: ->
|
||||
displayText "You need to drink with someone else 8"
|
||||
text: 'Drink with Lior'
|
||||
winState: DrinkState
|
||||
stateOptions:
|
||||
content: 'Drink with Lior'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -102,7 +115,7 @@ checkEndDrag = (e) ->
|
||||
world.removeConstraint mouseConstraint
|
||||
mouseConstraint = null
|
||||
if wheelSpinning == false and wheelStopped == true
|
||||
if Math.abs(wheel.body.angularVelocity) > 7.5
|
||||
if Math.abs(wheel.body.angularVelocity) > 4.5
|
||||
wheelSpinning = true
|
||||
wheelStopped = false
|
||||
console.log 'good spin'
|
||||
@@ -165,11 +178,11 @@ update = ->
|
||||
# console.debug 'update', {wheelSpinning, wheelStopped, angularVelocity: wheel.body.angularVelocity, arrowHasStopped: arrow.hasStopped()}
|
||||
if wheelSpinning and not wheelStopped and wheel.body.angularVelocity < 0.5 and arrow.hasStopped()
|
||||
currentSeg = wheel.currentSegment()
|
||||
console.debug 'checking segment', currentSeg
|
||||
wheelStopped = true
|
||||
wheelSpinning = false
|
||||
# wheel.body.angularVelocity = 0
|
||||
segConfig[currentSeg]?.onWin?()
|
||||
seg = segConfig[currentSeg]
|
||||
if seg.winState
|
||||
stateHandler.setState new seg.winState seg.stateOptions
|
||||
|
||||
draw = ->
|
||||
# ctx.fillStyle = '#fff';
|
||||
@@ -275,10 +288,10 @@ class Wheel
|
||||
ctx.fill()
|
||||
|
||||
if @segmentTexts[i].length
|
||||
ctx.rotate (i + (2/3)) * @deltaPI
|
||||
ctx.rotate (i + (3/5)) * @deltaPI
|
||||
ctx.font = '30px sans-serif'
|
||||
ctx.fillStyle = '#000000'
|
||||
ctx.fillText(@segmentTexts[i], 70, 0, 180)
|
||||
ctx.fillText(@segmentTexts[i], 70, 3, 280)
|
||||
|
||||
ctx.restore()
|
||||
i++
|
||||
|
||||
@@ -33,4 +33,4 @@ requestSession = ->
|
||||
chrome.cast.requestSession onRequestSessionSuccess, onLaunchError
|
||||
|
||||
sendMessage = (message) ->
|
||||
session.sendMessage namespace, message, onMessageSuccess
|
||||
session.sendMessage namespace, JSON.stringify(message), onMessageSuccess
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
body {
|
||||
width : 1280px;
|
||||
height : 720px;
|
||||
background : #44a52f;
|
||||
color : #fff;
|
||||
padding : 5%;
|
||||
font-family: "Droid Sans", Calibri, Arial, Tahoma, sans-serif;
|
||||
overflow: hidden;
|
||||
width : 100vw;
|
||||
height : 100vh;
|
||||
color : #fff;
|
||||
padding : 5%;
|
||||
overflow : hidden;
|
||||
background: linear-gradient(to bottom, #08541b 0%, #44a52f 20%);
|
||||
}
|
||||
#drawing_canvas {
|
||||
position : absolute;
|
||||
@@ -13,3 +12,40 @@ body {
|
||||
top : 50%;
|
||||
transform: translate(-50%, -18%);
|
||||
}
|
||||
.modal-content {
|
||||
color: #333;
|
||||
}
|
||||
#marker {
|
||||
position : absolute;
|
||||
bottom : 0;
|
||||
content : "";
|
||||
border : 20px solid rgba(0,0,0,0);
|
||||
border-right-color: white;
|
||||
width : 0;
|
||||
height : 0;
|
||||
margin-bottom : -20px;
|
||||
transition : all 1s ease-in-out;
|
||||
}
|
||||
#marker-line {
|
||||
width : 30px;
|
||||
height : 50vh;
|
||||
position : absolute;
|
||||
top : 50%;
|
||||
left : 5%;
|
||||
border : 2px solid white;
|
||||
box-shadow: 1px 1px 3px 3px rgba(0,0,0,0.3);
|
||||
transform : translateY(-50%);
|
||||
transition : all 1.2s ease-in-out;
|
||||
&.blue {
|
||||
background: linear-gradient(to bottom, #032a62 0%, #005dea 100%);
|
||||
}
|
||||
&.green {
|
||||
background: linear-gradient(to bottom, #00ce38 0%, #82e573 100%);
|
||||
}
|
||||
&.yellow {
|
||||
background: linear-gradient(to bottom, #ffdb39 0%, #ffa22a 100%);
|
||||
}
|
||||
&.red {
|
||||
background: linear-gradient(to bottom, #831a17 0%, #ff000a 100%);
|
||||
}
|
||||
}
|
||||
|
||||
BIN
static/images/amit.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
static/images/avihad.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
static/images/chen.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
static/images/dor.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
static/images/eran.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
static/images/lior.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
static/images/michael.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
static/images/tom.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
static/images/zeevi.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
@@ -11,8 +11,6 @@
|
||||
{% endassets %}
|
||||
<script type="text/javascript"
|
||||
src="//www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="//www.gstatic.com/cast/sdk/libs/games/1.0.0/cast_games_receiver.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="//cdnjs.cloudflare.com/ajax/libs/p2.js/0.6.0/p2.min.js"></script>
|
||||
{% assets "receiver_js" %}
|
||||
@@ -23,12 +21,16 @@
|
||||
<head>
|
||||
|
||||
<body>
|
||||
<h1>Receiver</h1>
|
||||
<h1>CastRoulette</h1>
|
||||
|
||||
Latest message:
|
||||
<div id="message"></div>
|
||||
|
||||
<canvas id="drawing_canvas"></canvas>
|
||||
|
||||
<div id="marker-line" class="blue">
|
||||
<div id="marker" style="bottom: 0"></div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<button type="button"
|
||||
onclick="sendMessage(document.querySelector('#message').value)">Send message</button>
|
||||
</div>
|
||||
<input type="text" id="spin" value="5" /><button type="button" onclick="sendMessage({spinWheel: document.querySelector('#spin').value})">Spin wheel</button>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||