2013-10-28 20:00:20 +00:00
// Copyright (C) 2013 Massachusetts Institute of Technology
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
// Scratch HTML5 Player
// Runtime.js
// Tim Mickel, July 2011
// Runtime takes care of the rendering and stepping logic.
'use strict' ;
var t = new Timer ( ) ;
var Runtime = function ( ) {
this . scene = null ;
this . sprites = [ ] ;
this . reporters = [ ] ;
this . keysDown = { } ;
this . mouseDown = false ;
this . mousePos = [ 0 , 0 ] ;
this . audioContext = null ;
this . audioGain = null ;
this . audioPlaying = [ ] ;
this . notesPlaying = [ ] ;
this . projectLoaded = false ;
2013-11-01 22:44:51 -04:00
} ;
2013-10-28 20:00:20 +00:00
// Initializer for the drawing and audio contexts.
Runtime . prototype . init = function ( ) {
this . scene = $ ( '#container' ) ;
window . AudioContext = window . AudioContext || window . webkitAudioContext ;
this . audioContext = new AudioContext ( ) ;
2014-04-09 01:17:50 -07:00
try {
this . audioGain = this . audioContext . createGain ( ) ;
} catch ( err ) {
this . audioGain = this . audioContext . createGainNode ( ) ;
}
2013-10-28 20:00:20 +00:00
this . audioGain . connect ( runtime . audioContext . destination ) ;
2013-11-01 22:44:51 -04:00
} ;
2013-10-28 20:00:20 +00:00
// Load start waits for the stage and the sprites to be loaded, without
// hanging the browser. When the loading is finished, we begin the step
// and animate methods.
Runtime . prototype . loadStart = function ( ) {
if ( ! runtime . stage . isLoaded ( ) ) {
setTimeout ( function ( runtime ) { runtime . loadStart ( ) ; } , 50 , this ) ;
return ;
}
for ( var obj = 0 ; obj < runtime . sprites . length ; obj ++ ) {
if ( typeof ( runtime . sprites [ obj ] ) == 'object' && runtime . sprites [ obj ] . constructor == Sprite ) {
if ( ! runtime . sprites [ obj ] . isLoaded ( ) ) {
setTimeout ( function ( runtime ) { runtime . loadStart ( ) ; } , 50 , this ) ;
return ;
}
}
}
if ( Instr . wavsLoaded != Instr . wavCount ) {
setTimeout ( function ( runtime ) { runtime . loadStart ( ) ; } , 50 , this ) ;
return ;
}
2013-11-14 20:08:52 -05:00
$ ( '#preloader' ) . css ( 'display' , 'none' ) ;
2013-10-28 20:00:20 +00:00
setInterval ( this . step , 33 ) ;
this . projectLoaded = true ;
2013-11-01 22:44:51 -04:00
} ;
2013-10-28 20:00:20 +00:00
Runtime . prototype . greenFlag = function ( ) {
if ( this . projectLoaded ) {
interp . activeThread = new Thread ( null ) ;
interp . threads = [ ] ;
2013-10-30 15:50:29 +02:00
interp . primitiveTable . timerReset ( ) ;
2013-10-28 20:00:20 +00:00
this . startGreenFlags ( ) ;
}
2013-11-01 22:44:51 -04:00
} ;
2013-10-28 20:00:20 +00:00
Runtime . prototype . stopAll = function ( ) {
interp . activeThread = new Thread ( null ) ;
interp . threads = [ ] ;
stopAllSounds ( ) ;
2014-03-08 09:41:01 -07:00
// Hide sprite bubbles, resetFilters and doAsk prompts
2013-10-28 20:00:20 +00:00
for ( var s = 0 ; s < runtime . sprites . length ; s ++ ) {
2014-03-08 09:41:01 -07:00
if ( runtime . sprites [ s ] . hideBubble ) runtime . sprites [ s ] . hideBubble ( ) ;
if ( runtime . sprites [ s ] . resetFilters ) runtime . sprites [ s ] . resetFilters ( ) ;
if ( runtime . sprites [ s ] . hideAsk ) runtime . sprites [ s ] . hideAsk ( ) ;
2013-10-28 20:00:20 +00:00
}
2013-11-03 07:10:45 -05:00
// Reset graphic effects
2013-11-14 23:44:00 -05:00
runtime . stage . resetFilters ( ) ;
2013-11-01 22:44:51 -04:00
} ;
2013-10-28 20:00:20 +00:00
// Step method for execution - called every 33 milliseconds
Runtime . prototype . step = function ( ) {
interp . stepThreads ( ) ;
for ( var r = 0 ; r < runtime . reporters . length ; r ++ ) {
runtime . reporters [ r ] . update ( ) ;
}
2013-11-01 22:44:51 -04:00
} ;
2013-10-28 20:00:20 +00:00
// Stack functions -- push and remove stacks
// to be run by the interpreter as threads.
Runtime . prototype . allStacksDo = function ( f ) {
var stage = runtime . stage ;
var stack ;
for ( var i = runtime . sprites . length - 1 ; i >= 0 ; i -- ) {
var o = runtime . sprites [ i ] ;
2013-11-01 22:44:51 -04:00
if ( typeof ( o ) == 'object' && o . constructor == Sprite ) {
2013-10-28 20:00:20 +00:00
$ . each ( o . stacks , function ( index , stack ) {
f ( stack , o ) ;
} ) ;
}
}
$ . each ( stage . stacks , function ( index , stack ) {
2014-04-09 01:17:50 -07:00
f ( stack , stage ) ;
2013-10-28 20:00:20 +00:00
} ) ;
2013-11-01 22:44:51 -04:00
} ;
2013-10-28 20:00:20 +00:00
// Hat triggers
Runtime . prototype . startGreenFlags = function ( ) {
function startIfGreenFlag ( stack , target ) {
if ( stack . op == 'whenGreenFlag' ) interp . toggleThread ( stack , target ) ;
}
this . allStacksDo ( startIfGreenFlag ) ;
2013-11-01 22:44:51 -04:00
} ;
2013-10-28 20:00:20 +00:00
Runtime . prototype . startKeyHats = function ( ch ) {
var keyName = null ;
if ( ( 'A' . charCodeAt ( 0 ) <= ch ) && ( ch <= 'Z' . charCodeAt ( 0 ) ) ||
( 'a' . charCodeAt ( 0 ) <= ch ) && ( ch <= 'z' . charCodeAt ( 0 ) ) )
keyName = String . fromCharCode ( ch ) . toLowerCase ( ) ;
if ( ( '0' . charCodeAt ( 0 ) <= ch ) && ( ch <= '9' . charCodeAt ( 0 ) ) )
keyName = String . fromCharCode ( ch ) ;
if ( ch == 37 ) keyName = "left arrow" ;
if ( ch == 39 ) keyName = "right arrow" ;
if ( ch == 38 ) keyName = "up arrow" ;
if ( ch == 40 ) keyName = "down arrow" ;
if ( ch == 32 ) keyName = "space" ;
if ( keyName == null ) return ;
2013-11-01 22:44:51 -04:00
var startMatchingKeyHats = function ( stack , target ) {
2013-10-28 20:00:20 +00:00
if ( ( stack . op == "whenKeyPressed" ) && ( stack . args [ 0 ] == keyName ) ) {
// Only start the stack if it is not already running
2013-11-01 22:44:51 -04:00
if ( ! interp . isRunning ( stack ) ) {
2013-10-28 20:00:20 +00:00
interp . toggleThread ( stack , target ) ;
2013-11-01 22:44:51 -04:00
}
2013-10-28 20:00:20 +00:00
}
}
runtime . allStacksDo ( startMatchingKeyHats ) ;
2013-11-01 22:44:51 -04:00
} ;
2013-10-28 20:00:20 +00:00
Runtime . prototype . startClickedHats = function ( sprite ) {
function startIfClicked ( stack , target ) {
2013-11-01 22:44:51 -04:00
if ( target == sprite && stack . op == "whenClicked" && ! interp . isRunning ( stack ) ) {
interp . toggleThread ( stack , target ) ;
2013-10-28 20:00:20 +00:00
}
}
runtime . allStacksDo ( startIfClicked ) ;
2013-11-01 22:44:51 -04:00
} ;
2013-10-28 20:00:20 +00:00
// Returns true if a key is pressed.
Runtime . prototype . keyIsDown = function ( ch ) {
return this . keysDown [ ch ] || false ;
2013-11-01 22:44:51 -04:00
} ;
2013-10-28 20:00:20 +00:00
// Sprite named -- returns one of the sprites on the stage.
Runtime . prototype . spriteNamed = function ( n ) {
if ( n == 'Stage' ) return this . stage ;
var selected _sprite = null ;
$ . each ( this . sprites , function ( index , s ) {
if ( s . objName == n ) {
2013-11-01 22:44:51 -04:00
selected _sprite = s ;
2013-10-28 20:00:20 +00:00
return false ;
}
} ) ;
return selected _sprite ;
2013-11-01 22:44:51 -04:00
} ;
2013-10-30 11:40:53 -06:00
Runtime . prototype . getTimeString = function ( which ) {
// Return local time properties.
var now = new Date ( ) ;
switch ( which ) {
case 'hour' : return now . getHours ( ) ;
case 'minute' : return now . getMinutes ( ) ;
case 'second' : return now . getSeconds ( ) ;
case 'year' : return now . getFullYear ( ) ; // four digit year (e.g. 2012)
case 'month' : return now . getMonth ( ) + 1 ; // 1-12
case 'date' : return now . getDate ( ) ; // 1-31
case 'day of week' : return now . getDay ( ) + 1 ; // 1-7, where 1 is Sunday
}
return '' ; // shouldn't happen
2013-11-01 22:44:51 -04:00
} ;
2013-10-30 11:40:53 -06:00
2013-10-28 20:00:20 +00:00
// Reassigns z-indices for layer functions
Runtime . prototype . reassignZ = function ( target , move ) {
var sprites = this . sprites ;
var oldIndex = - 1 ;
$ . each ( this . sprites , function ( index , sprite ) {
if ( sprite == target ) {
// Splice out the sprite from its old position
oldIndex = index ;
sprites . splice ( index , 1 ) ;
}
} ) ;
2013-11-01 22:44:51 -04:00
2013-10-28 20:00:20 +00:00
if ( move == null ) {
// Move to the front
this . sprites . splice ( this . sprites . length , 0 , target ) ;
2013-11-01 22:44:51 -04:00
} else if ( oldIndex - move >= 0 && oldIndex - move < this . sprites . length + 1 ) {
2013-10-28 20:00:20 +00:00
// Move to the new position
this . sprites . splice ( oldIndex - move , 0 , target ) ;
} else {
// No change is required
this . sprites . splice ( oldIndex , 0 , target ) ;
2013-11-01 22:44:51 -04:00
}
2013-10-28 20:00:20 +00:00
// Renumber the z-indices
var newZ = 1 ;
$ . each ( this . sprites , function ( index , sprite ) {
sprite . z = newZ ;
sprite . updateLayer ( ) ;
newZ ++ ;
} ) ;
2013-11-01 22:44:51 -04:00
} ;