mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-06-11 21:11:10 -04:00
Merge pull request #87 from tmickel/feature/sprites
Straw-man implementation of targets/sprites/clones
This commit is contained in:
commit
d9dff49558
20 changed files with 1258 additions and 164 deletions
4
Makefile
4
Makefile
|
@ -13,7 +13,7 @@ watch:
|
|||
$(WEBPACK) --watch
|
||||
|
||||
serve:
|
||||
$(WEBPACK_DEV_SERVER) --content-base ./
|
||||
$(WEBPACK_DEV_SERVER) --host 0.0.0.0 --content-base ./
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
@ -31,4 +31,4 @@ coverage:
|
|||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
.PHONY: build lint test coverage benchmark
|
||||
.PHONY: build lint test coverage benchmark serve
|
||||
|
|
7
index.html
Normal file
7
index.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0; URL='/playground'" />
|
||||
<title>Redirect to playground</title>
|
||||
</head>
|
||||
</html>
|
|
@ -23,6 +23,7 @@
|
|||
"highlightjs": "8.7.0",
|
||||
"json-loader": "0.5.4",
|
||||
"scratch-blocks": "git+https://git@github.com/LLK/scratch-blocks.git",
|
||||
"scratch-render": "git+https://git@github.com/LLK/scratch-render.git",
|
||||
"tap": "5.7.1",
|
||||
"webpack": "1.13.0",
|
||||
"webpack-dev-server": "1.14.1"
|
||||
|
|
|
@ -13,9 +13,14 @@
|
|||
<button id="greenflag">Green flag</button>
|
||||
<button id="stopall">Stop</button>
|
||||
<p>
|
||||
<a id="renderexplorer-link" href="#">Renderer</a><br />
|
||||
<a id="threadexplorer-link" href="#">VM Threads</a><br />
|
||||
<a id="blockexplorer-link" href="#">VM Block Representation</a>
|
||||
</p>
|
||||
<div id="tab-renderexplorer">
|
||||
Render<br />
|
||||
<canvas id="scratch-stage" style="width: 480px; height: 360px;"></canvas>
|
||||
</div>
|
||||
<div id="tab-threadexplorer">
|
||||
Thread explorer
|
||||
<pre id="threadexplorer"></pre>
|
||||
|
@ -28,58 +33,536 @@
|
|||
<div id="blocks"></div>
|
||||
|
||||
<xml id="toolbox" style="display: none">
|
||||
<category name="Events">
|
||||
<block type="event_whenflagclicked"></block>
|
||||
<block type="event_whenbroadcastreceived">
|
||||
<value name="BROADCAST_OPTION">
|
||||
<shadow type="event_broadcast_menu"></shadow>
|
||||
</value>
|
||||
<category name="Motion" colour="#4C97FF">
|
||||
<block type="motion_movesteps">
|
||||
<value name="STEPS">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_turnright">
|
||||
<value name="DEGREES">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">15</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_turnleft">
|
||||
<value name="DEGREES">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">15</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_pointindirection">
|
||||
<value name="DIRECTION">
|
||||
<shadow type="math_angle">
|
||||
<field name="NUM">90</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_pointtowards">
|
||||
<value name="TOWARDS">
|
||||
<shadow type="motion_pointtowards_menu">
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_gotoxy">
|
||||
<value name="X">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="Y">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_goto">
|
||||
<value name="TO">
|
||||
<shadow type="motion_goto_menu">
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_glidesecstoxy">
|
||||
<value name="SECS">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="X">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="Y">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_changexby">
|
||||
<value name="DX">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_setx">
|
||||
<value name="X">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_changeyby">
|
||||
<value name="DY">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_sety">
|
||||
<value name="Y">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_ifonedgebounce"></block>
|
||||
<block type="motion_setrotationstyle">
|
||||
<value name="STYLE">
|
||||
<shadow type="motion_setrotationstyle_menu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_xposition"></block>
|
||||
<block type="motion_yposition"></block>
|
||||
<block type="motion_direction"></block>
|
||||
</category>
|
||||
<category name="Looks" colour="#9966FF">
|
||||
<block type="looks_sayforsecs">
|
||||
<value name="MESSAGE">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">Hello!</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="SECS">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">2</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_say">
|
||||
<value name="MESSAGE">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">Hello!</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_thinkforsecs">
|
||||
<value name="MESSAGE">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">Hmm...</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="SECS">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">2</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_think">
|
||||
<value name="MESSAGE">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">Hmm...</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_show"></block>
|
||||
<block type="looks_hide"></block>
|
||||
<block type="looks_switchcostumeto">
|
||||
<value name="COSTUME">
|
||||
<shadow type="looks_costume"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_nextcostume"></block>
|
||||
<block type="looks_nextbackdrop"></block>
|
||||
<block type="looks_switchbackdropto">
|
||||
<value name="COSTUME">
|
||||
<shadow type="looks_backdrops"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_switchbackdroptoandwait">
|
||||
<value name="COSTUME">
|
||||
<shadow type="looks_backdrops"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_changeeffectby">
|
||||
<value name="EFFECT">
|
||||
<shadow type="looks_effectmenu"></shadow>
|
||||
</value>
|
||||
<value name="CHANGE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_seteffectto">
|
||||
<value name="EFFECT">
|
||||
<shadow type="looks_effectmenu"></shadow>
|
||||
</value>
|
||||
<value name="VALUE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_cleargraphiceffects"></block>
|
||||
<block type="looks_changesizeby">
|
||||
<value name="CHANGE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_setsizeto">
|
||||
<value name="SIZE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">100</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_gotofront"></block>
|
||||
<block type="looks_gobacklayers">
|
||||
<value name="NUM">
|
||||
<shadow type="math_integer">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_costumeorder"></block>
|
||||
<block type="looks_backdroporder"></block>
|
||||
<block type="looks_backdropname"></block>
|
||||
<block type="looks_size"></block>
|
||||
</category>
|
||||
<category name="Sound" colour="#D65CD6">
|
||||
<block type="sound_play">
|
||||
<value name="SOUND_MENU">
|
||||
<shadow type="sound_sounds_option"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sound_playuntildone">
|
||||
<value name="SOUND_MENU">
|
||||
<shadow type="sound_sounds_option"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sound_stopallsounds"></block>
|
||||
<block type="sound_playdrumforbeats">
|
||||
<value name="DRUMTYPE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="BEATS">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0.25</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sound_restforbeats">
|
||||
<value name="BEATS">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0.25</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sound_playnoteforbeats">
|
||||
<value name="NOTE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="BEATS">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0.5</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sound_setinstrumentto">
|
||||
<value name="INSTRUMENT">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sound_changevolumeby">
|
||||
<value name="VOLUME">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">-10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sound_setvolumeto">
|
||||
<value name="VOLUME">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">100</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sound_volume"></block>
|
||||
<block type="sound_changetempoby">
|
||||
<value name="TEMPO">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">20</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sound_settempotobpm">
|
||||
<value name="TEMPO">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">60</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sound_tempo"></block>
|
||||
</category>
|
||||
<category name="Pen" colour="#00B295">
|
||||
<block type="pen_clear"></block>
|
||||
<block type="pen_stamp"></block>
|
||||
<block type="pen_pendown"></block>
|
||||
<block type="pen_penup"></block>
|
||||
<block type="pen_setpencolortocolor">
|
||||
<value name="COLOR">
|
||||
<shadow type="colour_picker">
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="pen_changepencolorby">
|
||||
<value name="COLOR">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="pen_setpencolortonum">
|
||||
<value name="COLOR">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="pen_changepenshadeby">
|
||||
<value name="SHADE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="pen_setpenshadeto">
|
||||
<value name="SHADE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">50</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="pen_changepensizeby">
|
||||
<value name="SIZE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="pen_setpensizeto">
|
||||
<value name="SIZE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
</category>
|
||||
<category name="Data" colour="#FF8C1A">
|
||||
<block type="data_variable">
|
||||
<value name="VARIABLE">
|
||||
<shadow type="data_variablemenu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="data_setvariableto">
|
||||
<value name="VARIABLE">
|
||||
<shadow type="data_variablemenu"></shadow>
|
||||
</value>
|
||||
<value name="VALUE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="data_changevariableby">
|
||||
<value name="VARIABLE">
|
||||
<shadow type="data_variablemenu"></shadow>
|
||||
</value>
|
||||
<value name="VALUE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="data_showvariable">
|
||||
<value name="VARIABLE">
|
||||
<shadow type="data_variablemenu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="data_hidevariable">
|
||||
<value name="VARIABLE">
|
||||
<shadow type="data_variablemenu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
</category>
|
||||
<category name="Events" colour="#FFD500">
|
||||
<block type="event_whenflagclicked"></block>
|
||||
<block type="event_whenkeypressed">
|
||||
<value name="KEY_OPTION">
|
||||
<shadow type="event_keyoptions"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="event_whenthisspriteclicked"></block>
|
||||
<block type="event_whenbackdropswitchesto">
|
||||
<value name="BACKDROP">
|
||||
<shadow type="event_backdrops"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="event_whengreaterthan">
|
||||
<value name="WHENGREATERTHANMENU">
|
||||
<shadow type="event_whengreaterthanmenu"></shadow>
|
||||
</value>
|
||||
<value name="VALUE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="event_whenbroadcastreceived">
|
||||
<value name="BROADCAST_OPTION">
|
||||
<shadow type="event_broadcast_menu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="event_broadcast">
|
||||
<value name="BROADCAST_OPTION">
|
||||
<shadow type="event_broadcast_menu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="event_broadcastandwait">
|
||||
<value name="BROADCAST_OPTION">
|
||||
<shadow type="event_broadcast_menu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
</category>
|
||||
<category name="Control" colour="#FFAB19">
|
||||
<block type="control_wait">
|
||||
<value name="DURATION">
|
||||
<shadow type="math_positive_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="control_repeat">
|
||||
<value name="TIMES">
|
||||
<shadow type="math_whole_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="control_forever"></block>
|
||||
<block type="control_if"></block>
|
||||
<block type="control_if_else"></block>
|
||||
<block type="control_wait_until"></block>
|
||||
<block type="control_repeat_until"></block>
|
||||
<block type="control_stop">
|
||||
<value name="STOP_OPTION">
|
||||
<shadow type="control_stop_menu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="control_start_as_clone"></block>
|
||||
<block type="control_create_clone_of">
|
||||
<value name="CLONE_OPTION">
|
||||
<shadow type="control_create_clone_of_menu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="control_delete_this_clone"></block>
|
||||
</category>
|
||||
<category name="Sensing" colour="#4CBFE6">
|
||||
<block type="sensing_touchingobject">
|
||||
<value name="TOUCHINGOBJECTMENU">
|
||||
<shadow type="sensing_touchingobjectmenu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sensing_touchingcolor">
|
||||
<value name="COLOR">
|
||||
<shadow type="colour_picker"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sensing_coloristouchingcolor">
|
||||
<value name="COLOR">
|
||||
<shadow type="colour_picker"></shadow>
|
||||
</value>
|
||||
<value name="COLOR2">
|
||||
<shadow type="colour_picker"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sensing_distanceto">
|
||||
<value name="DISTANCETOMENU">
|
||||
<shadow type="sensing_distancetomenu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sensing_askandwait">
|
||||
<value name="QUESTION">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">What's your name?</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sensing_answer"></block>
|
||||
<block type="sensing_keypressed">
|
||||
<value name="KEY_OPTIONS">
|
||||
<shadow type="sensing_keyoptions"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="event_broadcast">
|
||||
<value name="BROADCAST_OPTION">
|
||||
<shadow type="event_broadcast_menu"></shadow>
|
||||
</value>
|
||||
<block type="sensing_mousedown"></block>
|
||||
<block type="sensing_mousex"></block>
|
||||
<block type="sensing_mousey"></block>
|
||||
<block type="sensing_loudness"></block>
|
||||
<block type="sensing_videoon">
|
||||
<value name="VIDEOONMENU1">
|
||||
<shadow type="sensing_videoonmenuone"></shadow>
|
||||
</value>
|
||||
<value name="VIDEOONMENU2">
|
||||
<shadow type="sensing_videoonmenutwo"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="event_broadcastandwait">
|
||||
<value name="BROADCAST_OPTION">
|
||||
<shadow type="event_broadcast_menu"></shadow>
|
||||
</value>
|
||||
<block type="sensing_videotoggle">
|
||||
<value name="VIDEOTOGGLEMENU">
|
||||
<shadow type="sensing_videotogglemenu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
</category>
|
||||
<category name="Control">
|
||||
<block type="control_wait">
|
||||
<value name="DURATION">
|
||||
<block type="sensing_setvideotransparency">
|
||||
<value name="TRANSPARENCY">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
<field name="NUM">50</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="control_repeat">
|
||||
<value name="TIMES">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">4</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="control_forever"></block>
|
||||
<block type="control_if"></block>
|
||||
<block type="control_if_else"></block>
|
||||
<block type="control_wait_until"></block>
|
||||
<block type="control_repeat_until"></block>
|
||||
<block type="control_stop">
|
||||
<value name="STOP_OPTION">
|
||||
<shadow type="control_stop_menu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="control_start_as_clone"></block>
|
||||
<block type="control_create_clone_of">
|
||||
<value name="CLONE_OPTION">
|
||||
<shadow type="control_create_clone_of_menu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="control_delete_this_clone"></block>
|
||||
</category>
|
||||
<category name="Operators">
|
||||
</block>
|
||||
<block type="sensing_timer"></block>
|
||||
<block type="sensing_resettimer"></block>
|
||||
<block type="sensing_current">
|
||||
<value name="CURRENTMENU">
|
||||
<shadow type="sensing_currentmenu"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sensing_dayssince2000"></block>
|
||||
<block type="sensing_username"></block>
|
||||
</category>
|
||||
<category name="Operators" colour="#40BF4A">
|
||||
<block type="operator_add">
|
||||
<value name="NUM1">
|
||||
<shadow type="math_number">
|
||||
|
@ -193,7 +676,7 @@
|
|||
</block>
|
||||
<block type="operator_letter_of">
|
||||
<value name="LETTER">
|
||||
<shadow type="math_number">
|
||||
<shadow type="math_whole_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
|
@ -239,8 +722,9 @@
|
|||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
</category>
|
||||
</xml>
|
||||
</category>
|
||||
<category name="More Blocks" colour="#FF6680"></category>
|
||||
</xml>
|
||||
|
||||
<!-- Syntax highlighter -->
|
||||
<script src="../node_modules/highlightjs/highlight.pack.min.js"></script>
|
||||
|
@ -249,6 +733,9 @@
|
|||
<script src="../node_modules/scratch-blocks/blockly_compressed_vertical.js"></script>
|
||||
<script src="../node_modules/scratch-blocks/blocks_compressed.js"></script>
|
||||
<script src="../node_modules/scratch-blocks/blocks_compressed_vertical.js"></script>
|
||||
<script src="../node_modules/scratch-blocks/msg/messages.js"></script>
|
||||
<!-- Renderer -->
|
||||
<script src="../node_modules/scratch-render/render.js"></script>
|
||||
<!-- VM Worker -->
|
||||
<script src="../vm.worker.js"></script>
|
||||
<!-- Playground -->
|
||||
|
|
|
@ -32,6 +32,6 @@ a {
|
|||
font-size: 10pt;
|
||||
}
|
||||
|
||||
#tab-blockexplorer {
|
||||
#tab-blockexplorer, #tab-threadexplorer {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,10 @@ window.onload = function() {
|
|||
var vm = new window.VirtualMachine();
|
||||
window.vm = vm;
|
||||
|
||||
var canvas = document.getElementById('scratch-stage');
|
||||
window.renderer = new window.RenderWebGLLocal(canvas);
|
||||
window.renderer.connectWorker(window.vm.vmWorker);
|
||||
|
||||
var toolbox = document.getElementById('toolbox');
|
||||
var workspace = window.Blockly.inject('blocks', {
|
||||
toolbox: toolbox,
|
||||
|
@ -47,11 +51,14 @@ window.onload = function() {
|
|||
}
|
||||
};
|
||||
|
||||
// Only request data from the VM thread if the appropriate tab is open.
|
||||
window.exploreTabOpen = false;
|
||||
var getPlaygroundData = function () {
|
||||
vm.getPlaygroundData();
|
||||
window.requestAnimationFrame(getPlaygroundData);
|
||||
if (window.exploreTabOpen) {
|
||||
window.requestAnimationFrame(getPlaygroundData);
|
||||
}
|
||||
};
|
||||
getPlaygroundData();
|
||||
|
||||
vm.on('playgroundData', function(data) {
|
||||
updateThreadExplorer(data.threads);
|
||||
|
@ -71,10 +78,20 @@ window.onload = function() {
|
|||
vm.on('BLOCK_GLOW_OFF', function(data) {
|
||||
workspace.glowBlock(data.id, false);
|
||||
});
|
||||
vm.on('VISUAL_REPORT', function(data) {
|
||||
workspace.reportValue(data.id, data.value);
|
||||
});
|
||||
|
||||
// Run threads
|
||||
vm.start();
|
||||
|
||||
// Inform VM of animation frames.
|
||||
var animate = function() {
|
||||
window.vm.animationFrame();
|
||||
requestAnimationFrame(animate);
|
||||
};
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
// Handlers for green flag and stop all.
|
||||
document.getElementById('greenflag').addEventListener('click', function() {
|
||||
vm.greenFlag();
|
||||
|
@ -85,16 +102,30 @@ window.onload = function() {
|
|||
|
||||
var tabBlockExplorer = document.getElementById('tab-blockexplorer');
|
||||
var tabThreadExplorer = document.getElementById('tab-threadexplorer');
|
||||
var tabRenderExplorer = document.getElementById('tab-renderexplorer');
|
||||
|
||||
// Handlers to show different explorers.
|
||||
document.getElementById('threadexplorer-link').addEventListener('click',
|
||||
function () {
|
||||
window.exploreTabOpen = true;
|
||||
getPlaygroundData();
|
||||
tabBlockExplorer.style.display = 'none';
|
||||
tabRenderExplorer.style.display = 'none';
|
||||
tabThreadExplorer.style.display = 'block';
|
||||
});
|
||||
document.getElementById('blockexplorer-link').addEventListener('click',
|
||||
function () {
|
||||
window.exploreTabOpen = true;
|
||||
getPlaygroundData();
|
||||
tabBlockExplorer.style.display = 'block';
|
||||
tabRenderExplorer.style.display = 'none';
|
||||
tabThreadExplorer.style.display = 'none';
|
||||
});
|
||||
document.getElementById('renderexplorer-link').addEventListener('click',
|
||||
function () {
|
||||
window.exploreTabOpen = false;
|
||||
tabBlockExplorer.style.display = 'none';
|
||||
tabRenderExplorer.style.display = 'block';
|
||||
tabThreadExplorer.style.display = 'none';
|
||||
});
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ function Scratch3ControlBlocks(runtime) {
|
|||
Scratch3ControlBlocks.prototype.getPrimitives = function() {
|
||||
return {
|
||||
'control_repeat': this.repeat,
|
||||
'control_repeat_until': this.repeatUntil,
|
||||
'control_forever': this.forever,
|
||||
'control_wait': this.wait,
|
||||
'control_if': this.if,
|
||||
|
@ -28,16 +29,50 @@ Scratch3ControlBlocks.prototype.repeat = function(args, util) {
|
|||
if (util.stackFrame.loopCounter === undefined) {
|
||||
util.stackFrame.loopCounter = parseInt(args.TIMES);
|
||||
}
|
||||
// Decrease counter
|
||||
util.stackFrame.loopCounter--;
|
||||
// If we still have some left, start the substack
|
||||
if (util.stackFrame.loopCounter >= 0) {
|
||||
util.startSubstack();
|
||||
// Only execute once per frame.
|
||||
// When the substack finishes, `repeat` will be executed again and
|
||||
// the second branch will be taken, yielding for the rest of the frame.
|
||||
if (!util.stackFrame.executedInFrame) {
|
||||
util.stackFrame.executedInFrame = true;
|
||||
// Decrease counter
|
||||
util.stackFrame.loopCounter--;
|
||||
// If we still have some left, start the substack
|
||||
if (util.stackFrame.loopCounter >= 0) {
|
||||
util.startSubstack();
|
||||
}
|
||||
} else {
|
||||
util.stackFrame.executedInFrame = false;
|
||||
util.yieldFrame();
|
||||
}
|
||||
};
|
||||
|
||||
Scratch3ControlBlocks.prototype.repeatUntil = function(args, util) {
|
||||
// Only execute once per frame.
|
||||
// When the substack finishes, `repeat` will be executed again and
|
||||
// the second branch will be taken, yielding for the rest of the frame.
|
||||
if (!util.stackFrame.executedInFrame) {
|
||||
util.stackFrame.executedInFrame = true;
|
||||
// If the condition is true, start the substack.
|
||||
if (!args.CONDITION) {
|
||||
util.startSubstack();
|
||||
}
|
||||
} else {
|
||||
util.stackFrame.executedInFrame = false;
|
||||
util.yieldFrame();
|
||||
}
|
||||
};
|
||||
|
||||
Scratch3ControlBlocks.prototype.forever = function(args, util) {
|
||||
util.startSubstack();
|
||||
// Only execute once per frame.
|
||||
// When the substack finishes, `forever` will be executed again and
|
||||
// the second branch will be taken, yielding for the rest of the frame.
|
||||
if (!util.stackFrame.executedInFrame) {
|
||||
util.stackFrame.executedInFrame = true;
|
||||
util.startSubstack();
|
||||
} else {
|
||||
util.stackFrame.executedInFrame = false;
|
||||
util.yieldFrame();
|
||||
}
|
||||
};
|
||||
|
||||
Scratch3ControlBlocks.prototype.wait = function(args) {
|
||||
|
@ -51,8 +86,8 @@ Scratch3ControlBlocks.prototype.wait = function(args) {
|
|||
Scratch3ControlBlocks.prototype.if = function(args, util) {
|
||||
// Only execute one time. `if` will be returned to
|
||||
// when the substack finishes, but it shouldn't execute again.
|
||||
if (util.stackFrame.executed === undefined) {
|
||||
util.stackFrame.executed = true;
|
||||
if (util.stackFrame.executedInFrame === undefined) {
|
||||
util.stackFrame.executedInFrame = true;
|
||||
if (args.CONDITION) {
|
||||
util.startSubstack();
|
||||
}
|
||||
|
@ -62,8 +97,8 @@ Scratch3ControlBlocks.prototype.if = function(args, util) {
|
|||
Scratch3ControlBlocks.prototype.ifElse = function(args, util) {
|
||||
// Only execute one time. `ifElse` will be returned to
|
||||
// when the substack finishes, but it shouldn't execute again.
|
||||
if (util.stackFrame.executed === undefined) {
|
||||
util.stackFrame.executed = true;
|
||||
if (util.stackFrame.executedInFrame === undefined) {
|
||||
util.stackFrame.executedInFrame = true;
|
||||
if (args.CONDITION) {
|
||||
util.startSubstack(1);
|
||||
} else {
|
||||
|
|
98
src/blocks/scratch3_looks.js
Normal file
98
src/blocks/scratch3_looks.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
function Scratch3LooksBlocks(runtime) {
|
||||
/**
|
||||
* The runtime instantiating this block package.
|
||||
* @type {Runtime}
|
||||
*/
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the block primitives implemented by this package.
|
||||
* @return {Object.<string, Function>} Mapping of opcode to Function.
|
||||
*/
|
||||
Scratch3LooksBlocks.prototype.getPrimitives = function() {
|
||||
return {
|
||||
'looks_say': this.say,
|
||||
'looks_sayforsecs': this.sayforsecs,
|
||||
'looks_think': this.think,
|
||||
'looks_thinkforsecs': this.sayforsecs,
|
||||
'looks_show': this.show,
|
||||
'looks_hide': this.hide,
|
||||
'looks_effectmenu': this.effectMenu,
|
||||
'looks_changeeffectby': this.changeEffect,
|
||||
'looks_seteffectto': this.setEffect,
|
||||
'looks_cleargraphiceffects': this.clearEffects,
|
||||
'looks_changesizeby': this.changeSize,
|
||||
'looks_setsizeto': this.setSize,
|
||||
'looks_size': this.getSize
|
||||
};
|
||||
};
|
||||
|
||||
Scratch3LooksBlocks.prototype.say = function (args, util) {
|
||||
util.target.setSay('say', args.MESSAGE);
|
||||
};
|
||||
|
||||
Scratch3LooksBlocks.prototype.sayforsecs = function (args, util) {
|
||||
util.target.setSay('say', args.MESSAGE);
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(function() {
|
||||
// Clear say bubble and proceed.
|
||||
util.target.setSay();
|
||||
resolve();
|
||||
}, 1000 * args.SECS);
|
||||
});
|
||||
};
|
||||
|
||||
Scratch3LooksBlocks.prototype.think = function (args, util) {
|
||||
util.target.setSay('think', args.MESSAGE);
|
||||
};
|
||||
|
||||
Scratch3LooksBlocks.prototype.thinkforsecs = function (args, util) {
|
||||
util.target.setSay('think', args.MESSAGE);
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(function() {
|
||||
// Clear say bubble and proceed.
|
||||
util.target.setSay();
|
||||
resolve();
|
||||
}, 1000 * args.SECS);
|
||||
});
|
||||
};
|
||||
|
||||
Scratch3LooksBlocks.prototype.show = function (args, util) {
|
||||
util.target.setVisible(true);
|
||||
};
|
||||
|
||||
Scratch3LooksBlocks.prototype.hide = function (args, util) {
|
||||
util.target.setVisible(false);
|
||||
};
|
||||
|
||||
Scratch3LooksBlocks.prototype.effectMenu = function (args) {
|
||||
return args.EFFECT.toLowerCase();
|
||||
};
|
||||
|
||||
Scratch3LooksBlocks.prototype.changeEffect = function (args, util) {
|
||||
var newValue = args.CHANGE + util.target.effects[args.EFFECT];
|
||||
util.target.setEffect(args.EFFECT, newValue);
|
||||
};
|
||||
|
||||
Scratch3LooksBlocks.prototype.setEffect = function (args, util) {
|
||||
util.target.setEffect(args.EFFECT, args.VALUE);
|
||||
};
|
||||
|
||||
Scratch3LooksBlocks.prototype.clearEffects = function (args, util) {
|
||||
util.target.clearEffects();
|
||||
};
|
||||
|
||||
Scratch3LooksBlocks.prototype.changeSize = function (args, util) {
|
||||
util.target.setSize(util.target.size + args.CHANGE);
|
||||
};
|
||||
|
||||
Scratch3LooksBlocks.prototype.setSize = function (args, util) {
|
||||
util.target.setSize(args.SIZE);
|
||||
};
|
||||
|
||||
Scratch3LooksBlocks.prototype.getSize = function (args, util) {
|
||||
return util.target.size;
|
||||
};
|
||||
|
||||
module.exports = Scratch3LooksBlocks;
|
83
src/blocks/scratch3_motion.js
Normal file
83
src/blocks/scratch3_motion.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
var MathUtil = require('../util/math-util');
|
||||
|
||||
function Scratch3MotionBlocks(runtime) {
|
||||
/**
|
||||
* The runtime instantiating this block package.
|
||||
* @type {Runtime}
|
||||
*/
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the block primitives implemented by this package.
|
||||
* @return {Object.<string, Function>} Mapping of opcode to Function.
|
||||
*/
|
||||
Scratch3MotionBlocks.prototype.getPrimitives = function() {
|
||||
return {
|
||||
'motion_movesteps': this.moveSteps,
|
||||
'motion_gotoxy': this.goToXY,
|
||||
'motion_turnright': this.turnRight,
|
||||
'motion_turnleft': this.turnLeft,
|
||||
'motion_pointindirection': this.pointInDirection,
|
||||
'motion_changexby': this.changeX,
|
||||
'motion_setx': this.setX,
|
||||
'motion_changeyby': this.changeY,
|
||||
'motion_sety': this.setY,
|
||||
'motion_xposition': this.getX,
|
||||
'motion_yposition': this.getY,
|
||||
'motion_direction': this.getDirection
|
||||
};
|
||||
};
|
||||
|
||||
Scratch3MotionBlocks.prototype.moveSteps = function (args, util) {
|
||||
var radians = MathUtil.degToRad(util.target.direction);
|
||||
var dx = args.STEPS * Math.cos(radians);
|
||||
var dy = args.STEPS * Math.sin(radians);
|
||||
util.target.setXY(util.target.x + dx, util.target.y + dy);
|
||||
};
|
||||
|
||||
Scratch3MotionBlocks.prototype.goToXY = function (args, util) {
|
||||
util.target.setXY(args.X, args.Y);
|
||||
};
|
||||
|
||||
Scratch3MotionBlocks.prototype.turnRight = function (args, util) {
|
||||
util.target.setDirection(util.target.direction + args.DEGREES);
|
||||
};
|
||||
|
||||
Scratch3MotionBlocks.prototype.turnLeft = function (args, util) {
|
||||
util.target.setDirection(util.target.direction - args.DEGREES);
|
||||
};
|
||||
|
||||
Scratch3MotionBlocks.prototype.pointInDirection = function (args, util) {
|
||||
util.target.setDirection(args.DIRECTION);
|
||||
};
|
||||
|
||||
Scratch3MotionBlocks.prototype.changeX = function (args, util) {
|
||||
util.target.setXY(util.target.x + args.DX, util.target.y);
|
||||
};
|
||||
|
||||
Scratch3MotionBlocks.prototype.setX = function (args, util) {
|
||||
util.target.setXY(args.X, util.target.y);
|
||||
};
|
||||
|
||||
Scratch3MotionBlocks.prototype.changeY = function (args, util) {
|
||||
util.target.setXY(util.target.x, util.target.y + args.DY);
|
||||
};
|
||||
|
||||
Scratch3MotionBlocks.prototype.setY = function (args, util) {
|
||||
util.target.setXY(util.target.x, args.Y);
|
||||
};
|
||||
|
||||
Scratch3MotionBlocks.prototype.getX = function (args, util) {
|
||||
return util.target.x;
|
||||
};
|
||||
|
||||
Scratch3MotionBlocks.prototype.getY = function (args, util) {
|
||||
return util.target.y;
|
||||
};
|
||||
|
||||
Scratch3MotionBlocks.prototype.getDirection = function (args, util) {
|
||||
return util.target.direction;
|
||||
};
|
||||
|
||||
module.exports = Scratch3MotionBlocks;
|
|
@ -1,5 +1,3 @@
|
|||
var Promise = require('promise');
|
||||
|
||||
function Scratch3OperatorsBlocks(runtime) {
|
||||
/**
|
||||
* The runtime instantiating this block package.
|
||||
|
@ -15,6 +13,9 @@ function Scratch3OperatorsBlocks(runtime) {
|
|||
Scratch3OperatorsBlocks.prototype.getPrimitives = function() {
|
||||
return {
|
||||
'math_number': this.number,
|
||||
'math_positive_number': this.number,
|
||||
'math_whole_number': this.number,
|
||||
'math_angle': this.number,
|
||||
'text': this.text,
|
||||
'operator_add': this.add,
|
||||
'operator_subtract': this.subtract,
|
||||
|
@ -31,7 +32,12 @@ Scratch3OperatorsBlocks.prototype.getPrimitives = function() {
|
|||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.number = function (args) {
|
||||
return Number(args.NUM);
|
||||
var num = Number(args.NUM);
|
||||
if (num !== num) {
|
||||
// NaN
|
||||
return 0;
|
||||
}
|
||||
return num;
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.text = function (args) {
|
||||
|
@ -79,16 +85,16 @@ Scratch3OperatorsBlocks.prototype.not = function (args) {
|
|||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.random = function (args) {
|
||||
// As a demo, this implementation of random returns after 1 second of yield.
|
||||
// @todo Match Scratch 2.0 implementation with int-truncation.
|
||||
// See: http://bit.ly/1Qc0GzC
|
||||
var examplePromise = new Promise(function(resolve) {
|
||||
setTimeout(function() {
|
||||
var res = (Math.random() * (args.TO - args.FROM)) + args.FROM;
|
||||
resolve(res);
|
||||
}, 1000);
|
||||
});
|
||||
return examplePromise;
|
||||
var low = args.FROM <= args.TO ? args.FROM : args.TO;
|
||||
var high = args.FROM <= args.TO ? args.TO : args.FROM;
|
||||
if (low == high) return low;
|
||||
// If both low and high are ints, truncate the result to an int.
|
||||
var lowInt = low == parseInt(low);
|
||||
var highInt = high == parseInt(high);
|
||||
if (lowInt && highInt) {
|
||||
return low + parseInt(Math.random() * ((high + 1) - low));
|
||||
}
|
||||
return (Math.random() * (high - low)) + low;
|
||||
};
|
||||
|
||||
module.exports = Scratch3OperatorsBlocks;
|
||||
|
|
|
@ -7,12 +7,13 @@ var Thread = require('./thread');
|
|||
*/
|
||||
var execute = function (sequencer, thread) {
|
||||
var runtime = sequencer.runtime;
|
||||
var target = runtime.targetForThread(thread);
|
||||
|
||||
// Current block to execute is the one on the top of the stack.
|
||||
var currentBlockId = thread.peekStack();
|
||||
var currentStackFrame = thread.peekStackFrame();
|
||||
|
||||
var opcode = runtime.blocks.getOpcode(currentBlockId);
|
||||
var opcode = target.blocks.getOpcode(currentBlockId);
|
||||
|
||||
if (!opcode) {
|
||||
console.warn('Could not get opcode for block: ' + currentBlockId);
|
||||
|
@ -29,13 +30,13 @@ var execute = function (sequencer, thread) {
|
|||
var argValues = {};
|
||||
|
||||
// Add all fields on this block to the argValues.
|
||||
var fields = runtime.blocks.getFields(currentBlockId);
|
||||
var fields = target.blocks.getFields(currentBlockId);
|
||||
for (var fieldName in fields) {
|
||||
argValues[fieldName] = fields[fieldName].value;
|
||||
}
|
||||
|
||||
// Recursively evaluate input blocks.
|
||||
var inputs = runtime.blocks.getInputs(currentBlockId);
|
||||
var inputs = target.blocks.getInputs(currentBlockId);
|
||||
for (var inputName in inputs) {
|
||||
var input = inputs[inputName];
|
||||
var inputBlockId = input.block;
|
||||
|
@ -62,14 +63,21 @@ var execute = function (sequencer, thread) {
|
|||
|
||||
var primitiveReportedValue = null;
|
||||
primitiveReportedValue = blockFunction(argValues, {
|
||||
yield: thread.yield.bind(thread),
|
||||
yield: function() {
|
||||
thread.setStatus(Thread.STATUS_YIELD);
|
||||
},
|
||||
yieldFrame: function() {
|
||||
thread.setStatus(Thread.STATUS_YIELD_FRAME);
|
||||
},
|
||||
done: function() {
|
||||
thread.setStatus(Thread.STATUS_RUNNING);
|
||||
sequencer.proceedThread(thread);
|
||||
},
|
||||
stackFrame: currentStackFrame.executionContext,
|
||||
startSubstack: function (substackNum) {
|
||||
sequencer.stepToSubstack(thread, substackNum);
|
||||
}
|
||||
},
|
||||
target: target
|
||||
});
|
||||
|
||||
// Deal with any reported value.
|
||||
|
@ -82,21 +90,33 @@ var execute = function (sequencer, thread) {
|
|||
if (isPromise) {
|
||||
if (thread.status === Thread.STATUS_RUNNING) {
|
||||
// Primitive returned a promise; automatically yield thread.
|
||||
thread.status = Thread.STATUS_YIELD;
|
||||
thread.setStatus(Thread.STATUS_YIELD);
|
||||
}
|
||||
// Promise handlers
|
||||
primitiveReportedValue.then(function(resolvedValue) {
|
||||
// Promise resolved: the primitive reported a value.
|
||||
thread.pushReportedValue(resolvedValue);
|
||||
// Report the value visually if necessary.
|
||||
if (typeof resolvedValue !== 'undefined' &&
|
||||
thread.peekStack() === thread.topBlock) {
|
||||
runtime.visualReport(thread.peekStack(), resolvedValue);
|
||||
}
|
||||
thread.setStatus(Thread.STATUS_RUNNING);
|
||||
sequencer.proceedThread(thread);
|
||||
}, function(rejectionReason) {
|
||||
// Promise rejected: the primitive had some error.
|
||||
// Log it and proceed.
|
||||
console.warn('Primitive rejected promise: ', rejectionReason);
|
||||
thread.setStatus(Thread.STATUS_RUNNING);
|
||||
sequencer.proceedThread(thread);
|
||||
});
|
||||
} else if (thread.status === Thread.STATUS_RUNNING) {
|
||||
thread.pushReportedValue(primitiveReportedValue);
|
||||
// Report the value visually if necessary.
|
||||
if (typeof primitiveReportedValue !== 'undefined' &&
|
||||
thread.peekStack() === thread.topBlock) {
|
||||
runtime.visualReport(thread.peekStack(), primitiveReportedValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -6,23 +6,25 @@ var util = require('util');
|
|||
var defaultBlockPackages = {
|
||||
'scratch3_control': require('../blocks/scratch3_control'),
|
||||
'scratch3_event': require('../blocks/scratch3_event'),
|
||||
'scratch3_looks': require('../blocks/scratch3_looks'),
|
||||
'scratch3_motion': require('../blocks/scratch3_motion'),
|
||||
'scratch3_operators': require('../blocks/scratch3_operators')
|
||||
};
|
||||
|
||||
/**
|
||||
* Manages blocks, stacks, and the sequencer.
|
||||
* @param {!Blocks} blocks Blocks instance for this runtime.
|
||||
* Manages targets, stacks, and the sequencer.
|
||||
* @param {!Array.<Target>} targets List of targets for this runtime.
|
||||
*/
|
||||
function Runtime (blocks) {
|
||||
function Runtime (targets) {
|
||||
// Bind event emitter
|
||||
EventEmitter.call(this);
|
||||
|
||||
// State for the runtime
|
||||
|
||||
/**
|
||||
* Block management and storage
|
||||
* Target management and storage.
|
||||
*/
|
||||
this.blocks = blocks;
|
||||
this.targets = targets;
|
||||
|
||||
/**
|
||||
* A list of threads that are currently running in the VM.
|
||||
|
@ -67,6 +69,12 @@ Runtime.BLOCK_GLOW_ON = 'BLOCK_GLOW_ON';
|
|||
*/
|
||||
Runtime.BLOCK_GLOW_OFF = 'BLOCK_GLOW_OFF';
|
||||
|
||||
/**
|
||||
* Event name for visual value report.
|
||||
* @const {string}
|
||||
*/
|
||||
Runtime.VISUAL_REPORT = 'VISUAL_REPORT';
|
||||
|
||||
/**
|
||||
* Inherit from EventEmitter
|
||||
*/
|
||||
|
@ -75,7 +83,7 @@ util.inherits(Runtime, EventEmitter);
|
|||
/**
|
||||
* How rapidly we try to step threads, in ms.
|
||||
*/
|
||||
Runtime.THREAD_STEP_INTERVAL = 1000 / 30;
|
||||
Runtime.THREAD_STEP_INTERVAL = 1000 / 60;
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
@ -163,32 +171,13 @@ Runtime.prototype.greenFlag = function () {
|
|||
this._removeThread(this.threads[i]);
|
||||
}
|
||||
// Add all top stacks with green flag
|
||||
var stacks = this.blocks.getStacks();
|
||||
for (var j = 0; j < stacks.length; j++) {
|
||||
var topBlock = stacks[j];
|
||||
if (this.blocks.getBlock(topBlock).opcode === 'event_whenflagclicked') {
|
||||
this._pushThread(stacks[j]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Distance sensor hack
|
||||
*/
|
||||
Runtime.prototype.startDistanceSensors = function () {
|
||||
// Add all top stacks with distance sensor
|
||||
var stacks = this.blocks.getStacks();
|
||||
for (var j = 0; j < stacks.length; j++) {
|
||||
var topBlock = stacks[j];
|
||||
if (this.blocks.getBlock(topBlock).opcode ===
|
||||
'wedo_whendistanceclose') {
|
||||
var alreadyRunning = false;
|
||||
for (var k = 0; k < this.threads.length; k++) {
|
||||
if (this.threads[k].topBlock === topBlock) {
|
||||
alreadyRunning = true;
|
||||
}
|
||||
}
|
||||
if (!alreadyRunning) {
|
||||
for (var t = 0; t < this.targets.length; t++) {
|
||||
var target = this.targets[t];
|
||||
var stacks = target.blocks.getStacks();
|
||||
for (var j = 0; j < stacks.length; j++) {
|
||||
var topBlock = stacks[j];
|
||||
if (target.blocks.getBlock(topBlock).opcode ===
|
||||
'event_whenflagclicked') {
|
||||
this._pushThread(stacks[j]);
|
||||
}
|
||||
}
|
||||
|
@ -228,9 +217,6 @@ Runtime.prototype._step = function () {
|
|||
* @param {boolean} isGlowing True to turn on glow; false to turn off.
|
||||
*/
|
||||
Runtime.prototype.glowBlock = function (blockId, isGlowing) {
|
||||
if (!this.blocks.getBlock(blockId)) {
|
||||
return;
|
||||
}
|
||||
if (isGlowing) {
|
||||
this.emit(Runtime.BLOCK_GLOW_ON, blockId);
|
||||
} else {
|
||||
|
@ -239,28 +225,45 @@ Runtime.prototype.glowBlock = function (blockId, isGlowing) {
|
|||
};
|
||||
|
||||
/**
|
||||
* setInterval implementation that works in a WebWorker or not.
|
||||
* @param {?Function} fcn Function to call.
|
||||
* @param {number} interval Interval at which to call it.
|
||||
* @return {number} Value returned by setInterval.
|
||||
* Emit value for reporter to show in the blocks.
|
||||
* @param {string} blockId ID for the block.
|
||||
* @param {string} value Value to show associated with the block.
|
||||
*/
|
||||
Runtime.prototype._setInterval = function(fcn, interval) {
|
||||
var setInterval = null;
|
||||
if (typeof window !== 'undefined' && window.setInterval) {
|
||||
setInterval = window.setInterval;
|
||||
} else if (typeof self !== 'undefined' && self.setInterval) {
|
||||
setInterval = self.setInterval;
|
||||
} else {
|
||||
return;
|
||||
Runtime.prototype.visualReport = function (blockId, value) {
|
||||
this.emit(Runtime.VISUAL_REPORT, blockId, String(value));
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the Target for a particular thread.
|
||||
* @param {!Thread} thread Thread to determine target for.
|
||||
* @return {?Target} Target object, if one exists.
|
||||
*/
|
||||
Runtime.prototype.targetForThread = function (thread) {
|
||||
// @todo This is a messy solution,
|
||||
// but prevents having circular data references.
|
||||
// Have a map or some other way to associate target with threads.
|
||||
for (var t = 0; t < this.targets.length; t++) {
|
||||
var target = this.targets[t];
|
||||
if (target.blocks.getBlock(thread.topBlock)) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle an animation frame from the main thread.
|
||||
*/
|
||||
Runtime.prototype.animationFrame = function () {
|
||||
if (self.renderer) {
|
||||
self.renderer.draw();
|
||||
}
|
||||
return setInterval(fcn, interval);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up timers to repeatedly step in a browser
|
||||
*/
|
||||
Runtime.prototype.start = function () {
|
||||
this._setInterval(function() {
|
||||
self.setInterval(function() {
|
||||
this._step();
|
||||
}.bind(this), Runtime.THREAD_STEP_INTERVAL);
|
||||
};
|
||||
|
|
|
@ -36,6 +36,13 @@ Sequencer.prototype.stepThreads = function (threads) {
|
|||
var inactiveThreads = [];
|
||||
// If all of the threads are yielding, we should yield.
|
||||
var numYieldingThreads = 0;
|
||||
// Clear all yield statuses that were for the previous frame.
|
||||
for (var t = 0; t < threads.length; t++) {
|
||||
if (threads[t].status === Thread.STATUS_YIELD_FRAME) {
|
||||
threads[t].setStatus(Thread.STATUS_RUNNING);
|
||||
}
|
||||
}
|
||||
|
||||
// While there are still threads to run and we are within WORK_TIME,
|
||||
// continue executing threads.
|
||||
while (threads.length > 0 &&
|
||||
|
@ -51,9 +58,10 @@ Sequencer.prototype.stepThreads = function (threads) {
|
|||
if (activeThread.status === Thread.STATUS_RUNNING) {
|
||||
// Normal-mode thread: step.
|
||||
this.startThread(activeThread);
|
||||
} else if (activeThread.status === Thread.STATUS_YIELD) {
|
||||
} else if (activeThread.status === Thread.STATUS_YIELD ||
|
||||
activeThread.status === Thread.STATUS_YIELD_FRAME) {
|
||||
// Yielding thread: do nothing for this step.
|
||||
continue;
|
||||
numYieldingThreads++;
|
||||
}
|
||||
if (activeThread.stack.length === 0 &&
|
||||
activeThread.status === Thread.STATUS_DONE) {
|
||||
|
@ -77,17 +85,14 @@ Sequencer.prototype.stepThreads = function (threads) {
|
|||
Sequencer.prototype.startThread = function (thread) {
|
||||
var currentBlockId = thread.peekStack();
|
||||
if (!currentBlockId) {
|
||||
// A "null block" - empty substack. Pop the stack.
|
||||
// A "null block" - empty substack.
|
||||
// Yield for the frame.
|
||||
thread.popStack();
|
||||
thread.status = Thread.STATUS_DONE;
|
||||
thread.setStatus(Thread.STATUS_YIELD_FRAME);
|
||||
return;
|
||||
}
|
||||
// Start showing run feedback in the editor.
|
||||
this.runtime.glowBlock(currentBlockId, true);
|
||||
|
||||
// Execute the current block
|
||||
execute(this, thread);
|
||||
|
||||
// If the block executed without yielding and without doing control flow,
|
||||
// move to done.
|
||||
if (thread.status === Thread.STATUS_RUNNING &&
|
||||
|
@ -106,7 +111,7 @@ Sequencer.prototype.stepToSubstack = function (thread, substackNum) {
|
|||
substackNum = 1;
|
||||
}
|
||||
var currentBlockId = thread.peekStack();
|
||||
var substackId = this.runtime.blocks.getSubstack(
|
||||
var substackId = this.runtime.targetForThread(thread).blocks.getSubstack(
|
||||
currentBlockId,
|
||||
substackNum
|
||||
);
|
||||
|
@ -147,23 +152,17 @@ Sequencer.prototype.stepToReporter = function (thread, blockId, inputName) {
|
|||
Sequencer.prototype.proceedThread = function (thread) {
|
||||
var currentBlockId = thread.peekStack();
|
||||
// Mark the status as done and proceed to the next block.
|
||||
this.runtime.glowBlock(currentBlockId, false);
|
||||
// If the block was yielding, move back to running state.
|
||||
thread.status = Thread.STATUS_RUNNING;
|
||||
// Pop from the stack - finished this level of execution.
|
||||
thread.popStack();
|
||||
// Push next connected block, if there is one.
|
||||
var nextBlockId = this.runtime.blocks.getNextBlock(currentBlockId);
|
||||
var nextBlockId = (this.runtime.targetForThread(thread).
|
||||
blocks.getNextBlock(currentBlockId));
|
||||
if (nextBlockId) {
|
||||
thread.pushStack(nextBlockId);
|
||||
}
|
||||
// Pop from the stack until we have a next block.
|
||||
while (thread.peekStack() === null && thread.stack.length > 0) {
|
||||
thread.popStack();
|
||||
}
|
||||
// If we still can't find a next block to run, mark the thread as done.
|
||||
if (thread.peekStack() === null) {
|
||||
thread.status = Thread.STATUS_DONE;
|
||||
// If we can't find a next block to run, mark the thread as done.
|
||||
if (!thread.peekStack()) {
|
||||
thread.setStatus(Thread.STATUS_DONE);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
20
src/engine/target.js
Normal file
20
src/engine/target.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
var Blocks = require('./blocks');
|
||||
|
||||
/**
|
||||
* @fileoverview
|
||||
* A Target is an abstract "code-running" object for the Scratch VM.
|
||||
* Examples include sprites/clones or potentially physical-world devices.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {?Blocks} blocks Blocks instance for the blocks owned by this target.
|
||||
* @constructor
|
||||
*/
|
||||
function Target (blocks) {
|
||||
if (!blocks) {
|
||||
blocks = new Blocks(this);
|
||||
}
|
||||
this.blocks = blocks;
|
||||
}
|
||||
|
||||
module.exports = Target;
|
|
@ -46,12 +46,18 @@ Thread.STATUS_RUNNING = 0;
|
|||
*/
|
||||
Thread.STATUS_YIELD = 1;
|
||||
|
||||
/**
|
||||
* Thread status for a single-frame yield.
|
||||
* @const
|
||||
*/
|
||||
Thread.STATUS_YIELD_FRAME = 2;
|
||||
|
||||
/**
|
||||
* Thread status for a finished/done thread.
|
||||
* Thread is in this state when there are no more blocks to execute.
|
||||
* @const
|
||||
*/
|
||||
Thread.STATUS_DONE = 2;
|
||||
Thread.STATUS_DONE = 3;
|
||||
|
||||
/**
|
||||
* Push stack and update stack frames appropriately.
|
||||
|
@ -118,10 +124,11 @@ Thread.prototype.pushReportedValue = function (value) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Yields the thread.
|
||||
* Set thread status.
|
||||
* @param {number} status Enum representing thread status.
|
||||
*/
|
||||
Thread.prototype.yield = function () {
|
||||
this.status = Thread.STATUS_YIELD;
|
||||
Thread.prototype.setStatus = function (status) {
|
||||
this.status = status;
|
||||
};
|
||||
|
||||
module.exports = Thread;
|
||||
|
|
45
src/index.js
45
src/index.js
|
@ -1,7 +1,7 @@
|
|||
var EventEmitter = require('events');
|
||||
var util = require('util');
|
||||
|
||||
var Blocks = require('./engine/blocks');
|
||||
var Sprite = require('./sprites/sprite');
|
||||
var Runtime = require('./engine/runtime');
|
||||
|
||||
/**
|
||||
|
@ -21,18 +21,23 @@ function VirtualMachine () {
|
|||
// Bind event emitter and runtime to VM instance
|
||||
// @todo Post message (Web Worker) polyfill
|
||||
EventEmitter.call(instance);
|
||||
instance.blocks = new Blocks();
|
||||
instance.runtime = new Runtime(instance.blocks);
|
||||
// @todo support multiple targets/sprites.
|
||||
// This is just a demo/example.
|
||||
var exampleSprite = new Sprite();
|
||||
exampleSprite.createClone();
|
||||
var exampleTargets = [exampleSprite.clones[0]];
|
||||
instance.exampleSprite = exampleSprite;
|
||||
instance.runtime = new Runtime(exampleTargets);
|
||||
|
||||
/**
|
||||
* Event listeners for scratch-blocks.
|
||||
*/
|
||||
instance.blockListener = (
|
||||
instance.blocks.generateBlockListener(false, instance.runtime)
|
||||
exampleSprite.blocks.generateBlockListener(false, instance.runtime)
|
||||
);
|
||||
|
||||
instance.flyoutBlockListener = (
|
||||
instance.blocks.generateBlockListener(true, instance.runtime)
|
||||
exampleSprite.blocks.generateBlockListener(true, instance.runtime)
|
||||
);
|
||||
|
||||
// Runtime emits are passed along as VM emits.
|
||||
|
@ -48,6 +53,9 @@ function VirtualMachine () {
|
|||
instance.runtime.on(Runtime.BLOCK_GLOW_OFF, function (id) {
|
||||
instance.emit(Runtime.BLOCK_GLOW_OFF, {id: id});
|
||||
});
|
||||
instance.runtime.on(Runtime.VISUAL_REPORT, function (id, value) {
|
||||
instance.emit(Runtime.VISUAL_REPORT, {id: id, value: value});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -81,17 +89,28 @@ VirtualMachine.prototype.stopAll = function () {
|
|||
*/
|
||||
VirtualMachine.prototype.getPlaygroundData = function () {
|
||||
this.emit('playgroundData', {
|
||||
blocks: this.blocks,
|
||||
blocks: this.exampleSprite.blocks,
|
||||
threads: this.runtime.threads
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle an animation frame.
|
||||
*/
|
||||
VirtualMachine.prototype.animationFrame = function () {
|
||||
this.runtime.animationFrame();
|
||||
};
|
||||
|
||||
/*
|
||||
* Worker handlers: for all public methods available above,
|
||||
* we must also provide a message handler in case the VM is run
|
||||
* from a worker environment.
|
||||
*/
|
||||
if (ENV_WORKER) {
|
||||
self.importScripts(
|
||||
'./node_modules/scratch-render/render-worker.js'
|
||||
);
|
||||
self.renderer = new self.RenderWebGLWorker();
|
||||
self.vmInstance = new VirtualMachine();
|
||||
self.onmessage = function (e) {
|
||||
var messageData = e.data;
|
||||
|
@ -114,12 +133,19 @@ if (ENV_WORKER) {
|
|||
case 'getPlaygroundData':
|
||||
self.postMessage({
|
||||
method: 'playgroundData',
|
||||
blocks: self.vmInstance.blocks,
|
||||
blocks: self.vmInstance.exampleSprite.blocks,
|
||||
threads: self.vmInstance.runtime.threads
|
||||
});
|
||||
break;
|
||||
case 'animationFrame':
|
||||
self.vmInstance.animationFrame();
|
||||
break;
|
||||
default:
|
||||
throw 'Unknown method' + messageData.method;
|
||||
if (e.data.id == 'RendererConnected') {
|
||||
//initRenderWorker();
|
||||
}
|
||||
self.renderer.onmessage(e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
// Bind runtime's emitted events to postmessages.
|
||||
|
@ -135,6 +161,9 @@ if (ENV_WORKER) {
|
|||
self.vmInstance.runtime.on(Runtime.BLOCK_GLOW_OFF, function (id) {
|
||||
self.postMessage({method: Runtime.BLOCK_GLOW_OFF, id: id});
|
||||
});
|
||||
self.vmInstance.runtime.on(Runtime.VISUAL_REPORT, function (id, value) {
|
||||
self.postMessage({method: Runtime.VISUAL_REPORT, id: id, value: value});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
187
src/sprites/clone.js
Normal file
187
src/sprites/clone.js
Normal file
|
@ -0,0 +1,187 @@
|
|||
var util = require('util');
|
||||
var MathUtil = require('../util/math-util');
|
||||
var Target = require('../engine/target');
|
||||
|
||||
/**
|
||||
* Clone (instance) of a sprite.
|
||||
* @param {!Blocks} spriteBlocks Reference to the sprite's blocks.
|
||||
* @constructor
|
||||
*/
|
||||
function Clone(spriteBlocks) {
|
||||
Target.call(this, spriteBlocks);
|
||||
/**
|
||||
* Reference to the global renderer for this VM, if one exists.
|
||||
* @type {?RenderWebGLWorker}
|
||||
*/
|
||||
this.renderer = null;
|
||||
// If this is not true, there is no renderer (e.g., running in a test env).
|
||||
if (typeof self !== 'undefined' && self.renderer) {
|
||||
// Pull from `self.renderer`.
|
||||
this.renderer = self.renderer;
|
||||
}
|
||||
/**
|
||||
* ID of the drawable for this clone returned by the renderer, if rendered.
|
||||
* @type {?Number}
|
||||
*/
|
||||
this.drawableID = null;
|
||||
|
||||
this.initDrawable();
|
||||
}
|
||||
util.inherits(Clone, Target);
|
||||
|
||||
/**
|
||||
* Create a clone's drawable with the this.renderer.
|
||||
*/
|
||||
Clone.prototype.initDrawable = function () {
|
||||
if (this.renderer) {
|
||||
var createPromise = this.renderer.createDrawable();
|
||||
var instance = this;
|
||||
createPromise.then(function (id) {
|
||||
instance.drawableID = id;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Clone-level properties.
|
||||
/**
|
||||
* Scratch X coordinate. Currently should range from -240 to 240.
|
||||
* @type {!number}
|
||||
*/
|
||||
Clone.prototype.x = 0;
|
||||
|
||||
/**
|
||||
* Scratch Y coordinate. Currently should range from -180 to 180.
|
||||
* @type {!number}
|
||||
*/
|
||||
Clone.prototype.y = 0;
|
||||
|
||||
/**
|
||||
* Scratch direction. Currently should range from -179 to 180.
|
||||
* @type {!number}
|
||||
*/
|
||||
Clone.prototype.direction = 90;
|
||||
|
||||
/**
|
||||
* Whether the clone is currently visible.
|
||||
* @type {!boolean}
|
||||
*/
|
||||
Clone.prototype.visible = true;
|
||||
|
||||
/**
|
||||
* Size of clone as a percent of costume size. Ranges from 5% to 535%.
|
||||
* @type {!number}
|
||||
*/
|
||||
Clone.prototype.size = 100;
|
||||
|
||||
/**
|
||||
* Map of current graphic effect values.
|
||||
* @type {!Object.<string, number>}
|
||||
*/
|
||||
Clone.prototype.effects = {
|
||||
'color': 0,
|
||||
'fisheye': 0,
|
||||
'whirl': 0,
|
||||
'pixelate': 0,
|
||||
'mosaic': 0,
|
||||
'brightness': 0,
|
||||
'ghost': 0
|
||||
};
|
||||
// End clone-level properties.
|
||||
|
||||
/**
|
||||
* Set the X and Y coordinates of a clone.
|
||||
* @param {!number} x New X coordinate of clone, in Scratch coordinates.
|
||||
* @param {!number} y New Y coordinate of clone, in Scratch coordinates.
|
||||
*/
|
||||
Clone.prototype.setXY = function (x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
if (this.renderer) {
|
||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||
position: [this.x, this.y]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the direction of a clone.
|
||||
* @param {!number} direction New direction of clone.
|
||||
*/
|
||||
Clone.prototype.setDirection = function (direction) {
|
||||
// Keep direction between -179 and +180.
|
||||
this.direction = MathUtil.wrapClamp(direction, -179, 180);
|
||||
if (this.renderer) {
|
||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||
direction: this.direction
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a say bubble on this clone.
|
||||
* @param {?string} type Type of say bubble: "say", "think", or null.
|
||||
* @param {?string} message Message to put in say bubble.
|
||||
*/
|
||||
Clone.prototype.setSay = function (type, message) {
|
||||
// @todo: Render to stage.
|
||||
if (!type || !message) {
|
||||
console.log('Clearing say bubble');
|
||||
return;
|
||||
}
|
||||
console.log('Setting say bubble:', type, message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set visibility of the clone; i.e., whether it's shown or hidden.
|
||||
* @param {!boolean} visible True if the sprite should be shown.
|
||||
*/
|
||||
Clone.prototype.setVisible = function (visible) {
|
||||
this.visible = visible;
|
||||
if (this.renderer) {
|
||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||
visible: this.visible
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set size of the clone, as a percentage of the costume size.
|
||||
* @param {!number} size Size of clone, from 5 to 535.
|
||||
*/
|
||||
Clone.prototype.setSize = function (size) {
|
||||
// Keep size between 5% and 535%.
|
||||
this.size = MathUtil.clamp(size, 5, 535);
|
||||
if (this.renderer) {
|
||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||
scale: [this.size, this.size]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a particular graphic effect on this clone.
|
||||
* @param {!string} effectName Name of effect (see `Clone.prototype.effects`).
|
||||
* @param {!number} value Numerical magnitude of effect.
|
||||
*/
|
||||
Clone.prototype.setEffect = function (effectName, value) {
|
||||
this.effects[effectName] = value;
|
||||
if (this.renderer) {
|
||||
var props = {};
|
||||
props[effectName] = this.effects[effectName];
|
||||
this.renderer.updateDrawableProperties(this.drawableID, props);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear all graphic effects on this clone.
|
||||
*/
|
||||
Clone.prototype.clearEffects = function () {
|
||||
for (var effectName in this.effects) {
|
||||
this.effects[effectName] = 0;
|
||||
}
|
||||
if (this.renderer) {
|
||||
this.renderer.updateDrawableProperties(this.drawableID, this.effects);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Clone;
|
29
src/sprites/sprite.js
Normal file
29
src/sprites/sprite.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
var Clone = require('./clone');
|
||||
var Blocks = require('../engine/blocks');
|
||||
|
||||
/**
|
||||
* Sprite to be used on the Scratch stage.
|
||||
* All clones of a sprite have shared blocks, shared costumes, shared variables.
|
||||
* @param {?Blocks} blocks Shared blocks object for all clones of sprite.
|
||||
* @constructor
|
||||
*/
|
||||
function Sprite (blocks) {
|
||||
if (!blocks) {
|
||||
// Shared set of blocks for all clones.
|
||||
blocks = new Blocks();
|
||||
}
|
||||
this.blocks = blocks;
|
||||
this.clones = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a clone of this sprite.
|
||||
* @returns {!Clone} Newly created clone.
|
||||
*/
|
||||
Sprite.prototype.createClone = function () {
|
||||
var newClone = new Clone(this.blocks);
|
||||
this.clones.push(newClone);
|
||||
return newClone;
|
||||
};
|
||||
|
||||
module.exports = Sprite;
|
48
src/util/math-util.js
Normal file
48
src/util/math-util.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
function MathUtil () {}
|
||||
|
||||
/**
|
||||
* Convert a value from degrees to radians.
|
||||
* @param {!number} deg Value in degrees.
|
||||
* @return {!number} Equivalent value in radians.
|
||||
*/
|
||||
MathUtil.degToRad = function (deg) {
|
||||
return (Math.PI * (90 - deg)) / 180;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a value from radians to degrees.
|
||||
* @param {!number} rad Value in radians.
|
||||
* @return {!number} Equivalent value in degrees.
|
||||
*/
|
||||
MathUtil.radToDeg = function (rad) {
|
||||
return rad * 180 / Math.PI;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clamp a number between two limits.
|
||||
* If n < min, return min. If n > max, return max. Else, return n.
|
||||
* @param {!number} n Number to clamp.
|
||||
* @param {!number} min Minimum limit.
|
||||
* @param {!number} max Maximum limit.
|
||||
* @return {!number} Value of n clamped to min and max.
|
||||
*/
|
||||
MathUtil.clamp = function (n, min, max) {
|
||||
return Math.min(Math.max(n, min), max);
|
||||
};
|
||||
|
||||
/**
|
||||
* Keep a number between two limits, wrapping "extra" into the range.
|
||||
* e.g., wrapClamp(7, 1, 5) == 2
|
||||
* wrapClamp(0, 1, 5) == 5
|
||||
* wrapClamp(-11, -10, 6) == 6, etc.
|
||||
* @param {!number} n Number to wrap.
|
||||
* @param {!number} min Minimum limit.
|
||||
* @param {!number} max Maximum limit.
|
||||
* @return {!number} Value of n wrapped between min and max.
|
||||
*/
|
||||
MathUtil.wrapClamp = function (n, min, max) {
|
||||
var range = (max - min) + 1;
|
||||
return n - Math.floor((n - min) / range) * range;
|
||||
};
|
||||
|
||||
module.exports = MathUtil;
|
|
@ -63,6 +63,10 @@ VirtualMachine.prototype.stopAll = function () {
|
|||
this.vmWorker.postMessage({method: 'stopAll'});
|
||||
};
|
||||
|
||||
VirtualMachine.prototype.animationFrame = function () {
|
||||
this.vmWorker.postMessage({method: 'animationFrame'});
|
||||
};
|
||||
|
||||
/**
|
||||
* Export and bind to `window`
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue