game finished
This commit is contained in:
commit
60703933a3
21
.babelrc
Normal file
21
.babelrc
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"presets":["react","es2015"],
|
||||
"env":{
|
||||
"development":{
|
||||
"plugins":[
|
||||
[
|
||||
"react-transform",
|
||||
{
|
||||
"transforms":[
|
||||
{
|
||||
"transform":"react-transform-hmr",
|
||||
"imports":["react"],
|
||||
"locals":["module"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
16
.eslintrc.js
Normal file
16
.eslintrc.js
Normal file
@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
"extends": "airbnb",
|
||||
"installedESLint": true,
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/jsx-filename-extension": [2, { extensions: ['.js','.jsx'] }],
|
||||
"func-names": [0],
|
||||
"new-cap": [2, { newIsCap: true ,capIsNew: true, capIsNewExceptions: ['List', 'Map']}],
|
||||
"linebreak-style": [0]
|
||||
},
|
||||
"env": {
|
||||
"browser": true
|
||||
}
|
||||
};
|
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
### Node template
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
.DS_Store
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
7
.idea/encodings.xml
generated
Normal file
7
.idea/encodings.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/unit/const.js" charset="UTF-8" />
|
||||
<file url="PROJECT" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
7
.idea/jsLibraryMappings.xml
generated
Normal file
7
.idea/jsLibraryMappings.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<file url="file://$PROJECT_DIR$" libraries="{react-tetris/node_modules}" />
|
||||
<includedPredefinedLibrary name="ECMAScript 6" />
|
||||
</component>
|
||||
</project>
|
16
.idea/misc.xml
generated
Normal file
16
.idea/misc.xml
generated
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="JSX" />
|
||||
</component>
|
||||
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
|
||||
<OptionsSetting value="true" id="Add" />
|
||||
<OptionsSetting value="true" id="Remove" />
|
||||
<OptionsSetting value="true" id="Checkout" />
|
||||
<OptionsSetting value="true" id="Update" />
|
||||
<OptionsSetting value="true" id="Status" />
|
||||
<OptionsSetting value="true" id="Edit" />
|
||||
<ConfirmationsSetting value="0" id="Add" />
|
||||
<ConfirmationsSetting value="0" id="Remove" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/react-tetris.iml" filepath="$PROJECT_DIR$/.idea/react-tetris.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
12
.idea/react-tetris.iml
generated
Normal file
12
.idea/react-tetris.iml
generated
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
4
.idea/watcherTasks.xml
generated
Normal file
4
.idea/watcherTasks.xml
generated
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectTasksOptions" suppressed-tasks="Babel" />
|
||||
</project>
|
942
.idea/workspace.xml
generated
Normal file
942
.idea/workspace.xml
generated
Normal file
@ -0,0 +1,942 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="8d44ef6b-457e-4f00-989d-971e2f1313d6" name="Default" comment="">
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/build/music.mp3" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/resource/image/icon.png" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/resource/image/share.png" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/resource/music/music.mp3" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/resource/music/music.wav" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/.babelrc" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/.eslintrc.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/.gitignore" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/.idea/jsLibraryMappings.xml" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/.idea/vcs.xml" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/README.md" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/build/index.html" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/build/loader.css" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/i18n.json" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/server/index.html" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/server/index.tmpl.html" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/actions/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/actions/keyboard.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/decorate/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/decorate/index.less" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/guide/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/guide/index.less" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/keyboard/button/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/keyboard/button/index.less" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/keyboard/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/keyboard/index.less" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/logo/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/logo/index.less" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/matrix/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/matrix/index.less" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/music/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/music/index.less" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/next/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/next/index.less" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/number/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/number/index.less" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/pause/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/pause/index.less" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/components/point/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/containers/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/containers/index.less" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/containers/loader.less" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/control/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/control/states.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/control/todo/down.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/control/todo/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/control/todo/left.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/control/todo/p.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/control/todo/r.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/control/todo/right.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/control/todo/rotate.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/control/todo/s.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/control/todo/space.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/clearLines/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/cur/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/drop/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/focus/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/keyboard/down.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/keyboard/drop.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/keyboard/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/keyboard/left.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/keyboard/music.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/keyboard/pause.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/keyboard/reset.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/keyboard/right.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/keyboard/rotate.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/lock/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/matrix/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/max/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/music/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/next/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/pause/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/points/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/reset/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/speedRun/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/speedStart/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/reducers/startLines/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/resource/css/loader.css" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/store/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/unit/block.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/unit/const.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/unit/event.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/unit/index.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/unit/music.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/unit/reducerType.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/webpack.config.js" />
|
||||
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/webpack.production.config.js" />
|
||||
</list>
|
||||
<ignored path="react-tetris.iws" />
|
||||
<ignored path=".idea/workspace.xml" />
|
||||
<ignored path="$PROJECT_DIR$/.tmp/" />
|
||||
<ignored path="$PROJECT_DIR$/temp/" />
|
||||
<ignored path="$PROJECT_DIR$/tmp/" />
|
||||
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
|
||||
<option name="TRACKING_ENABLED" value="true" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="CreatePatchCommitExecutor">
|
||||
<option name="PATCH_PATH" value="" />
|
||||
</component>
|
||||
<component name="ExecutionTargetManager" SELECTED_TARGET="default_target" />
|
||||
<component name="FavoritesManager">
|
||||
<favorites_list name="react-tetris" />
|
||||
</component>
|
||||
<component name="FileEditorManager">
|
||||
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
|
||||
<file leaf-file-name="index.js" pinned="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/src/control/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="24">
|
||||
<caret line="1" column="23" selection-start-line="1" selection-start-column="23" selection-end-line="1" selection-end-column="23" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="index.js" pinned="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/src/control/todo/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="216">
|
||||
<caret line="9" column="16" selection-start-line="9" selection-start-column="16" selection-end-line="9" selection-end-column="16" />
|
||||
<folding>
|
||||
<element signature="e#0#26#0" expanded="true" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="s.js" pinned="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/src/control/todo/s.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="264">
|
||||
<caret line="16" column="59" selection-start-line="16" selection-start-column="59" selection-end-line="16" selection-end-column="59" />
|
||||
<folding>
|
||||
<element signature="e#0#37#0" expanded="true" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="webpack.production.config.js" pinned="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/webpack.production.config.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="260">
|
||||
<caret line="50" column="26" selection-start-line="50" selection-start-column="26" selection-end-line="50" selection-end-column="26" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="i18n.json" pinned="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/i18n.json">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="240">
|
||||
<caret line="10" column="22" selection-start-line="10" selection-start-column="22" selection-end-line="10" selection-end-column="22" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="package.json" pinned="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/package.json">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="144">
|
||||
<caret line="6" column="4" selection-start-line="6" selection-start-column="4" selection-end-line="8" selection-end-column="2" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="README.md" pinned="false" current-in-tab="true">
|
||||
<entry file="file://$PROJECT_DIR$/README.md">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="92">
|
||||
<caret line="16" column="0" selection-start-line="16" selection-start-column="0" selection-end-line="23" selection-end-column="3" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="index.js" pinned="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/src/components/keyboard/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="158">
|
||||
<caret line="130" column="17" selection-start-line="130" selection-start-column="17" selection-end-line="130" selection-end-column="17" />
|
||||
<folding>
|
||||
<element signature="e#0#26#0" expanded="true" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="r.js" pinned="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/src/control/todo/r.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="192">
|
||||
<caret line="8" column="3" selection-start-line="8" selection-start-column="3" selection-end-line="8" selection-end-column="3" />
|
||||
<folding>
|
||||
<element signature="e#0#37#0" expanded="true" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
<file leaf-file-name="rotate.js" pinned="false" current-in-tab="false">
|
||||
<entry file="file://$PROJECT_DIR$/src/control/todo/rotate.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="-120">
|
||||
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||
<folding>
|
||||
<element signature="e#0#35#0" expanded="true" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</file>
|
||||
</leaf>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="IdeDocumentHistory">
|
||||
<option name="CHANGED_PATHS">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/server/index.html" />
|
||||
<option value="$PROJECT_DIR$/server/index.tmpl.html" />
|
||||
<option value="$PROJECT_DIR$/webpack.config.js" />
|
||||
<option value="$PROJECT_DIR$/src/components/keyboard/button/index.less" />
|
||||
<option value="$PROJECT_DIR$/src/components/next/index.js" />
|
||||
<option value="$PROJECT_DIR$/src/components/point/index.js" />
|
||||
<option value="$PROJECT_DIR$/src/components/decorate/index.js" />
|
||||
<option value="$PROJECT_DIR$/src/components/logo/index.js" />
|
||||
<option value="$PROJECT_DIR$/src/components/guide/index.js" />
|
||||
<option value="$PROJECT_DIR$/i18n.json" />
|
||||
<option value="$PROJECT_DIR$/src/containers/index.js" />
|
||||
<option value="$PROJECT_DIR$/build/index.html" />
|
||||
<option value="$PROJECT_DIR$/src/index.js" />
|
||||
<option value="$PROJECT_DIR$/i18n.js" />
|
||||
<option value="$PROJECT_DIR$/src/unit/const.js" />
|
||||
<option value="$PROJECT_DIR$/src/unit/index.js" />
|
||||
<option value="$PROJECT_DIR$/src/control/index.js" />
|
||||
<option value="$PROJECT_DIR$/src/control/todo/index.js" />
|
||||
<option value="$PROJECT_DIR$/src/control/todo/s.js" />
|
||||
<option value="$PROJECT_DIR$/src/components/keyboard/index.js" />
|
||||
<option value="$PROJECT_DIR$/package.json" />
|
||||
<option value="$PROJECT_DIR$/webpack.production.config.js" />
|
||||
<option value="$PROJECT_DIR$/README.md" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
|
||||
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
|
||||
<component name="JsGulpfileManager">
|
||||
<detection-done>true</detection-done>
|
||||
<sorting>DEFINITION_ORDER</sorting>
|
||||
</component>
|
||||
<component name="NodeModulesDirectoryManager">
|
||||
<handled-path value="$PROJECT_DIR$/node_modules" />
|
||||
</component>
|
||||
<component name="ProjectFrameBounds">
|
||||
<option name="width" value="1440" />
|
||||
<option name="height" value="900" />
|
||||
</component>
|
||||
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
|
||||
<OptionsSetting value="true" id="Add" />
|
||||
<OptionsSetting value="true" id="Remove" />
|
||||
<OptionsSetting value="true" id="Checkout" />
|
||||
<OptionsSetting value="true" id="Update" />
|
||||
<OptionsSetting value="true" id="Status" />
|
||||
<OptionsSetting value="true" id="Edit" />
|
||||
<ConfirmationsSetting value="0" id="Add" />
|
||||
<ConfirmationsSetting value="0" id="Remove" />
|
||||
</component>
|
||||
<component name="ProjectView">
|
||||
<navigator currentView="ProjectPane" proportions="" version="1">
|
||||
<flattenPackages />
|
||||
<showMembers />
|
||||
<showModules />
|
||||
<showLibraryContents />
|
||||
<hideEmptyPackages />
|
||||
<abbreviatePackageNames />
|
||||
<autoscrollToSource />
|
||||
<autoscrollFromSource />
|
||||
<sortByType />
|
||||
<manualOrder />
|
||||
<foldersAlwaysOnTop value="true" />
|
||||
</navigator>
|
||||
<panes>
|
||||
<pane id="ProjectPane">
|
||||
<subPane>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="src" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="src" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="unit" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="src" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="control" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="src" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="control" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="todo" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="src" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="containers" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
<PATH>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="react-tetris" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
<PATH_ELEMENT>
|
||||
<option name="myItemId" value="build" />
|
||||
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||
</PATH_ELEMENT>
|
||||
</PATH>
|
||||
</subPane>
|
||||
</pane>
|
||||
<pane id="Scope" />
|
||||
<pane id="Scratches" />
|
||||
</panes>
|
||||
</component>
|
||||
<component name="PropertiesComponent">
|
||||
<property name="settings.editor.selected.configurable" value="Settings.JavaScript" />
|
||||
<property name="JavaScriptPreferStrict" value="false" />
|
||||
<property name="JavaScriptWeakerCompletionTypeGuess" value="true" />
|
||||
<property name="settings.editor.splitter.proportion" value="0.2" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||
<property name="HbShouldOpenHtmlAsHb" value="" />
|
||||
<property name="js-jscs-nodeInterpreter" value="/usr/local/bin/node" />
|
||||
<property name="nodejs_interpreter_path" value="/usr/local/bin/node" />
|
||||
<property name="FullScreen" value="true" />
|
||||
</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager">
|
||||
<configuration default="true" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application">
|
||||
<method />
|
||||
</configuration>
|
||||
<configuration default="true" type="DartTestRunConfigurationType" factoryName="Dart Test">
|
||||
<method />
|
||||
</configuration>
|
||||
<configuration default="true" type="JavaScriptTestRunnerKarma" factoryName="Karma">
|
||||
<config-file value="" />
|
||||
<node-interpreter value="project" />
|
||||
<envs />
|
||||
<method />
|
||||
</configuration>
|
||||
<configuration default="true" type="JavascriptDebugType" factoryName="JavaScript Debug">
|
||||
<method />
|
||||
</configuration>
|
||||
<configuration default="true" type="NodeJSConfigurationType" factoryName="Node.js" path-to-node="project" working-dir="">
|
||||
<method />
|
||||
</configuration>
|
||||
<configuration default="true" type="cucumber.js" factoryName="Cucumber.js">
|
||||
<option name="cucumberJsArguments" value="" />
|
||||
<option name="executablePath" />
|
||||
<option name="filePath" />
|
||||
<method />
|
||||
</configuration>
|
||||
<configuration default="true" type="js.build_tools.gulp" factoryName="Gulp.js">
|
||||
<node-interpreter>project</node-interpreter>
|
||||
<node-options />
|
||||
<gulpfile />
|
||||
<tasks />
|
||||
<arguments />
|
||||
<envs />
|
||||
<method />
|
||||
</configuration>
|
||||
<configuration default="true" type="js.build_tools.npm" factoryName="npm">
|
||||
<command value="run-script" />
|
||||
<scripts />
|
||||
<node-interpreter value="project" />
|
||||
<envs />
|
||||
<method />
|
||||
</configuration>
|
||||
<configuration default="true" type="mocha-javascript-test-runner" factoryName="Mocha">
|
||||
<node-interpreter>project</node-interpreter>
|
||||
<node-options />
|
||||
<working-directory />
|
||||
<pass-parent-env>true</pass-parent-env>
|
||||
<envs />
|
||||
<ui />
|
||||
<extra-mocha-options />
|
||||
<test-kind>DIRECTORY</test-kind>
|
||||
<test-directory />
|
||||
<recursive>false</recursive>
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
<component name="ShelveChangesManager" show_recycled="false">
|
||||
<option name="remove_strategy" value="false" />
|
||||
</component>
|
||||
<component name="SvnConfiguration">
|
||||
<configuration>$USER_HOME$/.subversion</configuration>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="8d44ef6b-457e-4f00-989d-971e2f1313d6" name="Default" comment="" />
|
||||
<created>1482237015751</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1482237015751</updated>
|
||||
<workItem from="1482237017079" duration="7324000" />
|
||||
<workItem from="1482244970529" duration="3842000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TimeTrackingManager">
|
||||
<option name="totallyTimeSpent" value="11166000" />
|
||||
</component>
|
||||
<component name="ToolWindowManager">
|
||||
<frame x="0" y="0" width="1440" height="900" extended-state="0" />
|
||||
<editor active="false" />
|
||||
<layout>
|
||||
<window_info id="Project" active="true" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.26497534" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
|
||||
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" />
|
||||
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.329602" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.46766168" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
|
||||
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
|
||||
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
|
||||
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||
</layout>
|
||||
</component>
|
||||
<component name="Vcs.Log.UiProperties">
|
||||
<option name="RECENTLY_FILTERED_USER_GROUPS">
|
||||
<collection />
|
||||
</option>
|
||||
<option name="RECENTLY_FILTERED_BRANCH_GROUPS">
|
||||
<collection />
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsContentAnnotationSettings">
|
||||
<option name="myLimit" value="2678400000" />
|
||||
</component>
|
||||
<component name="XDebuggerManager">
|
||||
<breakpoint-manager />
|
||||
<watches-manager />
|
||||
</component>
|
||||
<component name="editorHistoryManager">
|
||||
<entry file="file://$PROJECT_DIR$/.babelrc">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="117">
|
||||
<caret line="6" column="28" selection-start-line="6" selection-start-column="28" selection-end-line="6" selection-end-column="28" />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/build/css.css.map">
|
||||
<provider editor-type-id="sourcemapFileViewerProvider">
|
||||
<state />
|
||||
</provider>
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="0">
|
||||
<caret line="0" column="17" selection-start-line="0" selection-start-column="17" selection-end-line="0" selection-end-column="17" />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/server/index.html">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="120">
|
||||
<caret line="5" column="47" selection-start-line="5" selection-start-column="41" selection-end-line="5" selection-end-column="47" />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/server/index.tmpl.html">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="-178">
|
||||
<caret line="3" column="14" selection-start-line="3" selection-start-column="14" selection-end-line="10" selection-end-column="49" />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/.eslintrc.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="192">
|
||||
<caret line="8" column="26" selection-start-line="8" selection-start-column="26" selection-end-line="8" selection-end-column="26" />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/containers/index.less">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="96">
|
||||
<caret line="4" column="10" selection-start-line="4" selection-start-column="10" selection-end-line="4" selection-end-column="10" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/components/keyboard/button/index.less">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="141">
|
||||
<caret line="21" column="16" selection-start-line="21" selection-start-column="16" selection-end-line="21" selection-end-column="16" />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/components/keyboard/button/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="48">
|
||||
<caret line="2" column="24" selection-start-line="2" selection-start-column="24" selection-end-line="2" selection-end-column="24" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/components/pause/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="0">
|
||||
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||
<folding>
|
||||
<element signature="e#0#26#0" expanded="false" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/components/number/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="-2053">
|
||||
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||
<folding>
|
||||
<element signature="e#0#26#0" expanded="false" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/components/next/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="48">
|
||||
<caret line="2" column="19" selection-start-line="2" selection-start-column="19" selection-end-line="2" selection-end-column="19" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/components/matrix/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="120">
|
||||
<caret line="5" column="15" selection-start-line="5" selection-start-column="0" selection-end-line="6" selection-end-column="0" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/components/decorate/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="72">
|
||||
<caret line="3" column="13" selection-start-line="3" selection-start-column="10" selection-end-line="3" selection-end-column="13" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/components/guide/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="59">
|
||||
<caret line="13" column="17" selection-start-line="13" selection-start-column="17" selection-end-line="13" selection-end-column="17" />
|
||||
<folding>
|
||||
<element signature="e#0#26#0" expanded="false" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/components/logo/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="96">
|
||||
<caret line="4" column="0" selection-start-line="4" selection-start-column="0" selection-end-line="4" selection-end-column="0" />
|
||||
<folding>
|
||||
<element signature="e#0#26#0" expanded="true" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/components/point/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="264">
|
||||
<caret line="11" column="18" selection-start-line="11" selection-start-column="18" selection-end-line="11" selection-end-column="18" />
|
||||
<folding>
|
||||
<element signature="e#0#26#0" expanded="false" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/unit/reducerType.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="0">
|
||||
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/containers/loader.less">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="0">
|
||||
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/webpack.config.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="116">
|
||||
<caret line="18" column="26" selection-start-line="18" selection-start-column="26" selection-end-line="18" selection-end-column="26" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/build/index.html">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="174">
|
||||
<caret line="18" column="43" selection-start-line="18" selection-start-column="43" selection-end-line="18" selection-end-column="43" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/reducers/pause/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="0">
|
||||
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||
<folding>
|
||||
<element signature="e#0#54#0" expanded="false" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/reducers/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="24">
|
||||
<caret line="1" column="23" selection-start-line="1" selection-start-column="23" selection-end-line="1" selection-end-column="23" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/i18n.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="2064">
|
||||
<caret line="86" column="16" selection-start-line="86" selection-start-column="16" selection-end-line="86" selection-end-column="16" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="264">
|
||||
<caret line="11" column="7" selection-start-line="11" selection-start-column="7" selection-end-line="11" selection-end-column="7" />
|
||||
<folding>
|
||||
<element signature="e#0#26#0" expanded="true" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/containers/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="912">
|
||||
<caret line="53" column="2" selection-start-line="53" selection-start-column="2" selection-end-line="53" selection-end-column="2" />
|
||||
<folding>
|
||||
<element signature="e#0#26#0" expanded="true" />
|
||||
<element signature="e#1081#1220#0" expanded="false" />
|
||||
<element signature="e#1242#1680#0" expanded="false" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/unit/const.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="24">
|
||||
<caret line="1" column="34" selection-start-line="1" selection-start-column="34" selection-end-line="1" selection-end-column="34" />
|
||||
<folding>
|
||||
<element signature="e#90#369#0" expanded="false" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/unit/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="24">
|
||||
<caret line="1" column="0" selection-start-line="1" selection-start-column="0" selection-end-line="1" selection-end-column="0" />
|
||||
<folding>
|
||||
<element signature="e#1712#1780#0" expanded="false" />
|
||||
<element signature="e#1807#2155#0" expanded="false" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/control/todo/down.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="0">
|
||||
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||
<folding>
|
||||
<element signature="e#0#35#0" expanded="false" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/control/todo/left.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="0">
|
||||
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||
<folding>
|
||||
<element signature="e#0#35#0" expanded="false" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/control/todo/p.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="0">
|
||||
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||
<folding>
|
||||
<element signature="e#0#37#0" expanded="false" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/control/todo/right.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="0">
|
||||
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||
<folding>
|
||||
<element signature="e#0#35#0" expanded="false" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/store/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="96">
|
||||
<caret line="4" column="0" selection-start-line="4" selection-start-column="0" selection-end-line="4" selection-end-column="0" />
|
||||
<folding>
|
||||
<element signature="e#0#36#0" expanded="false" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/unit/event.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="139">
|
||||
<caret line="47" column="15" selection-start-line="47" selection-start-column="15" selection-end-line="47" selection-end-column="15" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/control/todo/r.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="192">
|
||||
<caret line="8" column="3" selection-start-line="8" selection-start-column="3" selection-end-line="8" selection-end-column="3" />
|
||||
<folding>
|
||||
<element signature="e#0#37#0" expanded="true" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/components/keyboard/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="158">
|
||||
<caret line="130" column="17" selection-start-line="130" selection-start-column="17" selection-end-line="130" selection-end-column="17" />
|
||||
<folding>
|
||||
<element signature="e#0#26#0" expanded="true" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/control/todo/s.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="264">
|
||||
<caret line="16" column="59" selection-start-line="16" selection-start-column="59" selection-end-line="16" selection-end-column="59" />
|
||||
<folding>
|
||||
<element signature="e#0#37#0" expanded="true" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/control/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="24">
|
||||
<caret line="1" column="23" selection-start-line="1" selection-start-column="23" selection-end-line="1" selection-end-column="23" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/control/todo/index.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="216">
|
||||
<caret line="9" column="16" selection-start-line="9" selection-start-column="16" selection-end-line="9" selection-end-column="16" />
|
||||
<folding>
|
||||
<element signature="e#0#26#0" expanded="true" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/src/control/todo/rotate.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="-120">
|
||||
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||
<folding>
|
||||
<element signature="e#0#35#0" expanded="true" />
|
||||
</folding>
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/webpack.production.config.js">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="260">
|
||||
<caret line="50" column="26" selection-start-line="50" selection-start-column="26" selection-end-line="50" selection-end-column="26" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/i18n.json">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="240">
|
||||
<caret line="10" column="22" selection-start-line="10" selection-start-column="22" selection-end-line="10" selection-end-column="22" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/package.json">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="144">
|
||||
<caret line="6" column="4" selection-start-line="6" selection-start-column="4" selection-end-line="8" selection-end-column="2" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
<entry file="file://$PROJECT_DIR$/README.md">
|
||||
<provider selected="true" editor-type-id="text-editor">
|
||||
<state relative-caret-position="92">
|
||||
<caret line="16" column="0" selection-start-line="16" selection-start-column="0" selection-end-line="23" selection-end-column="3" />
|
||||
<folding />
|
||||
</state>
|
||||
</provider>
|
||||
</entry>
|
||||
</component>
|
||||
</project>
|
24
README.md
Normal file
24
README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# 用React+Redux+Immutable做 《俄罗斯方块》。
|
||||
# Use Tetact, Redux, Immutable to coding "Tetris".
|
||||
--------------------------------------------
|
||||
|
||||
### 安装 | Install
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### 开发 | Development
|
||||
```
|
||||
npm start
|
||||
```
|
||||
open [http://0.0.0.0:8080/](http://0.0.0.0:8080/)
|
||||
|
||||
### 打包 | Build
|
||||
## Mac:
|
||||
```
|
||||
npm run MacBuild
|
||||
```
|
||||
## Windows:
|
||||
```
|
||||
npm run WindowsBuild
|
||||
```
|
28
build/app-1.0.0.js
Normal file
28
build/app-1.0.0.js
Normal file
File diff suppressed because one or more lines are too long
1
build/app-1.0.0.js.map
Normal file
1
build/app-1.0.0.js.map
Normal file
File diff suppressed because one or more lines are too long
2
build/css-1.0.0.css
Normal file
2
build/css-1.0.0.css
Normal file
File diff suppressed because one or more lines are too long
1
build/css-1.0.0.css.map
Normal file
1
build/css-1.0.0.css.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"sources":[],"names":[],"mappings":"","file":"css-1.0.0.css","sourceRoot":""}
|
20
build/index.html
Normal file
20
build/index.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta name="description" content="使用React、Redux、Immutable制作的俄罗斯方块" />
|
||||
<meta name="keywords" content="俄罗斯方块,Tetris,React,Redux,Immuatble,JavaScript">
|
||||
<meta name="format-detection" content="telephone=no"/>
|
||||
<script>;(function(){var w=parseInt(window.screen.width),s=w/640,u=navigator.userAgent.toLowerCase(),m='<meta name="viewport" content="width=640,';if(/android (\d+\.\d+)/.test(u)){if(parseFloat(RegExp.$1)>2.3)m+='minimum-scale='+s+',maximum-scale='+s+',';}else{m+='user-scalable=no,';}m+='target-densitydpi=device-dpi">';document.write(m);}());</script>
|
||||
<meta charset="UTF-8">
|
||||
<title>俄罗斯方块</title>
|
||||
<link href="./loader.css" rel="stylesheet" />
|
||||
<link href="css-1.0.0.css" rel="stylesheet"></head>
|
||||
<body>
|
||||
<div id="root">
|
||||
<div class="load">
|
||||
<div class="loader">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="app-1.0.0.js"></script></body>
|
||||
</html>
|
84
build/loader.css
Normal file
84
build/loader.css
Normal file
@ -0,0 +1,84 @@
|
||||
body{
|
||||
background: #009688;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.load{
|
||||
width:240px;
|
||||
height:240px;
|
||||
position:absolute;
|
||||
top:50%;
|
||||
left:50%;
|
||||
margin:-120px 0 0 -120px;
|
||||
color:#efcc19;
|
||||
-webkit-animation:fadeIn 2s infinite ease-in-out;
|
||||
animation:fadeIn 2s infinite ease-in-out;
|
||||
-webkit-animation-delay:2s;
|
||||
animation-delay:2s;
|
||||
opacity:0;
|
||||
}
|
||||
.load .loader,.load .loader:before,.load .loader:after{
|
||||
background:#efcc19;
|
||||
-webkit-animation:load 1s infinite ease-in-out;
|
||||
animation:load 1s infinite ease-in-out;
|
||||
width:1em;
|
||||
height:4em
|
||||
}
|
||||
.load .loader:before,.load .loader:after{
|
||||
position:absolute;
|
||||
top:0;
|
||||
content:''
|
||||
}
|
||||
.load .loader:before{
|
||||
left:-1.5em;
|
||||
-webkit-animation-delay:-0.32s;
|
||||
animation-delay:-0.32s
|
||||
}
|
||||
.load .loader{
|
||||
text-indent:-9999em;
|
||||
margin:8em auto;
|
||||
position:relative;
|
||||
font-size:11px;
|
||||
-webkit-animation-delay:-0.16s;
|
||||
animation-delay:-0.16s
|
||||
}
|
||||
.load .loader:after{
|
||||
left:1.5em
|
||||
}
|
||||
@-webkit-keyframes load{
|
||||
0%,80%,100%{
|
||||
box-shadow:0 0 #efcc19;
|
||||
height:4em
|
||||
}
|
||||
40%{
|
||||
box-shadow:0 -2em #efcc19;height:5em
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load{
|
||||
0%,80%,100%{
|
||||
box-shadow:0 0 #efcc19;
|
||||
height:4em
|
||||
}
|
||||
40%{
|
||||
box-shadow:0 -2em #efcc19;
|
||||
height:5em
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeIn{
|
||||
0%{
|
||||
opacity:0;
|
||||
}
|
||||
100%{
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
@keyframes fadeIn{
|
||||
0%{
|
||||
opacity:0;
|
||||
}
|
||||
100%{
|
||||
opacity:1;
|
||||
}
|
||||
}
|
BIN
build/music.mp3
Normal file
BIN
build/music.mp3
Normal file
Binary file not shown.
90
i18n.json
Normal file
90
i18n.json
Normal file
@ -0,0 +1,90 @@
|
||||
{
|
||||
"lan": ["cn", "en"],
|
||||
"default": "cn",
|
||||
"data": {
|
||||
"title": {
|
||||
"cn": "俄罗斯方块",
|
||||
"en": "T E T R I S"
|
||||
},
|
||||
"about": {
|
||||
"cn": "关于\"我\"",
|
||||
"en": "About ME"
|
||||
},
|
||||
"QRCode":{
|
||||
"cn": "二维码",
|
||||
"en": "QR code"
|
||||
},
|
||||
"QRNotice": {
|
||||
"cn": "扫一扫用手机玩",
|
||||
"en": "Scan QR code to play with a mobile phone"
|
||||
},
|
||||
"linkTitle": {
|
||||
"cn": "使用React、Redux、Immutable编写「俄罗斯方块」",
|
||||
"en": "Use Tetact, Redux, Immutable to coding \"Tetris\""
|
||||
},
|
||||
"titleCenter": {
|
||||
"cn": "俄罗斯方块<br />TETRIS",
|
||||
"en": "TETRIS"
|
||||
},
|
||||
"point": {
|
||||
"cn": "得分",
|
||||
"en": "Point"
|
||||
},
|
||||
"highestScore": {
|
||||
"cn": "最高分",
|
||||
"en": "Max"
|
||||
},
|
||||
"lastRound": {
|
||||
"cn": "上轮得分",
|
||||
"en": "Last Round"
|
||||
},
|
||||
"cleans": {
|
||||
"cn": "消除行",
|
||||
"en": "Cleans"
|
||||
},
|
||||
"level": {
|
||||
"cn": "级别",
|
||||
"en": "Level"
|
||||
},
|
||||
"startLine": {
|
||||
"cn": "起始行",
|
||||
"en": "Start Line"
|
||||
},
|
||||
"next": {
|
||||
"cn": "下一个",
|
||||
"en": "Next"
|
||||
},
|
||||
"pause": {
|
||||
"cn": "暂停",
|
||||
"en": "Pause"
|
||||
},
|
||||
"sound": {
|
||||
"cn": "音效",
|
||||
"en": "Sound"
|
||||
},
|
||||
"reset": {
|
||||
"cn": "重玩",
|
||||
"en": "Reset"
|
||||
},
|
||||
"rotation": {
|
||||
"cn": "旋转",
|
||||
"en": "Rotation"
|
||||
},
|
||||
"left": {
|
||||
"cn": "左移",
|
||||
"en": "Left"
|
||||
},
|
||||
"right": {
|
||||
"cn": "右移",
|
||||
"en": "Right"
|
||||
},
|
||||
"down": {
|
||||
"cn": "下移",
|
||||
"en": "Down"
|
||||
},
|
||||
"drop": {
|
||||
"cn": "掉落",
|
||||
"en": "Drop"
|
||||
}
|
||||
}
|
||||
}
|
61
package.json
Normal file
61
package.json
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "react-tetris",
|
||||
"version": "1.0.0",
|
||||
"description": "使用React、Redux、Immutable编写「俄罗斯方块」。Use Tetact, Redux, Immutable to coding \"Tetris\".",
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server --progress",
|
||||
"MacBuild": "rm -rf ./build/* && NODE_ENV=production webpack --config ./webpack.production.config.js --progress && ls ./build",
|
||||
"WindowsBuild": "rm -rf ./build/* && set NODE_ENV=production && webpack --config ./webpack.production.config.js --progress && ls ./build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/chvin/react-tetris.git"
|
||||
},
|
||||
"keywords": [
|
||||
"Tetris",
|
||||
"React",
|
||||
"Redux",
|
||||
"Immutable",
|
||||
"俄罗斯方块"
|
||||
],
|
||||
"author": "Chvin",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/chvin/react-tetris/issues"
|
||||
},
|
||||
"homepage": "https://github.com/chvin/react-tetris#readme",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.13.2",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-plugin-react-transform": "^2.0.2",
|
||||
"babel-preset-es2015": "^6.13.2",
|
||||
"copy-webpack-plugin": "^3.0.1",
|
||||
"css-loader": "^0.23.1",
|
||||
"eslint": "^3.3.1",
|
||||
"eslint-config-airbnb": "^10.0.1",
|
||||
"eslint-loader": "^1.6.1",
|
||||
"eslint-plugin-import": "^1.13.0",
|
||||
"eslint-plugin-jsx-a11y": "^2.1.0",
|
||||
"eslint-plugin-react": "^6.1.1",
|
||||
"extract-text-webpack-plugin": "^1.0.1",
|
||||
"file-loader": "^0.9.0",
|
||||
"html-webpack-plugin": "^2.22.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"less": "^2.7.1",
|
||||
"less-loader": "^2.2.3",
|
||||
"react-transform-hmr": "^1.0.4",
|
||||
"style-loader": "^0.13.1",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^1.13.1",
|
||||
"webpack-dev-server": "^1.14.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.5",
|
||||
"immutable": "^3.8.1",
|
||||
"react": "^15.3.0",
|
||||
"react-dom": "^15.3.0",
|
||||
"react-redux": "^4.4.5",
|
||||
"redux": "^3.5.2",
|
||||
"redux-immutable": "^3.0.8"
|
||||
}
|
||||
}
|
21
server/index.html
Normal file
21
server/index.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta name="description" content="使用React、Redux、Immutable制作的俄罗斯方块" />
|
||||
<meta name="keywords" content="俄罗斯方块,Tetris,React,Redux,Immuatble,JavaScript">
|
||||
<meta name="format-detection" content="telephone=no"/>
|
||||
<script>;(function(){var w=parseInt(window.screen.width),s=w/640,u=navigator.userAgent.toLowerCase(),m='<meta name="viewport" content="width=640,';if(/android (\d+\.\d+)/.test(u)){if(parseFloat(RegExp.$1)>2.3)m+='minimum-scale='+s+',maximum-scale='+s+',';}else{m+='user-scalable=no,';}m+='target-densitydpi=device-dpi">';document.write(m);}());</script>
|
||||
<meta charset="UTF-8">
|
||||
<title>俄罗斯方块</title>
|
||||
<link href="./loader.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
<div class="load">
|
||||
<div class="loader">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
20
server/index.tmpl.html
Normal file
20
server/index.tmpl.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta name="description" content="使用React、Redux、Immutable制作的俄罗斯方块" />
|
||||
<meta name="keywords" content="俄罗斯方块,Tetris,React,Redux,Immuatble,JavaScript">
|
||||
<meta name="format-detection" content="telephone=no"/>
|
||||
<script>;(function(){var w=parseInt(window.screen.width),s=w/640,u=navigator.userAgent.toLowerCase(),m='<meta name="viewport" content="width=640,';if(/android (\d+\.\d+)/.test(u)){if(parseFloat(RegExp.$1)>2.3)m+='minimum-scale='+s+',maximum-scale='+s+',';}else{m+='user-scalable=no,';}m+='target-densitydpi=device-dpi">';document.write(m);}());</script>
|
||||
<meta charset="UTF-8">
|
||||
<title>俄罗斯方块</title>
|
||||
<link href="./loader.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
<div class="load">
|
||||
<div class="loader">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
128
src/actions/index.js
Normal file
128
src/actions/index.js
Normal file
@ -0,0 +1,128 @@
|
||||
import { getNextType } from '../unit';
|
||||
import * as reducerType from '../unit/reducerType';
|
||||
import Block from '../unit/block';
|
||||
import keyboard from './keyboard';
|
||||
|
||||
function nextBlock(next = getNextType()) {
|
||||
return {
|
||||
type: reducerType.NEXT_BLOCK,
|
||||
data: next,
|
||||
};
|
||||
}
|
||||
|
||||
function moveBlock(option) {
|
||||
return {
|
||||
type: reducerType.MOVE_BLOCK,
|
||||
data: option.reset === true ? null : new Block(option),
|
||||
};
|
||||
}
|
||||
|
||||
function speedStart(n) {
|
||||
return {
|
||||
type: reducerType.SPEED_START,
|
||||
data: n,
|
||||
};
|
||||
}
|
||||
|
||||
function speedRun(n) {
|
||||
return {
|
||||
type: reducerType.SPEED_RUN,
|
||||
data: n,
|
||||
};
|
||||
}
|
||||
|
||||
function startLines(n) {
|
||||
return {
|
||||
type: reducerType.START_LINES,
|
||||
data: n,
|
||||
};
|
||||
}
|
||||
|
||||
function matrix(data) {
|
||||
return {
|
||||
type: reducerType.MATRIX,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function lock(data) {
|
||||
return {
|
||||
type: reducerType.LOCK,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function clearLines(data) {
|
||||
return {
|
||||
type: reducerType.CLEAR_LINES,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function points(data) {
|
||||
return {
|
||||
type: reducerType.POINTS,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function max(data) {
|
||||
return {
|
||||
type: reducerType.MAX,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function reset(data) {
|
||||
return {
|
||||
type: reducerType.RESET,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function drop(data) {
|
||||
return {
|
||||
type: reducerType.DROP,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function pause(data) {
|
||||
return {
|
||||
type: reducerType.PAUSE,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function music(data) {
|
||||
return {
|
||||
type: reducerType.MUSIC,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function focus(data) {
|
||||
return {
|
||||
type: reducerType.FOCUS,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
nextBlock,
|
||||
moveBlock,
|
||||
speedStart,
|
||||
speedRun,
|
||||
startLines,
|
||||
matrix,
|
||||
lock,
|
||||
clearLines,
|
||||
points,
|
||||
reset,
|
||||
max,
|
||||
drop,
|
||||
pause,
|
||||
keyboard,
|
||||
music,
|
||||
focus,
|
||||
};
|
68
src/actions/keyboard.js
Normal file
68
src/actions/keyboard.js
Normal file
@ -0,0 +1,68 @@
|
||||
import * as reducerType from '../unit/reducerType';
|
||||
|
||||
function drop(data) {
|
||||
return {
|
||||
type: reducerType.KEY_DROP,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function down(data) {
|
||||
return {
|
||||
type: reducerType.KEY_DOWN,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function left(data) {
|
||||
return {
|
||||
type: reducerType.KEY_LEFT,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function right(data) {
|
||||
return {
|
||||
type: reducerType.KEY_RIGHT,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function rotate(data) {
|
||||
return {
|
||||
type: reducerType.KEY_ROTATE,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function reset(data) {
|
||||
return {
|
||||
type: reducerType.KEY_RESET,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function music(data) {
|
||||
return {
|
||||
type: reducerType.KEY_MUSIC,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function pause(data) {
|
||||
return {
|
||||
type: reducerType.KEY_PAUSE,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
drop,
|
||||
down,
|
||||
left,
|
||||
right,
|
||||
rotate,
|
||||
reset,
|
||||
music,
|
||||
pause,
|
||||
};
|
126
src/components/decorate/index.js
Normal file
126
src/components/decorate/index.js
Normal file
@ -0,0 +1,126 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
|
||||
import { i18n, lan } from '../../unit/const';
|
||||
import style from './index.less';
|
||||
|
||||
export default class Decorate extends React.Component {
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className={style.decorate}>
|
||||
<div className={style.topBorder}>
|
||||
<span className={cn(['l', style.mr])} style={{ width: 40 }} />
|
||||
<span className={cn(['l', style.mr])} />
|
||||
<span className={cn(['l', style.mr])} />
|
||||
<span className={cn(['l', style.mr])} />
|
||||
<span className={cn(['l', style.mr])} />
|
||||
<span className={cn(['r', style.ml])} style={{ width: 40 }} />
|
||||
<span className={cn(['r', style.ml])} />
|
||||
<span className={cn(['r', style.ml])} />
|
||||
<span className={cn(['r', style.ml])} />
|
||||
<span className={cn(['r', style.ml])} />
|
||||
</div>
|
||||
<h1>{i18n.title[lan]}</h1>
|
||||
<div className={style.view}>
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<b className="c" />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<em />
|
||||
<b className="c" />
|
||||
<p />
|
||||
<em />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<b className="c" />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<em />
|
||||
<b className="c" />
|
||||
<p />
|
||||
<b className="c" />
|
||||
<b className="c" />
|
||||
<b className="c" />
|
||||
<b className="c" />
|
||||
<p />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<b className="c" />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<b className="c" />
|
||||
<p />
|
||||
<b className="c" />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<b className="c" />
|
||||
<p />
|
||||
<em />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<em />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<em />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<em />
|
||||
<b className="c" />
|
||||
</div>
|
||||
<div className={cn([style.view, style.l])}>
|
||||
<em />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<b className="c" />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<b className="c" />
|
||||
<p />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<b className="c" />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<b className="c" />
|
||||
<p />
|
||||
<b className="c" />
|
||||
<b className="c" />
|
||||
<b className="c" />
|
||||
<b className="c" />
|
||||
<p />
|
||||
<em />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<b className="c" />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<em />
|
||||
<b className="c" />
|
||||
<p />
|
||||
<b className="c" />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<em />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<em />
|
||||
<b className="c" />
|
||||
<p />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<b className="c" />
|
||||
<div className="clear" />
|
||||
<b className="c" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
56
src/components/decorate/index.less
Normal file
56
src/components/decorate/index.less
Normal file
@ -0,0 +1,56 @@
|
||||
.decorate{
|
||||
h1{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
top: -12px;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 30px;
|
||||
}
|
||||
.topBorder{
|
||||
position:absolute;
|
||||
height:10px;
|
||||
width:100%;
|
||||
position:absolute;
|
||||
top:0px;
|
||||
left:0px;
|
||||
overflow:hidden;
|
||||
span{
|
||||
display:block;
|
||||
width:10px;
|
||||
height:10px;
|
||||
overflow:hidden;
|
||||
background:#000;
|
||||
&.mr{
|
||||
margin-right:10px;
|
||||
}
|
||||
&.ml{
|
||||
margin-left:10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.view{
|
||||
position: absolute;
|
||||
right: -70px;
|
||||
top: 20px;
|
||||
width: 44px;
|
||||
em {
|
||||
display: block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
overflow: hidden;
|
||||
float: left;
|
||||
}
|
||||
p {
|
||||
height: 22px;
|
||||
clear: both;
|
||||
}
|
||||
&.l{
|
||||
right: auto;
|
||||
left: -70px;
|
||||
}
|
||||
}
|
||||
}
|
51
src/components/guide/index.js
Normal file
51
src/components/guide/index.js
Normal file
@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import style from './index.less';
|
||||
import { transform, i18n, lan } from '../../unit/const';
|
||||
import { isMobile } from '../../unit';
|
||||
|
||||
export default class Guide extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
isMobile: isMobile(),
|
||||
};
|
||||
}
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div style={{ display: this.state.isMobile ? 'none' : 'block' }}>
|
||||
<div className={`${style.guide} ${style.right}`}>
|
||||
<div className={style.up}>
|
||||
<em style={{ [transform]: 'translate(0,-3px) scale(1,2)' }} />
|
||||
</div>
|
||||
<div className={style.left}>
|
||||
<em style={{ [transform]: 'translate(-7px,3px) rotate(-90deg) scale(1,2)' }} />
|
||||
</div>
|
||||
<div className={style.down}>
|
||||
<em style={{ [transform]: 'translate(0,9px) rotate(180deg) scale(1,2)' }} /></div>
|
||||
<div className={style.right}>
|
||||
<em style={{ [transform]: 'translate(7px,3px)rotate(90deg) scale(1,2)' }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${style.guide} ${style.left}`}>
|
||||
<p>
|
||||
<a href="https://github.com/chvin/react-tetris" rel="noopener noreferrer" target="_blank" title={i18n.linkTitle[lan]}>
|
||||
{i18n.about[lan]}
|
||||
</a>
|
||||
</p>
|
||||
<div className={style.space}>SPACE</div>
|
||||
</div>
|
||||
<div className={`${style.guide} ${style.qr}`}>
|
||||
<img
|
||||
src={`//game.weixin.qq.com/cgi-bin/comm/qrcode?s=10&m=1&d=${location.href}`}
|
||||
alt={i18n.QRCode[lan]}
|
||||
title={i18n.QRNotice[lan]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
115
src/components/guide/index.less
Normal file
115
src/components/guide/index.less
Normal file
@ -0,0 +1,115 @@
|
||||
.background(@from, @to){
|
||||
background: (@from + @to)/2;
|
||||
background: -webkit-gradient(linear, left top, left bottom, from(@from), to(@to));
|
||||
background: -moz-linear-gradient(top, @from, @from);
|
||||
// IE9使用filter背景渐变, 但因为使用了borader-radius, 所以不兼容, IE9使用中和的背景色即可
|
||||
//filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='@{from}', endColorstr='@{to}');
|
||||
}
|
||||
|
||||
.guide {
|
||||
position: absolute;
|
||||
left: 115%;
|
||||
right: 115%;
|
||||
text-align:center;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
&.right{
|
||||
right: auto;
|
||||
bottom: 5%;
|
||||
}
|
||||
&.left{
|
||||
left: auto;
|
||||
bottom: 5%;
|
||||
}
|
||||
p{
|
||||
text-align: right;
|
||||
margin-bottom: 350px;
|
||||
}
|
||||
|
||||
a{
|
||||
color:#005850;
|
||||
font-size:30px;
|
||||
position:relative;
|
||||
z-index:1;
|
||||
cursor: alias;
|
||||
}
|
||||
&.qr{
|
||||
left: auto;
|
||||
top: 5%;
|
||||
width: 190px;
|
||||
height:190px;
|
||||
opacity: .45;
|
||||
float: right;
|
||||
margin:0 0;
|
||||
padding:0 0 40px 40px;
|
||||
&:hover{
|
||||
img{
|
||||
transform: scale(1);
|
||||
-moz-transform: scale(1);
|
||||
-ms-transform: scale(1);
|
||||
}
|
||||
}
|
||||
img{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: scale(.2);
|
||||
transform-origin: top right;
|
||||
-moz-transform: scale(.2);
|
||||
-moz-transform-origin: top right;
|
||||
-ms-transform: scale(.2);
|
||||
-ms-transform-origin: top right;
|
||||
}
|
||||
}
|
||||
|
||||
&>div{
|
||||
width: 100px;
|
||||
height: 54px;
|
||||
background: /*#404040*/#364d4b;
|
||||
display:inline-block;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
color: #acc3c1;
|
||||
font-size: 16px;
|
||||
em {
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 6px solid;
|
||||
border-color: transparent transparent /*#ccc*/#acc3c1;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -9px 0 0 -6px;
|
||||
}
|
||||
&:before, &:after{
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius:4px;
|
||||
box-shadow: 0 5px 10px rgba(255, 255, 255, .15) inset;
|
||||
}
|
||||
&:before{
|
||||
box-shadow: 0 -5px 10px rgba(0, 0, 0, .15) inset;
|
||||
}
|
||||
}
|
||||
.up{
|
||||
height: 60px;
|
||||
display:block;
|
||||
margin:0 auto 2px;
|
||||
}
|
||||
.down{
|
||||
margin:0 10px;
|
||||
}
|
||||
.space{
|
||||
left: auto;
|
||||
bottom: 5%;
|
||||
height: 80px;
|
||||
width: 400px;
|
||||
line-height: 80px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
}
|
44
src/components/keyboard/button/index.js
Normal file
44
src/components/keyboard/button/index.js
Normal file
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import style from './index.less';
|
||||
import { transform } from '../../../unit/const';
|
||||
|
||||
export default class Button extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return nextProps.active !== this.props.active;
|
||||
}
|
||||
render() {
|
||||
const {
|
||||
active, color, size, top, left, label, position, arrow,
|
||||
} = this.props;
|
||||
return (
|
||||
<div
|
||||
className={cn({ [style.button]: true, [style[color]]: true, [style[size]]: true })}
|
||||
style={{ top, left }}
|
||||
>
|
||||
<i
|
||||
className={cn({ [style.active]: active })}
|
||||
ref={(c) => { this.dom = c; }}
|
||||
/>
|
||||
{ size === 's1' && <em
|
||||
style={{
|
||||
[transform]: `${arrow} scale(1,2)`,
|
||||
}}
|
||||
/> }
|
||||
<span className={cn({ [style.position]: position })}>{label}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Button.propTypes = {
|
||||
color: React.PropTypes.string.isRequired,
|
||||
size: React.PropTypes.string.isRequired,
|
||||
top: React.PropTypes.number.isRequired,
|
||||
left: React.PropTypes.number.isRequired,
|
||||
label: React.PropTypes.string.isRequired,
|
||||
position: React.PropTypes.bool,
|
||||
arrow: React.PropTypes.string,
|
||||
active: React.PropTypes.bool.isRequired,
|
||||
};
|
||||
|
106
src/components/keyboard/button/index.less
Normal file
106
src/components/keyboard/button/index.less
Normal file
@ -0,0 +1,106 @@
|
||||
.background(@from, @to){
|
||||
background: (@from + @to)/2;
|
||||
background: -webkit-gradient(linear, left top, left bottom, from(@from), to(@to));
|
||||
background: -moz-linear-gradient(top, @from, @from);
|
||||
// IE9使用filter背景渐变, 但因为使用了borader-radius, 所以不兼容, IE9使用中和的背景色即可
|
||||
//filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='@{from}', endColorstr='@{to}');
|
||||
}
|
||||
|
||||
.button{
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
color: #111;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
line-height: 1.6;
|
||||
&.s2{
|
||||
font-size: 16px;
|
||||
}
|
||||
span.position{
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 102px;
|
||||
}
|
||||
i{
|
||||
display: block;
|
||||
position: relative;
|
||||
border: 1px solid #000;
|
||||
border-radius: 50%;
|
||||
&:before, &:after{
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius:50%;
|
||||
box-shadow: 0 5px 10px rgba(255, 255, 255, .8) inset;
|
||||
}
|
||||
&:after{
|
||||
box-shadow: 0 -5px 10px rgba(0, 0, 0, .8) inset;
|
||||
}
|
||||
&.active{
|
||||
&:before{
|
||||
box-shadow: 0 -3px 6px rgba(255, 255, 255, .6) inset;
|
||||
}
|
||||
&:after{
|
||||
box-shadow: 0 5px 5px rgba(0, 0, 0, .6) inset;
|
||||
}
|
||||
}
|
||||
box-shadow: 0 3px 3px rgba(0, 0, 0, .2);
|
||||
|
||||
}
|
||||
|
||||
&.blue i{
|
||||
.background(#6e77ef, #4652f3);
|
||||
}
|
||||
&.green i{
|
||||
.background(#4bc441, #0ec400);
|
||||
}
|
||||
&.red i{
|
||||
.background(#dc3333, #de0000);
|
||||
}
|
||||
&.s0 i{
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
|
||||
}
|
||||
&.s1 i{
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
&.s2 i{
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
&:before, &:after{
|
||||
box-shadow: 0px 3px 6px rgba(255, 255, 255, .8) inset;
|
||||
}
|
||||
&:after{
|
||||
box-shadow: 0px -3px 6px rgba(0, 0, 0, .8) inset;
|
||||
}
|
||||
&.active{
|
||||
&:before{
|
||||
box-shadow: 0px -1px 2px rgba(255, 255, 255, .6) inset;
|
||||
}
|
||||
&:after{
|
||||
box-shadow: 0px 3px 3px rgba(0, 0, 0, .7) inset;
|
||||
}
|
||||
}
|
||||
box-shadow: 1px 1px 1px rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
&.s1{
|
||||
em{
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 8px solid;
|
||||
border-color: transparent transparent #111;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -12px 0 0 -8px;
|
||||
}
|
||||
}
|
||||
}
|
154
src/components/keyboard/index.js
Normal file
154
src/components/keyboard/index.js
Normal file
@ -0,0 +1,154 @@
|
||||
import React from 'react';
|
||||
import Immutable from 'immutable';
|
||||
import style from './index.less';
|
||||
import Button from './button';
|
||||
import store from '../../store';
|
||||
import todo from '../../control/todo';
|
||||
import { i18n, lan } from '../../unit/const';
|
||||
|
||||
export default class Keyboard extends React.Component {
|
||||
componentDidMount() {
|
||||
const touchEventCatch = {}; // 对于手机操作, 触发了touchstart, 将作出记录, 不再触发后面的mouse事件
|
||||
|
||||
// 在鼠标触发mousedown时, 移除元素时可以不触发mouseup, 这里做一个兼容, 以mouseout模拟mouseup
|
||||
const mouseDownEventCatch = {};
|
||||
document.addEventListener('touchstart', (e) => {
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}, true);
|
||||
|
||||
document.addEventListener('mousedown', (e) => {
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}, true);
|
||||
|
||||
Object.keys(todo).forEach((key) => {
|
||||
this[`dom_${key}`].dom.addEventListener('mousedown', () => {
|
||||
if (touchEventCatch[key] === true) {
|
||||
return;
|
||||
}
|
||||
todo[key].down(store);
|
||||
mouseDownEventCatch[key] = true;
|
||||
}, true);
|
||||
this[`dom_${key}`].dom.addEventListener('mouseup', () => {
|
||||
if (touchEventCatch[key] === true) {
|
||||
touchEventCatch[key] = false;
|
||||
return;
|
||||
}
|
||||
todo[key].up(store);
|
||||
mouseDownEventCatch[key] = false;
|
||||
}, true);
|
||||
this[`dom_${key}`].dom.addEventListener('mouseout', () => {
|
||||
if (mouseDownEventCatch[key] === true) {
|
||||
todo[key].up(store);
|
||||
}
|
||||
}, true);
|
||||
this[`dom_${key}`].dom.addEventListener('touchstart', () => {
|
||||
touchEventCatch[key] = true;
|
||||
todo[key].down(store);
|
||||
}, true);
|
||||
this[`dom_${key}`].dom.addEventListener('touchend', () => {
|
||||
todo[key].up(store);
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
shouldComponentUpdate({ keyboard, filling }) {
|
||||
return !Immutable.is(keyboard, this.props.keyboard) || filling !== this.props.filling;
|
||||
}
|
||||
render() {
|
||||
const keyboard = this.props.keyboard;
|
||||
return (
|
||||
<div
|
||||
className={style.keyboard}
|
||||
style={{
|
||||
marginTop: 20 + this.props.filling,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
color="blue"
|
||||
size="s1"
|
||||
top={0}
|
||||
left={374}
|
||||
label={i18n.rotation[lan]}
|
||||
arrow="translate(0, 63px)"
|
||||
position
|
||||
active={keyboard.get('rotate')}
|
||||
ref={(c) => { this.dom_rotate = c; }}
|
||||
/>
|
||||
<Button
|
||||
color="blue"
|
||||
size="s1"
|
||||
top={180}
|
||||
left={374}
|
||||
label={i18n.down[lan]}
|
||||
arrow="translate(0,-71px) rotate(180deg)"
|
||||
active={keyboard.get('down')}
|
||||
ref={(c) => { this.dom_down = c; }}
|
||||
/>
|
||||
<Button
|
||||
color="blue"
|
||||
size="s1"
|
||||
top={90}
|
||||
left={284}
|
||||
label={i18n.left[lan]}
|
||||
arrow="translate(60px, -12px) rotate(270deg)"
|
||||
active={keyboard.get('left')}
|
||||
ref={(c) => { this.dom_left = c; }}
|
||||
/>
|
||||
<Button
|
||||
color="blue"
|
||||
size="s1"
|
||||
top={90}
|
||||
left={464}
|
||||
label={i18n.right[lan]}
|
||||
arrow="translate(-60px, -12px) rotate(90deg)"
|
||||
active={keyboard.get('right')}
|
||||
ref={(c) => { this.dom_right = c; }}
|
||||
/>
|
||||
<Button
|
||||
color="blue"
|
||||
size="s0"
|
||||
top={100}
|
||||
left={52}
|
||||
label={`${i18n.drop[lan]} (SPACE)`}
|
||||
active={keyboard.get('drop')}
|
||||
ref={(c) => { this.dom_space = c; }}
|
||||
/>
|
||||
<Button
|
||||
color="red"
|
||||
size="s2"
|
||||
top={0}
|
||||
left={196}
|
||||
label={`${i18n.reset[lan]}(R)`}
|
||||
active={keyboard.get('reset')}
|
||||
ref={(c) => { this.dom_r = c; }}
|
||||
/>
|
||||
<Button
|
||||
color="green"
|
||||
size="s2"
|
||||
top={0}
|
||||
left={106}
|
||||
label={`${i18n.sound[lan]}(S)`}
|
||||
active={keyboard.get('music')}
|
||||
ref={(c) => { this.dom_s = c; }}
|
||||
/>
|
||||
<Button
|
||||
color="green"
|
||||
size="s2"
|
||||
top={0}
|
||||
left={16}
|
||||
label={`${i18n.pause[lan]}(P)`}
|
||||
active={keyboard.get('pause')}
|
||||
ref={(c) => { this.dom_p = c; }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Keyboard.propTypes = {
|
||||
filling: React.PropTypes.number.isRequired,
|
||||
keyboard: React.PropTypes.object.isRequired,
|
||||
};
|
6
src/components/keyboard/index.less
Normal file
6
src/components/keyboard/index.less
Normal file
@ -0,0 +1,6 @@
|
||||
.keyboard{
|
||||
width: 580px;
|
||||
height: 330px;
|
||||
margin: 20px auto 0;
|
||||
position:relative;
|
||||
}
|
150
src/components/logo/index.js
Normal file
150
src/components/logo/index.js
Normal file
@ -0,0 +1,150 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import style from './index.less';
|
||||
import { i18n, lan } from '../../unit/const';
|
||||
|
||||
export default class Logo extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
style: style.r1,
|
||||
display: 'none',
|
||||
};
|
||||
}
|
||||
componentWillMount() {
|
||||
this.animate(this.props);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if ( // 只有在游戏进入开始, 或结束时 触发改变
|
||||
(
|
||||
[this.props.cur, nextProps.cur].indexOf(false) !== -1 &&
|
||||
(this.props.cur !== nextProps.cur)
|
||||
) ||
|
||||
(this.props.reset !== nextProps.reset)
|
||||
) {
|
||||
this.animate(nextProps);
|
||||
}
|
||||
}
|
||||
shouldComponentUpdate({ cur, reset }) {
|
||||
return cur !== this.props.cur || reset !== this.props.reset || !cur;
|
||||
}
|
||||
animate({ cur, reset }) {
|
||||
clearTimeout(Logo.timeout);
|
||||
this.setState({
|
||||
style: style.r1,
|
||||
display: 'none',
|
||||
});
|
||||
if (cur || reset) {
|
||||
this.setState({ display: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
let m = 'r'; // 方向
|
||||
let count = 0;
|
||||
|
||||
const set = (func, delay) => {
|
||||
if (!func) {
|
||||
return;
|
||||
}
|
||||
Logo.timeout = setTimeout(func, delay);
|
||||
};
|
||||
|
||||
const show = (func) => { // 显示
|
||||
set(() => {
|
||||
this.setState({
|
||||
display: 'block',
|
||||
});
|
||||
if (func) {
|
||||
func();
|
||||
}
|
||||
}, 150);
|
||||
};
|
||||
|
||||
const hide = (func) => { // 隐藏
|
||||
set(() => {
|
||||
this.setState({
|
||||
display: 'none',
|
||||
});
|
||||
if (func) {
|
||||
func();
|
||||
}
|
||||
}, 150);
|
||||
};
|
||||
|
||||
const eyes = (func, delay1, delay2) => { // 龙在眨眼睛
|
||||
set(() => {
|
||||
this.setState({ style: style[m + 2] });
|
||||
set(() => {
|
||||
this.setState({ style: style[m + 1] });
|
||||
if (func) {
|
||||
func();
|
||||
}
|
||||
}, delay2);
|
||||
}, delay1);
|
||||
};
|
||||
|
||||
const run = (func) => { // 开始跑步啦!
|
||||
set(() => {
|
||||
this.setState({ style: style[m + 4] });
|
||||
set(() => {
|
||||
this.setState({ style: style[m + 3] });
|
||||
count++;
|
||||
if (count === 10 || count === 20 || count === 30) {
|
||||
m = m === 'r' ? 'l' : 'r';
|
||||
}
|
||||
if (count < 40) {
|
||||
run(func);
|
||||
return;
|
||||
}
|
||||
this.setState({ style: style[m + 1] });
|
||||
if (func) {
|
||||
set(func, 4000);
|
||||
}
|
||||
}, 100);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const dra = () => {
|
||||
count = 0;
|
||||
eyes(() => {
|
||||
eyes(() => {
|
||||
eyes(() => {
|
||||
this.setState({ style: style[m + 2] });
|
||||
run(dra);
|
||||
}, 150, 150);
|
||||
}, 150, 150);
|
||||
}, 1000, 1500);
|
||||
};
|
||||
|
||||
show(() => { // 忽隐忽现
|
||||
hide(() => {
|
||||
show(() => {
|
||||
hide(() => {
|
||||
show(() => {
|
||||
dra(); // 开始运动
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
render() {
|
||||
if (this.props.cur) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={style.logo} style={{ display: this.state.display }}>
|
||||
<div className={cn({ bg: true, [style.dragon]: true, [this.state.style]: true })} />
|
||||
<p dangerouslySetInnerHTML={{ __html: i18n.titleCenter[lan] }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Logo.propTypes = {
|
||||
cur: React.PropTypes.bool,
|
||||
reset: React.PropTypes.bool.isRequired,
|
||||
};
|
||||
Logo.statics = {
|
||||
timeout: null,
|
||||
};
|
44
src/components/logo/index.less
Normal file
44
src/components/logo/index.less
Normal file
@ -0,0 +1,44 @@
|
||||
.logo {
|
||||
width: 224px;
|
||||
height: 200px;
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 12px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
p {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
line-height: 1.4;
|
||||
top: 100px;
|
||||
left: 0;
|
||||
font-family: initial;
|
||||
letter-spacing: 6px;
|
||||
text-shadow: 1px 1px 1px rgba(255, 255, 255,.35);
|
||||
}
|
||||
.dragon {
|
||||
width: 80px;
|
||||
height: 86px;
|
||||
margin: 0 auto;
|
||||
background-position: 0 -100px;
|
||||
&.r1,&.l1 {
|
||||
background-position: 0 -100px;
|
||||
}
|
||||
&.r2,&.l2 {
|
||||
background-position: -100px -100px;
|
||||
}
|
||||
&.r3,&.l3 {
|
||||
background-position: -200px -100px;
|
||||
}
|
||||
&.r4,&.l4 {
|
||||
background-position: -300px -100px;
|
||||
}
|
||||
&.l1,&.l2,&.l3,&.l4{
|
||||
transform: scale(-1, 1);
|
||||
-webkit-transform: scale(-1, 1);
|
||||
-ms-transform: scale(-1, 1);
|
||||
-moz-transform: scale(-1, 1);
|
||||
-o-transform: scale(-1, 1);
|
||||
}
|
||||
}
|
||||
}
|
171
src/components/matrix/index.js
Normal file
171
src/components/matrix/index.js
Normal file
@ -0,0 +1,171 @@
|
||||
import React from 'react';
|
||||
import immutable, { List } from 'immutable';
|
||||
import classnames from 'classnames';
|
||||
import style from './index.less';
|
||||
import { isClear } from '../../unit/';
|
||||
import { fillLine, blankLine } from '../../unit/const';
|
||||
import states from '../../control/states';
|
||||
|
||||
const t = setTimeout;
|
||||
|
||||
export default class Matrix extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
clearLines: false,
|
||||
animateColor: 2,
|
||||
isOver: false,
|
||||
overState: null,
|
||||
};
|
||||
}
|
||||
componentWillReceiveProps(nextProps = {}) {
|
||||
const clears = isClear(nextProps.matrix);
|
||||
const overs = nextProps.reset;
|
||||
this.setState({
|
||||
clearLines: clears,
|
||||
isOver: overs,
|
||||
});
|
||||
if (clears && !this.state.clearLines) {
|
||||
this.clearAnimate(clears);
|
||||
}
|
||||
if (!clears && overs && !this.state.isOver) {
|
||||
this.over(nextProps);
|
||||
}
|
||||
}
|
||||
shouldComponentUpdate(nextProps = {}) { // 使用Immutable 比较两个List 是否相等
|
||||
const props = this.props;
|
||||
return !(
|
||||
immutable.is(nextProps.matrix, props.matrix) &&
|
||||
immutable.is(
|
||||
(nextProps.cur && nextProps.cur.shape),
|
||||
(props.cur && props.cur.shape)
|
||||
) &&
|
||||
immutable.is(
|
||||
(nextProps.cur && nextProps.cur.xy),
|
||||
(props.cur && props.cur.xy)
|
||||
)
|
||||
) || this.state.clearLines
|
||||
|| this.state.isOver;
|
||||
}
|
||||
getResult(props = this.props) {
|
||||
const cur = props.cur;
|
||||
const shape = cur && cur.shape;
|
||||
const xy = cur && cur.xy;
|
||||
|
||||
let matrix = props.matrix;
|
||||
const clearLines = this.state.clearLines;
|
||||
if (clearLines) {
|
||||
const animateColor = this.state.animateColor;
|
||||
clearLines.forEach((index) => {
|
||||
matrix = matrix.set(index, List([
|
||||
animateColor,
|
||||
animateColor,
|
||||
animateColor,
|
||||
animateColor,
|
||||
animateColor,
|
||||
animateColor,
|
||||
animateColor,
|
||||
animateColor,
|
||||
animateColor,
|
||||
animateColor,
|
||||
]));
|
||||
});
|
||||
} else if (shape) {
|
||||
shape.forEach((m, k1) => (
|
||||
m.forEach((n, k2) => {
|
||||
if (n && xy.get(0) + k1 >= 0) { // 竖坐标可以为负
|
||||
let line = matrix.get(xy.get(0) + k1);
|
||||
let color;
|
||||
if (line.get(xy.get(1) + k2) === 1 && !clearLines) { // 矩阵与方块重合
|
||||
color = 2;
|
||||
} else {
|
||||
color = 1;
|
||||
}
|
||||
line = line.set(xy.get(1) + k2, color);
|
||||
matrix = matrix.set(xy.get(0) + k1, line);
|
||||
}
|
||||
})
|
||||
));
|
||||
}
|
||||
return matrix;
|
||||
}
|
||||
clearAnimate() {
|
||||
const anima = (callback) => {
|
||||
t(() => {
|
||||
this.setState({
|
||||
animateColor: 0,
|
||||
});
|
||||
t(() => {
|
||||
this.setState({
|
||||
animateColor: 2,
|
||||
});
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
}, 100);
|
||||
}, 100);
|
||||
};
|
||||
anima(() => {
|
||||
anima(() => {
|
||||
anima(() => {
|
||||
t(() => {
|
||||
states.clearLines(this.props.matrix, this.state.clearLines);
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
over(nextProps) {
|
||||
let overState = this.getResult(nextProps);
|
||||
this.setState({
|
||||
overState,
|
||||
});
|
||||
|
||||
const exLine = (index) => {
|
||||
if (index <= 19) {
|
||||
overState = overState.set(19 - index, List(fillLine));
|
||||
} else if (index >= 20 && index <= 39) {
|
||||
overState = overState.set(index - 20, List(blankLine));
|
||||
} else {
|
||||
states.overEnd();
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
overState,
|
||||
});
|
||||
};
|
||||
|
||||
for (let i = 0; i <= 40; i++) {
|
||||
t(exLine.bind(null, i), 40 * (i + 1));
|
||||
}
|
||||
}
|
||||
render() {
|
||||
let matrix;
|
||||
if (this.state.isOver) {
|
||||
matrix = this.state.overState;
|
||||
} else {
|
||||
matrix = this.getResult();
|
||||
}
|
||||
return (
|
||||
<div className={style.matrix}>{
|
||||
matrix.map((p, k1) => (<p key={k1}>
|
||||
{
|
||||
p.map((e, k2) => <b
|
||||
className={classnames({
|
||||
c: e === 1,
|
||||
d: e === 2,
|
||||
})}
|
||||
key={k2}
|
||||
/>)
|
||||
}
|
||||
</p>))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Matrix.propTypes = {
|
||||
matrix: React.PropTypes.object.isRequired,
|
||||
cur: React.PropTypes.object,
|
||||
};
|
9
src/components/matrix/index.less
Normal file
9
src/components/matrix/index.less
Normal file
@ -0,0 +1,9 @@
|
||||
.matrix{
|
||||
border:2px solid #000;
|
||||
padding:3px 1px 1px 3px;
|
||||
width:228px;
|
||||
p{
|
||||
width:220px;
|
||||
height:22px;
|
||||
}
|
||||
}
|
26
src/components/music/index.js
Normal file
26
src/components/music/index.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import style from './index.less';
|
||||
|
||||
export default class Music extends React.Component {
|
||||
shouldComponentUpdate({ data }) {
|
||||
return data !== this.props.data;
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
{
|
||||
bg: true,
|
||||
[style.music]: true,
|
||||
[style.c]: !this.props.data,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Music.propTypes = {
|
||||
data: React.PropTypes.bool.isRequired,
|
||||
};
|
11
src/components/music/index.less
Normal file
11
src/components/music/index.less
Normal file
@ -0,0 +1,11 @@
|
||||
.music{
|
||||
width: 25px;
|
||||
height: 21px;
|
||||
background-position: -175px -75px;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: -12px;
|
||||
&.c{
|
||||
background-position: -150px -75px;
|
||||
}
|
||||
}
|
69
src/components/next/index.js
Normal file
69
src/components/next/index.js
Normal file
@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import style from './index.less';
|
||||
import { blockShape } from '../../unit/const';
|
||||
|
||||
const xy = { // 方块在下一个中的坐标
|
||||
I: [1, 0],
|
||||
L: [0, 0],
|
||||
J: [0, 0],
|
||||
Z: [0, 0],
|
||||
S: [0, 0],
|
||||
O: [0, 1],
|
||||
T: [0, 0],
|
||||
};
|
||||
|
||||
const empty = [
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
];
|
||||
|
||||
export default class Next extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
block: empty,
|
||||
};
|
||||
}
|
||||
componentWillMount() {
|
||||
this.build(this.props.data);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.build(nextProps.data);
|
||||
}
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return nextProps.data !== this.props.data;
|
||||
}
|
||||
build(type) {
|
||||
const shape = blockShape[type];
|
||||
const block = empty.map(e => ([...e]));
|
||||
shape.forEach((m, k1) => {
|
||||
m.forEach((n, k2) => {
|
||||
if (n) {
|
||||
block[k1 + xy[type][0]][k2 + xy[type][1]] = 1;
|
||||
}
|
||||
});
|
||||
});
|
||||
this.setState({ block });
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className={style.next}>
|
||||
{
|
||||
this.state.block.map((arr, k1) => (
|
||||
<div key={k1}>
|
||||
{
|
||||
arr.map((e, k2) => (
|
||||
<b className={e ? 'c' : ''} key={k2} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Next.propTypes = {
|
||||
data: React.PropTypes.string,
|
||||
};
|
7
src/components/next/index.less
Normal file
7
src/components/next/index.less
Normal file
@ -0,0 +1,7 @@
|
||||
.next{
|
||||
div{
|
||||
height: 22px;
|
||||
width: 88px;
|
||||
float: right;
|
||||
}
|
||||
}
|
93
src/components/number/index.js
Normal file
93
src/components/number/index.js
Normal file
@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import style from './index.less';
|
||||
|
||||
const render = (data) => (
|
||||
<div className={style.number}>
|
||||
{
|
||||
data.map((e, k) => (
|
||||
<span className={cn(['bg', style[`s_${e}`]])} key={k} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
const formate = (num) => (
|
||||
num < 10 ? `0${num}`.split('') : `${num}`.split('')
|
||||
);
|
||||
|
||||
|
||||
export default class Number extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
time_count: false,
|
||||
time: new Date(),
|
||||
};
|
||||
}
|
||||
componentWillMount() {
|
||||
if (!this.props.time) {
|
||||
return;
|
||||
}
|
||||
const clock = () => {
|
||||
const count = +Number.timeInterval;
|
||||
Number.timeInterval = setTimeout(() => {
|
||||
this.setState({
|
||||
time: new Date(),
|
||||
time_count: count, // 用来做 shouldComponentUpdate 优化
|
||||
});
|
||||
clock();
|
||||
}, 1000);
|
||||
};
|
||||
clock();
|
||||
}
|
||||
shouldComponentUpdate({ number }) {
|
||||
if (this.props.time) { // 右下角时钟
|
||||
if (this.state.time_count !== Number.time_count) {
|
||||
if (this.state.time_count !== false) {
|
||||
Number.time_count = this.state.time_count; // 记录clock上一次的缓存
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false; // 经过判断这次的时间已经渲染, 返回false
|
||||
}
|
||||
return this.props.number !== number;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
if (!this.props.time) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(Number.timeInterval);
|
||||
}
|
||||
render() {
|
||||
if (this.props.time) { // 右下角时钟
|
||||
const now = this.state.time;
|
||||
const hour = formate(now.getHours());
|
||||
const min = formate(now.getMinutes());
|
||||
const sec = now.getSeconds() % 2;
|
||||
const t = hour.concat(sec ? 'd' : 'd_c', min);
|
||||
return (render(t));
|
||||
}
|
||||
|
||||
const num = `${this.props.number}`.split('');
|
||||
for (let i = 0, len = this.props.length - num.length; i < len; i++) {
|
||||
num.unshift('n');
|
||||
}
|
||||
return (render(num));
|
||||
}
|
||||
}
|
||||
|
||||
Number.statics = {
|
||||
timeInterval: null,
|
||||
time_count: null,
|
||||
};
|
||||
|
||||
Number.propTypes = {
|
||||
number: React.PropTypes.number,
|
||||
length: React.PropTypes.number,
|
||||
time: React.PropTypes.bool,
|
||||
};
|
||||
|
||||
Number.defaultProps = {
|
||||
length: 6,
|
||||
};
|
23
src/components/number/index.less
Normal file
23
src/components/number/index.less
Normal file
@ -0,0 +1,23 @@
|
||||
.number{
|
||||
height:24px;
|
||||
font-size:14px;
|
||||
float:right;
|
||||
span{
|
||||
float:left;
|
||||
width:14px;
|
||||
height:24px;
|
||||
}
|
||||
.s_0{background-position:-75px -25px;}
|
||||
.s_1{background-position:-89px -25px;}
|
||||
.s_2{background-position:-103px -25px;}
|
||||
.s_3{background-position:-117px -25px;}
|
||||
.s_4{background-position:-131px -25px;}
|
||||
.s_5{background-position:-145px -25px;}
|
||||
.s_6{background-position:-159px -25px;}
|
||||
.s_7{background-position:-173px -25px;}
|
||||
.s_8{background-position:-187px -25px;}
|
||||
.s_9{background-position:-201px -25px;}
|
||||
.s_n{background-position:-215px -25px;}
|
||||
.s_d{background-position:-243px -25px;}
|
||||
.s_d_c{background-position:-229px -25px;}
|
||||
}
|
65
src/components/pause/index.js
Normal file
65
src/components/pause/index.js
Normal file
@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import style from './index.less';
|
||||
|
||||
export default class Pause extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = { // 控制显示状态
|
||||
showPause: false,
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
this.setShake(this.props.data);
|
||||
}
|
||||
componentWillReceiveProps({ data }) {
|
||||
this.setShake(data);
|
||||
}
|
||||
shouldComponentUpdate({ data }) {
|
||||
if (data) { // 如果暂停了, 不会有太多的dispatch, 考虑到闪烁效果, 直接返回true
|
||||
return true;
|
||||
}
|
||||
return data !== this.props.data;
|
||||
}
|
||||
setShake(bool) { // 根据props显示闪烁或停止闪烁
|
||||
if (bool && !Pause.timeout) {
|
||||
Pause.timeout = setInterval(() => {
|
||||
this.setState({
|
||||
showPause: !this.state.showPause,
|
||||
});
|
||||
}, 250);
|
||||
}
|
||||
if (!bool && Pause.timeout) {
|
||||
clearInterval(Pause.timeout);
|
||||
this.setState({
|
||||
showPause: false,
|
||||
});
|
||||
Pause.timeout = null;
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
{
|
||||
bg: true,
|
||||
[style.pause]: true,
|
||||
[style.c]: this.state.showPause,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Pause.statics = {
|
||||
timeout: null,
|
||||
};
|
||||
|
||||
Pause.propTypes = {
|
||||
data: React.PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
Pause.defaultProps = {
|
||||
data: false,
|
||||
};
|
11
src/components/pause/index.less
Normal file
11
src/components/pause/index.less
Normal file
@ -0,0 +1,11 @@
|
||||
.pause {
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
background-position: -100px -75px;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 18px;
|
||||
&.c {
|
||||
background-position: -75px -75px;
|
||||
}
|
||||
}
|
78
src/components/point/index.js
Normal file
78
src/components/point/index.js
Normal file
@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import Number from '../number';
|
||||
import { i18n, lan } from '../../unit/const';
|
||||
|
||||
const DF = i18n.point[lan];
|
||||
const ZDF = i18n.highestScore[lan];
|
||||
const SLDF = i18n.lastRound[lan];
|
||||
|
||||
export default class Point extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
label: '',
|
||||
number: 0,
|
||||
};
|
||||
}
|
||||
componentWillMount() {
|
||||
this.onChange(this.props);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.onChange(nextProps);
|
||||
}
|
||||
shouldComponentUpdate({ cur, point, max }) {
|
||||
const props = this.props;
|
||||
return cur !== props.cur || point !== props.point || max !== props.max || !props.cur;
|
||||
}
|
||||
onChange({ cur, point, max }) {
|
||||
clearInterval(Point.timeout);
|
||||
if (cur) { // 在游戏进行中
|
||||
this.setState({
|
||||
label: point >= max ? ZDF : DF,
|
||||
number: point,
|
||||
});
|
||||
} else { // 游戏未开始
|
||||
const toggle = () => { // 最高分与上轮得分交替出现
|
||||
this.setState({
|
||||
label: SLDF,
|
||||
number: point,
|
||||
});
|
||||
Point.timeout = setTimeout(() => {
|
||||
this.setState({
|
||||
label: ZDF,
|
||||
number: max,
|
||||
});
|
||||
Point.timeout = setTimeout(toggle, 3000);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
if (point !== 0) { // 如果为上轮没玩, 也不用提示了
|
||||
toggle();
|
||||
} else {
|
||||
this.setState({
|
||||
label: ZDF,
|
||||
number: max,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<p>{ this.state.label }</p>
|
||||
<Number number={this.state.number} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Point.statics = {
|
||||
timeout: null,
|
||||
};
|
||||
|
||||
Point.propTypes = {
|
||||
cur: React.PropTypes.bool,
|
||||
max: React.PropTypes.number.isRequired,
|
||||
point: React.PropTypes.number.isRequired,
|
||||
};
|
||||
|
161
src/containers/index.js
Normal file
161
src/containers/index.js
Normal file
@ -0,0 +1,161 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import style from './index.less';
|
||||
|
||||
import Matrix from '../components/matrix';
|
||||
import Decorate from '../components/decorate';
|
||||
import Number from '../components/number';
|
||||
import Next from '../components/next';
|
||||
import Music from '../components/music';
|
||||
import Pause from '../components/pause';
|
||||
import Point from '../components/point';
|
||||
import Logo from '../components/logo';
|
||||
import Keyboard from '../components/keyboard';
|
||||
import Guide from '../components/guide';
|
||||
|
||||
import { transform, lastRecord, speeds, i18n, lan } from '../unit/const';
|
||||
import { visibilityChangeEvent, isFocus } from '../unit/';
|
||||
import states from '../control/states';
|
||||
|
||||
class App extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
w: document.documentElement.clientWidth,
|
||||
h: document.documentElement.clientHeight,
|
||||
};
|
||||
}
|
||||
componentWillMount() {
|
||||
window.addEventListener('resize', this.resize.bind(this), true);
|
||||
}
|
||||
componentDidMount() {
|
||||
if (visibilityChangeEvent) { // 将页面的焦点变换写入store
|
||||
document.addEventListener(visibilityChangeEvent, () => {
|
||||
states.focus(isFocus());
|
||||
}, false);
|
||||
}
|
||||
|
||||
if (lastRecord) { // 读取记录
|
||||
if (lastRecord.cur && !lastRecord.pause) { // 拿到上一次游戏的状态, 如果在游戏中且没有暂停, 游戏继续
|
||||
const speedRun = this.props.speedRun;
|
||||
let timeout = speeds[speedRun - 1] / 2; // 继续时, 给予当前下落速度一半的停留时间
|
||||
// 停留时间不小于最快速的速度
|
||||
timeout = speedRun < speeds[speeds.length - 1] ? speeds[speeds.length - 1] : speedRun;
|
||||
states.auto(timeout);
|
||||
}
|
||||
if (!lastRecord.cur) {
|
||||
states.overStart();
|
||||
}
|
||||
} else {
|
||||
states.overStart();
|
||||
}
|
||||
}
|
||||
resize() {
|
||||
this.setState({
|
||||
w: document.documentElement.clientWidth,
|
||||
h: document.documentElement.clientHeight,
|
||||
});
|
||||
}
|
||||
render() {
|
||||
let filling = 0;
|
||||
const size = (() => {
|
||||
const w = this.state.w;
|
||||
const h = this.state.h;
|
||||
const ratio = h / w;
|
||||
let scale;
|
||||
let css = {};
|
||||
if (ratio < 1.5) {
|
||||
scale = h / 960;
|
||||
} else {
|
||||
scale = w / 640;
|
||||
filling = (h - (960 * scale)) / scale / 3;
|
||||
css = {
|
||||
paddingTop: Math.floor(filling) + 42,
|
||||
paddingBottom: Math.floor(filling),
|
||||
marginTop: Math.floor(-480 - (filling * 1.5)),
|
||||
};
|
||||
}
|
||||
css[transform] = `scale(${scale})`;
|
||||
return css;
|
||||
})();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={style.app}
|
||||
style={size}
|
||||
>
|
||||
<div className={classnames({ [style.rect]: true, [style.drop]: this.props.drop })}>
|
||||
<Decorate />
|
||||
<div className={style.screen}>
|
||||
<div className={style.panel}>
|
||||
<Matrix
|
||||
matrix={this.props.matrix}
|
||||
cur={this.props.cur}
|
||||
reset={this.props.reset}
|
||||
/>
|
||||
<Logo cur={!!this.props.cur} reset={this.props.reset} />
|
||||
<div className={style.state}>
|
||||
<Point cur={!!this.props.cur} point={this.props.points} max={this.props.max} />
|
||||
<p>{ this.props.cur ? i18n.cleans[lan] : i18n.startLine[lan] }</p>
|
||||
<Number number={this.props.cur ? this.props.clearLines : this.props.startLines} />
|
||||
<p>{i18n.level[lan]}</p>
|
||||
<Number
|
||||
number={this.props.cur ? this.props.speedRun : this.props.speedStart}
|
||||
length={1}
|
||||
/>
|
||||
<p>{i18n.next[lan]}</p>
|
||||
<Next data={this.props.next} />
|
||||
<div className={style.bottom}>
|
||||
<Music data={this.props.music} />
|
||||
<Pause data={this.props.pause} />
|
||||
<Number time />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Keyboard filling={filling} keyboard={this.props.keyboard} />
|
||||
<Guide />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
App.propTypes = {
|
||||
music: React.PropTypes.bool.isRequired,
|
||||
pause: React.PropTypes.bool.isRequired,
|
||||
matrix: React.PropTypes.object.isRequired,
|
||||
next: React.PropTypes.string.isRequired,
|
||||
cur: React.PropTypes.object,
|
||||
dispatch: React.PropTypes.func.isRequired,
|
||||
speedStart: React.PropTypes.number.isRequired,
|
||||
speedRun: React.PropTypes.number.isRequired,
|
||||
startLines: React.PropTypes.number.isRequired,
|
||||
clearLines: React.PropTypes.number.isRequired,
|
||||
points: React.PropTypes.number.isRequired,
|
||||
max: React.PropTypes.number.isRequired,
|
||||
reset: React.PropTypes.bool.isRequired,
|
||||
drop: React.PropTypes.bool.isRequired,
|
||||
keyboard: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
pause: state.get('pause'),
|
||||
music: state.get('music'),
|
||||
matrix: state.get('matrix'),
|
||||
next: state.get('next'),
|
||||
cur: state.get('cur'),
|
||||
speedStart: state.get('speedStart'),
|
||||
speedRun: state.get('speedRun'),
|
||||
startLines: state.get('startLines'),
|
||||
clearLines: state.get('clearLines'),
|
||||
points: state.get('points'),
|
||||
max: state.get('max'),
|
||||
reset: state.get('reset'),
|
||||
drop: state.get('drop'),
|
||||
keyboard: state.get('keyboard'),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(App);
|
133
src/containers/index.less
Normal file
133
src/containers/index.less
Normal file
@ -0,0 +1,133 @@
|
||||
body{
|
||||
background: #009688;
|
||||
padding: 0;margin:0;
|
||||
font: 20px/1 "HanHei SC","PingHei","PingFang SC","STHeitiSC-Light","Helvetica Neue","Helvetica","Arial",sans-serif;
|
||||
overflow: hidden;
|
||||
cursor: default;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-moz-font-feature-settings: 'liga', 'kern';
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
:global{
|
||||
|
||||
.r{
|
||||
float: right;
|
||||
}
|
||||
.l{
|
||||
float: left;
|
||||
}
|
||||
|
||||
.clear{
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
|
||||
b {
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 2px;
|
||||
border: 2px solid #879372;
|
||||
margin: 0 2px 2px 0;
|
||||
float: left;
|
||||
&:after{
|
||||
content: '';
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #879372;
|
||||
overflow: hidden;
|
||||
}
|
||||
&.c
|
||||
{
|
||||
border-color:#000;
|
||||
&:after{
|
||||
background:#000;
|
||||
}
|
||||
}
|
||||
&.d{
|
||||
border-color:#560000;
|
||||
&:after{
|
||||
background:#560000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bg{
|
||||
background:url('//img.alicdn.com/tps/TB1qq7kNXXXXXacXFXXXXXXXXXX-400-186.png') no-repeat;
|
||||
overflow:hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
*{
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
.app{
|
||||
width: 640px;
|
||||
padding-top: 42px;
|
||||
box-shadow: 0 0 10px #fff inset;
|
||||
border-radius: 20px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -480px 0 0 -320px;
|
||||
background: #efcc19;
|
||||
}
|
||||
|
||||
.rect{
|
||||
width: 480px;
|
||||
padding: 45px 0 35px;
|
||||
border: #000 solid;
|
||||
border-width: 0 10px 10px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
&.drop{ -webkit-transform:translateY(5px);transform:translateY(5px); }
|
||||
|
||||
}
|
||||
|
||||
.screen{
|
||||
width: 390px;
|
||||
height: 478px;
|
||||
border: solid 5px;
|
||||
border-color: #987f0f #fae36c #fae36c #987f0f;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
.panel{
|
||||
width: 380px;
|
||||
height: 468px;
|
||||
margin: 0 auto;
|
||||
background: #9ead86;
|
||||
padding: 8px;
|
||||
border: 2px solid #494536;
|
||||
}
|
||||
}
|
||||
|
||||
.state {
|
||||
width: 108px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 15px;
|
||||
p {
|
||||
line-height: 47px;
|
||||
height: 57px;
|
||||
padding: 10px 0 0;
|
||||
white-space: nowrap;
|
||||
clear: both;
|
||||
}
|
||||
.bottom {
|
||||
position: absolute;
|
||||
width: 114px;
|
||||
top: 426px;
|
||||
left: 0;
|
||||
}
|
||||
}
|
70
src/containers/loader.less
Normal file
70
src/containers/loader.less
Normal file
@ -0,0 +1,70 @@
|
||||
|
||||
.load{
|
||||
|
||||
@-webkit-keyframes loads{
|
||||
0%,80%,100%{
|
||||
box-shadow:0 0 #efcc19;
|
||||
height:4em}
|
||||
40%{
|
||||
box-shadow:0 -2em #efcc19;
|
||||
height:5em
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loads{
|
||||
0%,80%,100%{
|
||||
box-shadow:0 0 #efcc19;
|
||||
height:4em
|
||||
}
|
||||
40%{
|
||||
box-shadow:0 -2em #efcc19;
|
||||
height:5em
|
||||
}
|
||||
}
|
||||
|
||||
width:240px;
|
||||
height:240px;
|
||||
float:left;
|
||||
position:relative;
|
||||
color:#fff;
|
||||
text-align:center;
|
||||
position:absolute;
|
||||
top:50%;
|
||||
left:50%;
|
||||
margin:-120px 0 0 -120px;
|
||||
p{
|
||||
position:absolute;
|
||||
bottom:0;
|
||||
left:-25%;
|
||||
width:150%;
|
||||
white-space:nowrap;
|
||||
display:none;
|
||||
}
|
||||
.loader{
|
||||
&,&:before,&:after{
|
||||
background:#efcc19;
|
||||
-webkit-animation:loads 1s infinite ease-in-out;
|
||||
animation:loads 1s infinite ease-in-out;
|
||||
width:1em;
|
||||
height:4em
|
||||
}
|
||||
&:before,&:after{
|
||||
position:absolute;top:0;content:''
|
||||
}
|
||||
&:before{
|
||||
left:-1.5em;
|
||||
-webkit-animation-delay:-0.32s;
|
||||
animation-delay:-0.32s
|
||||
}
|
||||
text-indent:-9999em;
|
||||
margin:8em auto;
|
||||
position:relative;
|
||||
font-size:11px;
|
||||
-webkit-animation-delay:-0.16s;
|
||||
animation-delay:-0.16s;
|
||||
&:after{
|
||||
left:1.5em
|
||||
}
|
||||
}
|
||||
}
|
||||
|
44
src/control/index.js
Normal file
44
src/control/index.js
Normal file
@ -0,0 +1,44 @@
|
||||
import store from '../store';
|
||||
import todo from './todo';
|
||||
|
||||
const keyboard = {
|
||||
37: 'left',
|
||||
38: 'rotate',
|
||||
39: 'right',
|
||||
40: 'down',
|
||||
32: 'space',
|
||||
83: 's',
|
||||
82: 'r',
|
||||
80: 'p',
|
||||
};
|
||||
|
||||
let keydownActive;
|
||||
|
||||
const boardKeys = Object.keys(keyboard).map(e => parseInt(e, 10));
|
||||
|
||||
const keyDown = (e) => {
|
||||
if (e.metaKey === true || boardKeys.indexOf(e.keyCode) === -1) {
|
||||
return;
|
||||
}
|
||||
const type = keyboard[e.keyCode];
|
||||
if (type === keydownActive) {
|
||||
return;
|
||||
}
|
||||
keydownActive = type;
|
||||
todo[type].down(store);
|
||||
};
|
||||
|
||||
const keyUp = (e) => {
|
||||
if (e.metaKey === true || boardKeys.indexOf(e.keyCode) === -1) {
|
||||
return;
|
||||
}
|
||||
const type = keyboard[e.keyCode];
|
||||
if (type === keydownActive) {
|
||||
keydownActive = '';
|
||||
}
|
||||
todo[type].up(store);
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', keyDown, true);
|
||||
document.addEventListener('keyup', keyUp, true);
|
||||
|
203
src/control/states.js
Normal file
203
src/control/states.js
Normal file
@ -0,0 +1,203 @@
|
||||
import { List } from 'immutable';
|
||||
import store from '../store';
|
||||
import { want, isClear, isOver } from '../unit/';
|
||||
import actions from '../actions';
|
||||
import { speeds, blankLine, blankMatrix, clearPoints, eachLines } from '../unit/const';
|
||||
import { music } from '../unit/music';
|
||||
|
||||
|
||||
const getStartMatrix = (startLines) => { // 生成startLines
|
||||
const getLine = (min, max) => { // 返回标亮个数在min~max之间一行方块, (包含边界)
|
||||
const count = parseInt((((max - min) + 1) * Math.random()) + min, 10);
|
||||
const line = [];
|
||||
for (let i = 0; i < count; i++) { // 插入高亮
|
||||
line.push(1);
|
||||
}
|
||||
for (let i = 0, len = 10 - count; i < len; i++) { // 在随机位置插入灰色
|
||||
const index = parseInt(((line.length + 1) * Math.random()), 10);
|
||||
line.splice(index, 0, 0);
|
||||
}
|
||||
|
||||
return List(line);
|
||||
};
|
||||
let startMatrix = List([]);
|
||||
|
||||
for (let i = 0; i < startLines; i++) {
|
||||
if (i <= 2) { // 0-3
|
||||
startMatrix = startMatrix.push(getLine(5, 8));
|
||||
} else if (i <= 6) { // 4-6
|
||||
startMatrix = startMatrix.push(getLine(4, 9));
|
||||
} else { // 7-9
|
||||
startMatrix = startMatrix.push(getLine(3, 9));
|
||||
}
|
||||
}
|
||||
for (let i = 0, len = 20 - startLines; i < len; i++) { // 插入上部分的灰色
|
||||
startMatrix = startMatrix.unshift(List(blankLine));
|
||||
}
|
||||
return startMatrix;
|
||||
};
|
||||
|
||||
const states = {
|
||||
// 自动下落setTimeout变量
|
||||
fallInterval: null,
|
||||
|
||||
// 游戏开始
|
||||
start: () => {
|
||||
if (music.start) {
|
||||
music.start();
|
||||
}
|
||||
const state = store.getState();
|
||||
states.dispatchPoints(0);
|
||||
store.dispatch(actions.speedRun(state.get('speedStart')));
|
||||
const startLines = state.get('startLines');
|
||||
const startMatrix = getStartMatrix(startLines);
|
||||
store.dispatch(actions.matrix(startMatrix));
|
||||
store.dispatch(actions.moveBlock({ type: state.get('next') }));
|
||||
store.dispatch(actions.nextBlock());
|
||||
states.auto();
|
||||
},
|
||||
|
||||
// 自动下落
|
||||
auto: (timeout) => {
|
||||
const out = (timeout < 0 ? 0 : timeout);
|
||||
let state = store.getState();
|
||||
let cur = state.get('cur');
|
||||
const fall = () => {
|
||||
state = store.getState();
|
||||
cur = state.get('cur');
|
||||
const next = cur.fall();
|
||||
if (want(next, state.get('matrix'))) {
|
||||
store.dispatch(actions.moveBlock(next));
|
||||
states.fallInterval = setTimeout(fall, speeds[state.get('speedRun') - 1]);
|
||||
} else {
|
||||
let matrix = state.get('matrix');
|
||||
const shape = cur && cur.shape;
|
||||
const xy = cur && cur.xy;
|
||||
shape.forEach((m, k1) => (
|
||||
m.forEach((n, k2) => {
|
||||
if (n && xy.get(0) + k1 >= 0) { // 竖坐标可以为负
|
||||
let line = matrix.get(xy.get(0) + k1);
|
||||
line = line.set(xy.get(1) + k2, 1);
|
||||
matrix = matrix.set(xy.get(0) + k1, line);
|
||||
}
|
||||
})
|
||||
));
|
||||
states.nextAround(matrix);
|
||||
}
|
||||
};
|
||||
clearTimeout(states.fallInterval);
|
||||
states.fallInterval = setTimeout(fall,
|
||||
out === undefined ? speeds[state.get('speedRun') - 1] : out);
|
||||
},
|
||||
|
||||
// 一个方块结束, 触发下一个
|
||||
nextAround: (matrix, stopDownTrigger) => {
|
||||
clearTimeout(states.fallInterval);
|
||||
store.dispatch(actions.lock(true));
|
||||
store.dispatch(actions.matrix(matrix));
|
||||
if (typeof stopDownTrigger === 'function') {
|
||||
stopDownTrigger();
|
||||
}
|
||||
|
||||
const addPoints = (store.getState().get('points') + 10) +
|
||||
((store.getState().get('speedRun') - 1) * 2); // 速度越快, 得分越高
|
||||
|
||||
states.dispatchPoints(addPoints);
|
||||
|
||||
if (isClear(matrix)) {
|
||||
if (music.clear) {
|
||||
music.clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isOver(matrix)) {
|
||||
if (music.gameover) {
|
||||
music.gameover();
|
||||
}
|
||||
states.overStart();
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
store.dispatch(actions.lock(false));
|
||||
store.dispatch(actions.moveBlock({ type: store.getState().get('next') }));
|
||||
store.dispatch(actions.nextBlock());
|
||||
states.auto();
|
||||
}, 100);
|
||||
},
|
||||
|
||||
// 页面焦点变换
|
||||
focus: (isFocus) => {
|
||||
store.dispatch(actions.focus(isFocus));
|
||||
if (!isFocus) {
|
||||
clearTimeout(states.fallInterval);
|
||||
return;
|
||||
}
|
||||
const state = store.getState();
|
||||
if (state.get('cur') && !state.get('reset') && !state.get('pause')) {
|
||||
states.auto();
|
||||
}
|
||||
},
|
||||
|
||||
// 暂停
|
||||
pause: (isPause) => {
|
||||
store.dispatch(actions.pause(isPause));
|
||||
if (isPause) {
|
||||
clearTimeout(states.fallInterval);
|
||||
return;
|
||||
}
|
||||
states.auto();
|
||||
},
|
||||
|
||||
// 消除行
|
||||
clearLines: (matrix, lines) => {
|
||||
const state = store.getState();
|
||||
let newMatrix = matrix;
|
||||
lines.forEach(n => {
|
||||
newMatrix = newMatrix.splice(n, 1);
|
||||
newMatrix = newMatrix.unshift(List(blankLine));
|
||||
});
|
||||
store.dispatch(actions.matrix(newMatrix));
|
||||
store.dispatch(actions.moveBlock({ type: state.get('next') }));
|
||||
store.dispatch(actions.nextBlock());
|
||||
states.auto();
|
||||
store.dispatch(actions.lock(false));
|
||||
const clearLines = state.get('clearLines') + lines.length;
|
||||
store.dispatch(actions.clearLines(clearLines)); // 更新消除行
|
||||
|
||||
const addPoints = store.getState().get('points') +
|
||||
clearPoints[lines.length - 1]; // 一次消除的行越多, 加分越多
|
||||
states.dispatchPoints(addPoints);
|
||||
|
||||
const speedAdd = Math.floor(clearLines / eachLines); // 消除行数, 增加对应速度
|
||||
let speedNow = state.get('speedStart') + speedAdd;
|
||||
speedNow = speedNow > 6 ? 6 : speedNow;
|
||||
store.dispatch(actions.speedRun(speedNow));
|
||||
},
|
||||
|
||||
// 游戏结束, 触发动画
|
||||
overStart: () => {
|
||||
clearTimeout(states.fallInterval);
|
||||
store.dispatch(actions.lock(true));
|
||||
store.dispatch(actions.reset(true));
|
||||
store.dispatch(actions.pause(false));
|
||||
},
|
||||
|
||||
// 游戏结束动画完成
|
||||
overEnd: () => {
|
||||
store.dispatch(actions.matrix(blankMatrix));
|
||||
store.dispatch(actions.moveBlock({ reset: true }));
|
||||
store.dispatch(actions.reset(false));
|
||||
store.dispatch(actions.lock(false));
|
||||
store.dispatch(actions.clearLines(0));
|
||||
},
|
||||
|
||||
// 写入分数
|
||||
dispatchPoints: (point) => { // 写入分数, 同时判断是否创造最高分
|
||||
store.dispatch(actions.points(point));
|
||||
if (point > 0 && point > store.getState().get('max')) {
|
||||
store.dispatch(actions.max(point));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default states;
|
87
src/control/todo/down.js
Normal file
87
src/control/todo/down.js
Normal file
@ -0,0 +1,87 @@
|
||||
import { want } from '../../unit/';
|
||||
import event from '../../unit/event';
|
||||
import actions from '../../actions';
|
||||
import states from '../states';
|
||||
import { music } from '../../unit/music';
|
||||
|
||||
const down = (store) => {
|
||||
store.dispatch(actions.keyboard.down(true));
|
||||
if (store.getState().get('cur') !== null) {
|
||||
event.down({
|
||||
key: 'down',
|
||||
begin: 40,
|
||||
interval: 40,
|
||||
callback: (stopDownTrigger) => {
|
||||
const state = store.getState();
|
||||
if (state.get('lock')) {
|
||||
return;
|
||||
}
|
||||
if (music.move) {
|
||||
music.move();
|
||||
}
|
||||
const cur = state.get('cur');
|
||||
if (cur === null) {
|
||||
return;
|
||||
}
|
||||
if (state.get('pause')) {
|
||||
states.pause(false);
|
||||
return;
|
||||
}
|
||||
const next = cur.fall();
|
||||
if (want(next, state.get('matrix'))) {
|
||||
store.dispatch(actions.moveBlock(next));
|
||||
states.auto();
|
||||
} else {
|
||||
let matrix = state.get('matrix');
|
||||
const shape = cur.shape;
|
||||
const xy = cur.xy;
|
||||
shape.forEach((m, k1) => (
|
||||
m.forEach((n, k2) => {
|
||||
if (n && xy.get(0) + k1 >= 0) { // 竖坐标可以为负
|
||||
let line = matrix.get(xy.get(0) + k1);
|
||||
line = line.set(xy.get(1) + k2, 1);
|
||||
matrix = matrix.set(xy.get(0) + k1, line);
|
||||
}
|
||||
})
|
||||
));
|
||||
states.nextAround(matrix, stopDownTrigger);
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
event.down({
|
||||
key: 'down',
|
||||
begin: 200,
|
||||
interval: 100,
|
||||
callback: () => {
|
||||
if (store.getState().get('lock')) {
|
||||
return;
|
||||
}
|
||||
const state = store.getState();
|
||||
const cur = state.get('cur');
|
||||
if (cur) {
|
||||
return;
|
||||
}
|
||||
if (music.move) {
|
||||
music.move();
|
||||
}
|
||||
let startLines = state.get('startLines');
|
||||
startLines = startLines - 1 < 0 ? 10 : startLines - 1;
|
||||
store.dispatch(actions.startLines(startLines));
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const up = (store) => {
|
||||
store.dispatch(actions.keyboard.down(false));
|
||||
event.up({
|
||||
key: 'down',
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export default {
|
||||
down,
|
||||
up,
|
||||
};
|
19
src/control/todo/index.js
Normal file
19
src/control/todo/index.js
Normal file
@ -0,0 +1,19 @@
|
||||
import left from './left';
|
||||
import right from './right';
|
||||
import down from './down';
|
||||
import rotate from './rotate';
|
||||
import space from './space';
|
||||
import s from './s';
|
||||
import r from './r';
|
||||
import p from './p';
|
||||
|
||||
export default {
|
||||
left,
|
||||
down,
|
||||
rotate,
|
||||
right,
|
||||
space,
|
||||
r,
|
||||
p,
|
||||
s,
|
||||
};
|
61
src/control/todo/left.js
Normal file
61
src/control/todo/left.js
Normal file
@ -0,0 +1,61 @@
|
||||
import { want } from '../../unit/';
|
||||
import event from '../../unit/event';
|
||||
import actions from '../../actions';
|
||||
import states from '../states';
|
||||
import { speeds, delays } from '../../unit/const';
|
||||
import { music } from '../../unit/music';
|
||||
|
||||
const down = (store) => {
|
||||
store.dispatch(actions.keyboard.left(true));
|
||||
event.down({
|
||||
key: 'left',
|
||||
begin: 200,
|
||||
interval: 100,
|
||||
callback: () => {
|
||||
const state = store.getState();
|
||||
if (state.get('lock')) {
|
||||
return;
|
||||
}
|
||||
if (music.move) {
|
||||
music.move();
|
||||
}
|
||||
const cur = state.get('cur');
|
||||
if (cur !== null) {
|
||||
if (state.get('pause')) {
|
||||
states.pause(false);
|
||||
return;
|
||||
}
|
||||
const next = cur.left();
|
||||
const delay = delays[state.get('speedRun') - 1];
|
||||
let timeStamp;
|
||||
if (want(next, state.get('matrix'))) {
|
||||
next.timeStamp += parseInt(delay, 10);
|
||||
store.dispatch(actions.moveBlock(next));
|
||||
timeStamp = next.timeStamp;
|
||||
} else {
|
||||
cur.timeStamp += parseInt(parseInt(delay, 10) / 1.5, 10); // 真实移动delay多一点,碰壁delay少一点
|
||||
store.dispatch(actions.moveBlock(cur));
|
||||
timeStamp = cur.timeStamp;
|
||||
}
|
||||
const remain = speeds[state.get('speedRun') - 1] - (Date.now() - timeStamp);
|
||||
states.auto(remain);
|
||||
} else {
|
||||
let speed = state.get('speedStart');
|
||||
speed = speed - 1 < 1 ? 6 : speed - 1;
|
||||
store.dispatch(actions.speedStart(speed));
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const up = (store) => {
|
||||
store.dispatch(actions.keyboard.left(false));
|
||||
event.up({
|
||||
key: 'left',
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
down,
|
||||
up,
|
||||
};
|
37
src/control/todo/p.js
Normal file
37
src/control/todo/p.js
Normal file
@ -0,0 +1,37 @@
|
||||
import event from '../../unit/event';
|
||||
import states from '../states';
|
||||
import actions from '../../actions';
|
||||
|
||||
const down = (store) => {
|
||||
store.dispatch(actions.keyboard.pause(true));
|
||||
event.down({
|
||||
key: 'p',
|
||||
once: true,
|
||||
callback: () => {
|
||||
const state = store.getState();
|
||||
if (state.get('lock')) {
|
||||
return;
|
||||
}
|
||||
const cur = state.get('cur');
|
||||
const isPause = state.get('pause');
|
||||
if (cur !== null) { // 暂停
|
||||
states.pause(!isPause);
|
||||
} else { // 新的开始
|
||||
states.start();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const up = (store) => {
|
||||
store.dispatch(actions.keyboard.pause(false));
|
||||
event.up({
|
||||
key: 'p',
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export default {
|
||||
down,
|
||||
up,
|
||||
};
|
42
src/control/todo/r.js
Normal file
42
src/control/todo/r.js
Normal file
@ -0,0 +1,42 @@
|
||||
import event from '../../unit/event';
|
||||
import states from '../states';
|
||||
import actions from '../../actions';
|
||||
|
||||
const down = (store) => {
|
||||
store.dispatch(actions.keyboard.reset(true));
|
||||
if (store.getState().get('lock')) {
|
||||
return;
|
||||
}
|
||||
if (store.getState().get('cur') !== null) {
|
||||
event.down({
|
||||
key: 'r',
|
||||
once: true,
|
||||
callback: () => {
|
||||
states.overStart();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
event.down({
|
||||
key: 'r',
|
||||
once: true,
|
||||
callback: () => {
|
||||
if (store.getState().get('lock')) {
|
||||
return;
|
||||
}
|
||||
states.start();
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const up = (store) => {
|
||||
store.dispatch(actions.keyboard.reset(false));
|
||||
event.up({
|
||||
key: 'r',
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
down,
|
||||
up,
|
||||
};
|
61
src/control/todo/right.js
Normal file
61
src/control/todo/right.js
Normal file
@ -0,0 +1,61 @@
|
||||
import { want } from '../../unit/';
|
||||
import event from '../../unit/event';
|
||||
import actions from '../../actions';
|
||||
import states from '../states';
|
||||
import { speeds, delays } from '../../unit/const';
|
||||
import { music } from '../../unit/music';
|
||||
|
||||
const down = (store) => {
|
||||
store.dispatch(actions.keyboard.right(true));
|
||||
event.down({
|
||||
key: 'right',
|
||||
begin: 200,
|
||||
interval: 100,
|
||||
callback: () => {
|
||||
const state = store.getState();
|
||||
if (state.get('lock')) {
|
||||
return;
|
||||
}
|
||||
if (music.move) {
|
||||
music.move();
|
||||
}
|
||||
const cur = state.get('cur');
|
||||
if (cur !== null) {
|
||||
if (state.get('pause')) {
|
||||
states.pause(false);
|
||||
return;
|
||||
}
|
||||
const next = cur.right();
|
||||
const delay = delays[state.get('speedRun') - 1];
|
||||
let timeStamp;
|
||||
if (want(next, state.get('matrix'))) {
|
||||
next.timeStamp += parseInt(delay, 10);
|
||||
store.dispatch(actions.moveBlock(next));
|
||||
timeStamp = next.timeStamp;
|
||||
} else {
|
||||
cur.timeStamp += parseInt(parseInt(delay, 10) / 1.5, 10); // 真实移动delay多一点,碰壁delay少一点
|
||||
store.dispatch(actions.moveBlock(cur));
|
||||
timeStamp = cur.timeStamp;
|
||||
}
|
||||
const remain = speeds[state.get('speedRun') - 1] - (Date.now() - timeStamp);
|
||||
states.auto(remain);
|
||||
} else {
|
||||
let speed = state.get('speedStart');
|
||||
speed = speed + 1 > 6 ? 1 : speed + 1;
|
||||
store.dispatch(actions.speedStart(speed));
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const up = (store) => {
|
||||
store.dispatch(actions.keyboard.right(false));
|
||||
event.up({
|
||||
key: 'right',
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
down,
|
||||
up,
|
||||
};
|
69
src/control/todo/rotate.js
Normal file
69
src/control/todo/rotate.js
Normal file
@ -0,0 +1,69 @@
|
||||
import { want } from '../../unit/';
|
||||
import event from '../../unit/event';
|
||||
import actions from '../../actions';
|
||||
import states from '../states';
|
||||
import { music } from '../../unit/music';
|
||||
|
||||
const down = (store) => {
|
||||
store.dispatch(actions.keyboard.rotate(true));
|
||||
if (store.getState().get('cur') !== null) {
|
||||
event.down({
|
||||
key: 'rotate',
|
||||
once: true,
|
||||
callback: () => {
|
||||
const state = store.getState();
|
||||
if (state.get('lock')) {
|
||||
return;
|
||||
}
|
||||
if (state.get('pause')) {
|
||||
states.pause(false);
|
||||
}
|
||||
const cur = state.get('cur');
|
||||
if (cur === null) {
|
||||
return;
|
||||
}
|
||||
if (music.rotate) {
|
||||
music.rotate();
|
||||
}
|
||||
const next = cur.rotate();
|
||||
if (want(next, state.get('matrix'))) {
|
||||
store.dispatch(actions.moveBlock(next));
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
event.down({
|
||||
key: 'rotate',
|
||||
begin: 200,
|
||||
interval: 100,
|
||||
callback: () => {
|
||||
if (store.getState().get('lock')) {
|
||||
return;
|
||||
}
|
||||
if (music.move) {
|
||||
music.move();
|
||||
}
|
||||
const state = store.getState();
|
||||
const cur = state.get('cur');
|
||||
if (cur) {
|
||||
return;
|
||||
}
|
||||
let startLines = state.get('startLines');
|
||||
startLines = startLines + 1 > 10 ? 0 : startLines + 1;
|
||||
store.dispatch(actions.startLines(startLines));
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const up = (store) => {
|
||||
store.dispatch(actions.keyboard.rotate(false));
|
||||
event.up({
|
||||
key: 'rotate',
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
down,
|
||||
up,
|
||||
};
|
32
src/control/todo/s.js
Normal file
32
src/control/todo/s.js
Normal file
@ -0,0 +1,32 @@
|
||||
import event from '../../unit/event';
|
||||
import actions from '../../actions';
|
||||
|
||||
const down = (store) => {
|
||||
store.dispatch(actions.keyboard.music(true));
|
||||
if (store.getState().get('lock')) {
|
||||
return;
|
||||
}
|
||||
event.down({
|
||||
key: 's',
|
||||
once: true,
|
||||
callback: () => {
|
||||
if (store.getState().get('lock')) {
|
||||
return;
|
||||
}
|
||||
store.dispatch(actions.music(!store.getState().get('music')));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const up = (store) => {
|
||||
store.dispatch(actions.keyboard.music(false));
|
||||
event.up({
|
||||
key: 's',
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export default {
|
||||
down,
|
||||
up,
|
||||
};
|
68
src/control/todo/space.js
Normal file
68
src/control/todo/space.js
Normal file
@ -0,0 +1,68 @@
|
||||
import { want } from '../../unit/';
|
||||
import event from '../../unit/event';
|
||||
import actions from '../../actions';
|
||||
import states from '../states';
|
||||
import { music } from '../../unit/music';
|
||||
|
||||
const down = (store) => {
|
||||
store.dispatch(actions.keyboard.drop(true));
|
||||
event.down({
|
||||
key: 'space',
|
||||
once: true,
|
||||
callback: () => {
|
||||
const state = store.getState();
|
||||
if (state.get('lock')) {
|
||||
return;
|
||||
}
|
||||
const cur = state.get('cur');
|
||||
if (cur !== null) { // 置底
|
||||
if (state.get('pause')) {
|
||||
states.pause(false);
|
||||
return;
|
||||
}
|
||||
if (music.fall) {
|
||||
music.fall();
|
||||
}
|
||||
let index = 0;
|
||||
let bottom = cur.fall(index);
|
||||
while (want(bottom, state.get('matrix'))) {
|
||||
bottom = cur.fall(index);
|
||||
index++;
|
||||
}
|
||||
let matrix = state.get('matrix');
|
||||
bottom = cur.fall(index - 2);
|
||||
store.dispatch(actions.moveBlock(bottom));
|
||||
const shape = bottom.shape;
|
||||
const xy = bottom.xy;
|
||||
shape.forEach((m, k1) => (
|
||||
m.forEach((n, k2) => {
|
||||
if (n && xy[0] + k1 >= 0) { // 竖坐标可以为负
|
||||
let line = matrix.get(xy[0] + k1);
|
||||
line = line.set(xy[1] + k2, 1);
|
||||
matrix = matrix.set(xy[0] + k1, line);
|
||||
}
|
||||
})
|
||||
));
|
||||
store.dispatch(actions.drop(true));
|
||||
setTimeout(() => {
|
||||
store.dispatch(actions.drop(false));
|
||||
}, 100);
|
||||
states.nextAround(matrix);
|
||||
} else {
|
||||
states.start();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const up = (store) => {
|
||||
store.dispatch(actions.keyboard.drop(false));
|
||||
event.up({
|
||||
key: 'space',
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
down,
|
||||
up,
|
||||
};
|
18
src/index.js
Normal file
18
src/index.js
Normal file
@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import store from './store';
|
||||
import App from './containers/';
|
||||
import './unit/const';
|
||||
import './control';
|
||||
import { subscribeRecord } from './unit';
|
||||
|
||||
subscribeRecord(store); // 将更新的状态记录到localStorage
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
, document.getElementById('root')
|
||||
);
|
||||
|
19
src/reducers/clearLines/index.js
Normal file
19
src/reducers/clearLines/index.js
Normal file
@ -0,0 +1,19 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { lastRecord } from '../../unit/const';
|
||||
|
||||
let initState = lastRecord && !isNaN(parseInt(lastRecord.clearLines, 10)) ?
|
||||
parseInt(lastRecord.clearLines, 10) : 0;
|
||||
if (initState < 0) {
|
||||
initState = 0;
|
||||
}
|
||||
|
||||
const clearLines = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.CLEAR_LINES:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default clearLines;
|
29
src/reducers/cur/index.js
Normal file
29
src/reducers/cur/index.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { List } from 'immutable';
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { lastRecord } from '../../unit/const';
|
||||
import Block from '../../unit/block';
|
||||
|
||||
const initState = (() => {
|
||||
if (!lastRecord || !lastRecord.cur) { // 无记录 或 有记录 但方块为空, 返回 null
|
||||
return null;
|
||||
}
|
||||
const cur = lastRecord.cur;
|
||||
const option = {
|
||||
type: cur.type,
|
||||
rotateIndex: cur.rotateIndex,
|
||||
shape: List(cur.shape.map(e => List(e))),
|
||||
xy: cur.xy,
|
||||
};
|
||||
return new Block(option);
|
||||
})();
|
||||
|
||||
const cur = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.MOVE_BLOCK:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default cur;
|
15
src/reducers/drop/index.js
Normal file
15
src/reducers/drop/index.js
Normal file
@ -0,0 +1,15 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { lastRecord } from '../../unit/const';
|
||||
|
||||
const initState = lastRecord && lastRecord.drop !== undefined ? !!lastRecord.drop : false;
|
||||
|
||||
const drop = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.DROP:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default drop;
|
14
src/reducers/focus/index.js
Normal file
14
src/reducers/focus/index.js
Normal file
@ -0,0 +1,14 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { isFocus } from '../../unit/';
|
||||
|
||||
const initState = isFocus();
|
||||
const focus = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.FOCUS:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default focus;
|
39
src/reducers/index.js
Normal file
39
src/reducers/index.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { combineReducers } from 'redux-immutable';
|
||||
import pause from './pause';
|
||||
import music from './music';
|
||||
import matrix from './matrix';
|
||||
import next from './next';
|
||||
import cur from './cur';
|
||||
import startLines from './startLines';
|
||||
import max from './max';
|
||||
import points from './points';
|
||||
import speedStart from './speedStart';
|
||||
import speedRun from './speedRun';
|
||||
import lock from './lock';
|
||||
import clearLines from './clearLines';
|
||||
import reset from './reset';
|
||||
import drop from './drop';
|
||||
import keyboard from './keyboard';
|
||||
import focus from './focus';
|
||||
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
pause,
|
||||
music,
|
||||
matrix,
|
||||
next,
|
||||
cur,
|
||||
startLines,
|
||||
max,
|
||||
points,
|
||||
speedStart,
|
||||
speedRun,
|
||||
lock,
|
||||
clearLines,
|
||||
reset,
|
||||
drop,
|
||||
keyboard,
|
||||
focus,
|
||||
});
|
||||
|
||||
export default rootReducer;
|
14
src/reducers/keyboard/down.js
Normal file
14
src/reducers/keyboard/down.js
Normal file
@ -0,0 +1,14 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
|
||||
const initState = false;
|
||||
|
||||
const reducer = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.KEY_DOWN:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
14
src/reducers/keyboard/drop.js
Normal file
14
src/reducers/keyboard/drop.js
Normal file
@ -0,0 +1,14 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
|
||||
const initState = false;
|
||||
|
||||
const reducer = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.KEY_DROP:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
22
src/reducers/keyboard/index.js
Normal file
22
src/reducers/keyboard/index.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { combineReducers } from 'redux-immutable';
|
||||
import drop from './drop';
|
||||
import down from './down';
|
||||
import left from './left';
|
||||
import right from './right';
|
||||
import rotate from './rotate';
|
||||
import reset from './reset';
|
||||
import music from './music';
|
||||
import pause from './pause';
|
||||
|
||||
const keyboardReducer = combineReducers({
|
||||
drop,
|
||||
down,
|
||||
left,
|
||||
right,
|
||||
rotate,
|
||||
reset,
|
||||
music,
|
||||
pause,
|
||||
});
|
||||
|
||||
export default keyboardReducer;
|
14
src/reducers/keyboard/left.js
Normal file
14
src/reducers/keyboard/left.js
Normal file
@ -0,0 +1,14 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
|
||||
const initState = false;
|
||||
|
||||
const reducer = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.KEY_LEFT:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
14
src/reducers/keyboard/music.js
Normal file
14
src/reducers/keyboard/music.js
Normal file
@ -0,0 +1,14 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
|
||||
const initState = false;
|
||||
|
||||
const reducer = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.KEY_MUSIC:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
14
src/reducers/keyboard/pause.js
Normal file
14
src/reducers/keyboard/pause.js
Normal file
@ -0,0 +1,14 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
|
||||
const initState = false;
|
||||
|
||||
const reducer = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.KEY_PAUSE:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
14
src/reducers/keyboard/reset.js
Normal file
14
src/reducers/keyboard/reset.js
Normal file
@ -0,0 +1,14 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
|
||||
const initState = false;
|
||||
|
||||
const reducer = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.KEY_RESET:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
14
src/reducers/keyboard/right.js
Normal file
14
src/reducers/keyboard/right.js
Normal file
@ -0,0 +1,14 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
|
||||
const initState = false;
|
||||
|
||||
const reducer = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.KEY_RIGHT:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
14
src/reducers/keyboard/rotate.js
Normal file
14
src/reducers/keyboard/rotate.js
Normal file
@ -0,0 +1,14 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
|
||||
const initState = false;
|
||||
|
||||
const reducer = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.KEY_ROTATE:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
15
src/reducers/lock/index.js
Normal file
15
src/reducers/lock/index.js
Normal file
@ -0,0 +1,15 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { lastRecord } from '../../unit/const';
|
||||
|
||||
const initState = lastRecord && lastRecord.lock !== undefined ? !!lastRecord.lock : false;
|
||||
|
||||
const lock = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.LOCK:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default lock;
|
17
src/reducers/matrix/index.js
Normal file
17
src/reducers/matrix/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { List } from 'immutable';
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { blankMatrix, lastRecord } from '../../unit/const';
|
||||
|
||||
const initState = lastRecord && Array.isArray(lastRecord.matrix) ?
|
||||
List(lastRecord.matrix.map(e => List(e))) : blankMatrix;
|
||||
|
||||
const matrix = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.MATRIX:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default matrix;
|
22
src/reducers/max/index.js
Normal file
22
src/reducers/max/index.js
Normal file
@ -0,0 +1,22 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { lastRecord, maxPoint } from '../../unit/const';
|
||||
|
||||
let initState = lastRecord && !isNaN(parseInt(lastRecord.max, 10)) ?
|
||||
parseInt(lastRecord.max, 10) : 0;
|
||||
|
||||
if (initState < 0) {
|
||||
initState = 0;
|
||||
} else if (initState > maxPoint) {
|
||||
initState = maxPoint;
|
||||
}
|
||||
|
||||
const parse = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.MAX:
|
||||
return action.data > 999999 ? 999999 : action.data; // 最大分数
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default parse;
|
21
src/reducers/music/index.js
Normal file
21
src/reducers/music/index.js
Normal file
@ -0,0 +1,21 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { lastRecord } from '../../unit/const';
|
||||
import { hasWebAudioAPI } from '../../unit/music';
|
||||
|
||||
let initState = lastRecord && lastRecord.music !== undefined ? !!lastRecord.music : true;
|
||||
if (!hasWebAudioAPI.data) {
|
||||
initState = false;
|
||||
}
|
||||
const music = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.MUSIC:
|
||||
if (!hasWebAudioAPI.data) { // 若浏览器不支持 WebAudioApi, 将无法播放音效
|
||||
return false;
|
||||
}
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default music;
|
16
src/reducers/next/index.js
Normal file
16
src/reducers/next/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { getNextType } from '../../unit';
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { lastRecord, blockType } from '../../unit/const';
|
||||
|
||||
const initState = lastRecord && blockType.indexOf(lastRecord.next) !== -1 ?
|
||||
lastRecord.next : getNextType();
|
||||
const parse = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.NEXT_BLOCK:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default parse;
|
14
src/reducers/pause/index.js
Normal file
14
src/reducers/pause/index.js
Normal file
@ -0,0 +1,14 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { lastRecord } from '../../unit/const';
|
||||
|
||||
const initState = lastRecord && lastRecord.pause !== undefined ? !!lastRecord.pause : false;
|
||||
const pause = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.PAUSE:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default pause;
|
22
src/reducers/points/index.js
Normal file
22
src/reducers/points/index.js
Normal file
@ -0,0 +1,22 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { lastRecord, maxPoint } from '../../unit/const';
|
||||
|
||||
let initState = lastRecord && !isNaN(parseInt(lastRecord.points, 10)) ?
|
||||
parseInt(lastRecord.points, 10) : 0;
|
||||
|
||||
if (initState < 0) {
|
||||
initState = 0;
|
||||
} else if (initState > maxPoint) {
|
||||
initState = maxPoint;
|
||||
}
|
||||
|
||||
const points = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.POINTS:
|
||||
return action.data > maxPoint ? maxPoint : action.data; // 最大分数
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default points;
|
14
src/reducers/reset/index.js
Normal file
14
src/reducers/reset/index.js
Normal file
@ -0,0 +1,14 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { lastRecord } from '../../unit/const';
|
||||
|
||||
const initState = lastRecord && lastRecord.reset ? !!lastRecord.reset : false;
|
||||
const reset = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.RESET:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reset;
|
19
src/reducers/speedRun/index.js
Normal file
19
src/reducers/speedRun/index.js
Normal file
@ -0,0 +1,19 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { lastRecord } from '../../unit/const';
|
||||
|
||||
let initState = lastRecord && !isNaN(parseInt(lastRecord.speedRun, 10)) ?
|
||||
parseInt(lastRecord.speedRun, 10) : 1;
|
||||
if (initState < 1 || initState > 6) {
|
||||
initState = 1;
|
||||
}
|
||||
|
||||
const speedRun = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.SPEED_RUN:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default speedRun;
|
19
src/reducers/speedStart/index.js
Normal file
19
src/reducers/speedStart/index.js
Normal file
@ -0,0 +1,19 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { lastRecord } from '../../unit/const';
|
||||
|
||||
let initState = lastRecord && !isNaN(parseInt(lastRecord.speedStart, 10)) ?
|
||||
parseInt(lastRecord.speedStart, 10) : 1;
|
||||
if (initState < 1 || initState > 6) {
|
||||
initState = 1;
|
||||
}
|
||||
|
||||
const speedStart = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.SPEED_START:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default speedStart;
|
19
src/reducers/startLines/index.js
Normal file
19
src/reducers/startLines/index.js
Normal file
@ -0,0 +1,19 @@
|
||||
import * as reducerType from '../../unit/reducerType';
|
||||
import { lastRecord } from '../../unit/const';
|
||||
|
||||
let initState = lastRecord && !isNaN(parseInt(lastRecord.startLines, 10)) ?
|
||||
parseInt(lastRecord.startLines, 10) : 0;
|
||||
if (initState < 0 || initState > 10) {
|
||||
initState = 0;
|
||||
}
|
||||
|
||||
const startLines = (state = initState, action) => {
|
||||
switch (action.type) {
|
||||
case reducerType.START_LINES:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default startLines;
|
84
src/resource/css/loader.css
Normal file
84
src/resource/css/loader.css
Normal file
@ -0,0 +1,84 @@
|
||||
body{
|
||||
background: #009688;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.load{
|
||||
width:240px;
|
||||
height:240px;
|
||||
position:absolute;
|
||||
top:50%;
|
||||
left:50%;
|
||||
margin:-120px 0 0 -120px;
|
||||
color:#efcc19;
|
||||
-webkit-animation:fadeIn 2s infinite ease-in-out;
|
||||
animation:fadeIn 2s infinite ease-in-out;
|
||||
-webkit-animation-delay:2s;
|
||||
animation-delay:2s;
|
||||
opacity:0;
|
||||
}
|
||||
.load .loader,.load .loader:before,.load .loader:after{
|
||||
background:#efcc19;
|
||||
-webkit-animation:load 1s infinite ease-in-out;
|
||||
animation:load 1s infinite ease-in-out;
|
||||
width:1em;
|
||||
height:4em
|
||||
}
|
||||
.load .loader:before,.load .loader:after{
|
||||
position:absolute;
|
||||
top:0;
|
||||
content:''
|
||||
}
|
||||
.load .loader:before{
|
||||
left:-1.5em;
|
||||
-webkit-animation-delay:-0.32s;
|
||||
animation-delay:-0.32s
|
||||
}
|
||||
.load .loader{
|
||||
text-indent:-9999em;
|
||||
margin:8em auto;
|
||||
position:relative;
|
||||
font-size:11px;
|
||||
-webkit-animation-delay:-0.16s;
|
||||
animation-delay:-0.16s
|
||||
}
|
||||
.load .loader:after{
|
||||
left:1.5em
|
||||
}
|
||||
@-webkit-keyframes load{
|
||||
0%,80%,100%{
|
||||
box-shadow:0 0 #efcc19;
|
||||
height:4em
|
||||
}
|
||||
40%{
|
||||
box-shadow:0 -2em #efcc19;height:5em
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load{
|
||||
0%,80%,100%{
|
||||
box-shadow:0 0 #efcc19;
|
||||
height:4em
|
||||
}
|
||||
40%{
|
||||
box-shadow:0 -2em #efcc19;
|
||||
height:5em
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeIn{
|
||||
0%{
|
||||
opacity:0;
|
||||
}
|
||||
100%{
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
@keyframes fadeIn{
|
||||
0%{
|
||||
opacity:0;
|
||||
}
|
||||
100%{
|
||||
opacity:1;
|
||||
}
|
||||
}
|
BIN
src/resource/image/icon.png
Normal file
BIN
src/resource/image/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
BIN
src/resource/image/share.png
Executable file
BIN
src/resource/image/share.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
BIN
src/resource/music/music.mp3
Normal file
BIN
src/resource/music/music.mp3
Normal file
Binary file not shown.
BIN
src/resource/music/music.wav
Normal file
BIN
src/resource/music/music.wav
Normal file
Binary file not shown.
6
src/store/index.js
Normal file
6
src/store/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { createStore } from 'redux';
|
||||
import rootReducer from '../reducers';
|
||||
|
||||
const store = createStore(rootReducer, window.devToolsExtension && window.devToolsExtension());
|
||||
|
||||
export default store;
|
109
src/unit/block.js
Normal file
109
src/unit/block.js
Normal file
@ -0,0 +1,109 @@
|
||||
import { List } from 'immutable';
|
||||
import { blockShape, origin } from './const';
|
||||
|
||||
class Block {
|
||||
constructor(option) {
|
||||
this.type = option.type;
|
||||
|
||||
if (!option.rotateIndex) {
|
||||
this.rotateIndex = 0;
|
||||
} else {
|
||||
this.rotateIndex = option.rotateIndex;
|
||||
}
|
||||
|
||||
if (!option.timeStamp) {
|
||||
this.timeStamp = Date.now();
|
||||
} else {
|
||||
this.timeStamp = option.timeStamp;
|
||||
}
|
||||
|
||||
if (!option.shape) { // init
|
||||
this.shape = List(blockShape[option.type].map(e => List(e)));
|
||||
} else {
|
||||
this.shape = option.shape;
|
||||
}
|
||||
if (!option.xy) {
|
||||
switch (option.type) {
|
||||
case 'I': // I
|
||||
this.xy = List([0, 3]);
|
||||
break;
|
||||
case 'L': // L
|
||||
this.xy = List([-1, 4]);
|
||||
break;
|
||||
case 'J': // J
|
||||
this.xy = List([-1, 4]);
|
||||
break;
|
||||
case 'Z': // Z
|
||||
this.xy = List([-1, 4]);
|
||||
break;
|
||||
case 'S': // S
|
||||
this.xy = List([-1, 4]);
|
||||
break;
|
||||
case 'O': // O
|
||||
this.xy = List([-1, 4]);
|
||||
break;
|
||||
case 'T': // T
|
||||
this.xy = List([-1, 4]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this.xy = List(option.xy);
|
||||
}
|
||||
}
|
||||
rotate() {
|
||||
const shape = this.shape;
|
||||
let result = List([]);
|
||||
shape.forEach(m => m.forEach((n, k) => {
|
||||
const index = m.size - k - 1;
|
||||
if (result.get(index) === undefined) {
|
||||
result = result.set(index, List([]));
|
||||
}
|
||||
const tempK = result.get(index).push(n);
|
||||
result = result.set(index, tempK);
|
||||
}));
|
||||
const nextXy = [
|
||||
this.xy.get(0) + origin[this.type][this.rotateIndex][0],
|
||||
this.xy.get(1) + origin[this.type][this.rotateIndex][1],
|
||||
];
|
||||
const nextRotateIndex = this.rotateIndex + 1 >= origin[this.type].length ?
|
||||
0 : this.rotateIndex + 1;
|
||||
return {
|
||||
shape: result,
|
||||
type: this.type,
|
||||
xy: nextXy,
|
||||
rotateIndex: nextRotateIndex,
|
||||
timeStamp: this.timeStamp,
|
||||
};
|
||||
}
|
||||
fall(n = 1) {
|
||||
return {
|
||||
shape: this.shape,
|
||||
type: this.type,
|
||||
xy: [this.xy.get(0) + n, this.xy.get(1)],
|
||||
rotateIndex: this.rotateIndex,
|
||||
timeStamp: Date.now(),
|
||||
};
|
||||
}
|
||||
right() {
|
||||
return {
|
||||
shape: this.shape,
|
||||
type: this.type,
|
||||
xy: [this.xy.get(0), this.xy.get(1) + 1],
|
||||
rotateIndex: this.rotateIndex,
|
||||
timeStamp: this.timeStamp,
|
||||
};
|
||||
}
|
||||
left() {
|
||||
return {
|
||||
shape: this.shape,
|
||||
type: this.type,
|
||||
xy: [this.xy.get(0), this.xy.get(1) - 1],
|
||||
rotateIndex: this.rotateIndex,
|
||||
timeStamp: this.timeStamp,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default Block;
|
125
src/unit/const.js
Normal file
125
src/unit/const.js
Normal file
@ -0,0 +1,125 @@
|
||||
import { List } from 'immutable';
|
||||
import i18n from '../../i18n.json';
|
||||
|
||||
const blockShape = {
|
||||
I: [
|
||||
[1, 1, 1, 1],
|
||||
],
|
||||
L: [
|
||||
[0, 0, 1],
|
||||
[1, 1, 1],
|
||||
],
|
||||
J: [
|
||||
[1, 0, 0],
|
||||
[1, 1, 1],
|
||||
],
|
||||
Z: [
|
||||
[1, 1, 0],
|
||||
[0, 1, 1],
|
||||
],
|
||||
S: [
|
||||
[0, 1, 1],
|
||||
[1, 1, 0],
|
||||
],
|
||||
O: [
|
||||
[1, 1],
|
||||
[1, 1],
|
||||
],
|
||||
T: [
|
||||
[0, 1, 0],
|
||||
[1, 1, 1],
|
||||
],
|
||||
};
|
||||
|
||||
const origin = {
|
||||
I: [[-1, 1], [1, -1]],
|
||||
L: [[0, 0]],
|
||||
J: [[0, 0]],
|
||||
Z: [[0, 0]],
|
||||
S: [[0, 0]],
|
||||
O: [[0, 0]],
|
||||
T: [[0, 0], [1, 0], [-1, 1], [0, -1]],
|
||||
};
|
||||
|
||||
const blockType = Object.keys(blockShape);
|
||||
|
||||
const speeds = [800, 650, 500, 370, 250, 160];
|
||||
|
||||
const delays = [50, 60, 70, 80, 90, 100];
|
||||
|
||||
const fillLine = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
|
||||
|
||||
const blankLine = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
const blankMatrix = (() => {
|
||||
const matrix = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
matrix.push(List(blankLine));
|
||||
}
|
||||
return List(matrix);
|
||||
})();
|
||||
|
||||
const clearPoints = [100, 300, 700, 1500];
|
||||
|
||||
const StorageKey = 'REACT_TETRIS';
|
||||
|
||||
const lastRecord = (() => { // 上一把的状态
|
||||
let data = localStorage.getItem(StorageKey);
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (window.btoa) {
|
||||
data = atob(data);
|
||||
}
|
||||
data = decodeURIComponent(data);
|
||||
data = JSON.parse(data);
|
||||
} catch (e) {
|
||||
if (window.console || window.console.error) {
|
||||
window.console.error('读取记录错误:', e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return data;
|
||||
})();
|
||||
|
||||
const maxPoint = 999999;
|
||||
|
||||
const transform = (function () {
|
||||
const trans = ['transform', 'webkitTransform', 'msTransform', 'mozTransform', 'oTransform'];
|
||||
const body = document.body;
|
||||
return trans.filter((e) => body.style[e] !== undefined)[0];
|
||||
}());
|
||||
|
||||
const eachLines = 20; // 每消除eachLines行, 增加速度
|
||||
|
||||
const getParam = (param) => { // 获取浏览器参数
|
||||
const r = new RegExp(`\\?(?:.+&)?${param}=(.*?)(?:&.*)?$`);
|
||||
const m = window.location.toString().match(r);
|
||||
return m ? decodeURI(m[1]) : '';
|
||||
};
|
||||
|
||||
const lan = (() => {
|
||||
let l = getParam('lan').toLowerCase();
|
||||
l = i18n.lan.indexOf(l) === -1 ? i18n.default : l;
|
||||
return l;
|
||||
})();
|
||||
|
||||
module.exports = {
|
||||
blockShape,
|
||||
origin,
|
||||
blockType,
|
||||
speeds,
|
||||
delays,
|
||||
fillLine,
|
||||
blankLine,
|
||||
blankMatrix,
|
||||
clearPoints,
|
||||
StorageKey,
|
||||
lastRecord,
|
||||
maxPoint,
|
||||
eachLines,
|
||||
transform,
|
||||
lan,
|
||||
i18n: i18n.data,
|
||||
};
|
52
src/unit/event.js
Normal file
52
src/unit/event.js
Normal file
@ -0,0 +1,52 @@
|
||||
const eventName = {};
|
||||
|
||||
const down = (o) => { // 键盘、手指按下
|
||||
const keys = Object.keys(eventName);
|
||||
keys.forEach(i => {
|
||||
clearTimeout(eventName[i]);
|
||||
eventName[i] = null;
|
||||
});
|
||||
if (!o.callback) {
|
||||
return;
|
||||
}
|
||||
const clear = () => {
|
||||
clearTimeout(eventName[o.key]);
|
||||
};
|
||||
o.callback(clear);
|
||||
if (o.once === true) {
|
||||
return;
|
||||
}
|
||||
let begin = o.begin || 100;
|
||||
const interval = o.interval || 50;
|
||||
const loop = () => {
|
||||
eventName[o.key] = setTimeout(() => {
|
||||
begin = null;
|
||||
loop();
|
||||
o.callback(clear);
|
||||
}, begin || interval);
|
||||
};
|
||||
loop();
|
||||
};
|
||||
|
||||
const up = (o) => { // 键盘、手指松开
|
||||
clearTimeout(eventName[o.key]);
|
||||
eventName[o.key] = null;
|
||||
if (!o.callback) {
|
||||
return;
|
||||
}
|
||||
o.callback();
|
||||
};
|
||||
|
||||
const clearAll = () => {
|
||||
const keys = Object.keys(eventName);
|
||||
keys.forEach(i => {
|
||||
clearTimeout(eventName[i]);
|
||||
eventName[i] = null;
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
down,
|
||||
up,
|
||||
clearAll,
|
||||
};
|
103
src/unit/index.js
Normal file
103
src/unit/index.js
Normal file
@ -0,0 +1,103 @@
|
||||
import { blockType, StorageKey } from './const';
|
||||
|
||||
const hiddenProperty = (() => { // document[hiddenProperty] 可以判断页面是否失焦
|
||||
let names = [
|
||||
'hidden',
|
||||
'webkitHidden',
|
||||
'mozHidden',
|
||||
'msHidden',
|
||||
];
|
||||
names = names.filter((e) => (e in document));
|
||||
return names.length > 0 ? names[0] : false;
|
||||
})();
|
||||
|
||||
const visibilityChangeEvent = (() => {
|
||||
if (!hiddenProperty) {
|
||||
return false;
|
||||
}
|
||||
return hiddenProperty.replace(/hidden/i, 'visibilitychange'); // 如果属性有前缀, 相应的事件也有前缀
|
||||
})();
|
||||
|
||||
const isFocus = () => {
|
||||
if (!hiddenProperty) { // 如果不存在该特性, 认为一直聚焦
|
||||
return true;
|
||||
}
|
||||
return !document[hiddenProperty];
|
||||
};
|
||||
|
||||
const unit = {
|
||||
getNextType() { // 随机获取下一个方块类型
|
||||
const len = blockType.length;
|
||||
return blockType[Math.floor(Math.random() * len)];
|
||||
},
|
||||
want(next, matrix) { // 方块是否能移到到指定位置
|
||||
const xy = next.xy;
|
||||
const shape = next.shape;
|
||||
const horizontal = shape.get(0).size;
|
||||
return shape.every((m, k1) => (
|
||||
m.every((n, k2) => {
|
||||
if (xy[1] < 0) { // left
|
||||
return false;
|
||||
}
|
||||
if (xy[1] + horizontal > 10) { // right
|
||||
return false;
|
||||
}
|
||||
if (xy[0] + k1 < 0) { // top
|
||||
return true;
|
||||
}
|
||||
if (xy[0] + k1 >= 20) { // bottom
|
||||
return false;
|
||||
}
|
||||
if (n) {
|
||||
if (matrix.get(xy[0] + k1).get(xy[1] + k2)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
));
|
||||
},
|
||||
isClear(matrix) { // 是否达到消除状态
|
||||
const clearLines = [];
|
||||
matrix.forEach((m, k) => {
|
||||
if (m.every(n => !!n)) {
|
||||
clearLines.push(k);
|
||||
}
|
||||
});
|
||||
if (clearLines.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return clearLines;
|
||||
},
|
||||
isOver(matrix) { // 游戏是否结束, 第一行落下方块为依据
|
||||
return matrix.get(0).some(n => !!n);
|
||||
},
|
||||
subscribeRecord(store) { // 将状态记录到 localStorage
|
||||
store.subscribe(() => {
|
||||
let data = store.getState().toJS();
|
||||
if (data.lock) { // 当状态为锁定, 不记录
|
||||
return;
|
||||
}
|
||||
data = JSON.stringify(data);
|
||||
data = encodeURIComponent(data);
|
||||
if (window.btoa) {
|
||||
data = btoa(data);
|
||||
}
|
||||
localStorage.setItem(StorageKey, data);
|
||||
});
|
||||
},
|
||||
isMobile() { // 判断是否为移动端
|
||||
const ua = navigator.userAgent;
|
||||
const android = /Android (\d+\.\d+)/.test(ua);
|
||||
const iphone = ua.indexOf('iPhone') > -1;
|
||||
const ipod = ua.indexOf('iPod') > -1;
|
||||
const ipad = ua.indexOf('iPad') > -1;
|
||||
const nokiaN = ua.indexOf('NokiaN') > -1;
|
||||
return android || iphone || ipod || ipad || nokiaN;
|
||||
},
|
||||
visibilityChangeEvent,
|
||||
isFocus,
|
||||
};
|
||||
|
||||
module.exports = unit;
|
100
src/unit/music.js
Normal file
100
src/unit/music.js
Normal file
@ -0,0 +1,100 @@
|
||||
import store from '../store';
|
||||
|
||||
// 使用 Web Audio API
|
||||
const AudioContext = (
|
||||
window.AudioContext ||
|
||||
window.webkitAudioContext ||
|
||||
window.mozAudioContext ||
|
||||
window.oAudioContext ||
|
||||
window.msAudioContext
|
||||
);
|
||||
|
||||
const hasWebAudioAPI = {
|
||||
data: !!AudioContext && location.protocol.indexOf('http') !== -1,
|
||||
};
|
||||
|
||||
|
||||
const music = {};
|
||||
|
||||
(() => {
|
||||
if (!hasWebAudioAPI.data) {
|
||||
return;
|
||||
}
|
||||
const url = './music.mp3';
|
||||
const context = new AudioContext();
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('GET', url, true);
|
||||
req.responseType = 'arraybuffer';
|
||||
|
||||
req.onload = () => {
|
||||
context.decodeAudioData(req.response, (buf) => { // 将拿到的audio解码转为buffer
|
||||
const getSource = () => { // 创建source源。
|
||||
const source = context.createBufferSource();
|
||||
source.buffer = buf;
|
||||
source.connect(context.destination);
|
||||
return source;
|
||||
};
|
||||
|
||||
music.killStart = () => { // 游戏开始的音乐只播放一次
|
||||
music.start = () => {};
|
||||
};
|
||||
|
||||
music.start = () => { // 游戏开始
|
||||
music.killStart();
|
||||
if (!store.getState().get('music')) {
|
||||
return;
|
||||
}
|
||||
getSource().start(0, 3.7202, 3.6224);
|
||||
};
|
||||
|
||||
music.clear = () => { // 消除方块
|
||||
if (!store.getState().get('music')) {
|
||||
return;
|
||||
}
|
||||
getSource().start(0, 0, 0.7675);
|
||||
};
|
||||
|
||||
music.fall = () => { // 立即下落
|
||||
if (!store.getState().get('music')) {
|
||||
return;
|
||||
}
|
||||
getSource().start(0, 1.2558, 0.3546);
|
||||
};
|
||||
|
||||
music.gameover = () => { // 游戏结束
|
||||
if (!store.getState().get('music')) {
|
||||
return;
|
||||
}
|
||||
getSource().start(0, 8.1276, 1.1437);
|
||||
};
|
||||
|
||||
music.rotate = () => { // 旋转
|
||||
if (!store.getState().get('music')) {
|
||||
return;
|
||||
}
|
||||
getSource().start(0, 2.2471, 0.0807);
|
||||
};
|
||||
|
||||
music.move = () => { // 移动
|
||||
if (!store.getState().get('music')) {
|
||||
return;
|
||||
}
|
||||
getSource().start(0, 2.9088, 0.1437);
|
||||
};
|
||||
},
|
||||
(error) => {
|
||||
if (window.console && window.console.error) {
|
||||
window.console.error(`音频: ${url} 读取错误`, error);
|
||||
hasWebAudioAPI.data = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
req.send();
|
||||
})();
|
||||
|
||||
module.exports = {
|
||||
hasWebAudioAPI,
|
||||
music,
|
||||
};
|
||||
|
23
src/unit/reducerType.js
Normal file
23
src/unit/reducerType.js
Normal file
@ -0,0 +1,23 @@
|
||||
export const PAUSE = 'PAUSE';
|
||||
export const MUSIC = 'MUSIC';
|
||||
export const MATRIX = 'MATRIX';
|
||||
export const NEXT_BLOCK = 'NEXT_BLOCK';
|
||||
export const MOVE_BLOCK = 'MOVE_BLOCK';
|
||||
export const START_LINES = 'START_LINES';
|
||||
export const MAX = 'MAX';
|
||||
export const POINTS = 'POINTS';
|
||||
export const SPEED_START = 'SPEED_START';
|
||||
export const SPEED_RUN = 'SPEED_RUN';
|
||||
export const LOCK = 'LOCK';
|
||||
export const CLEAR_LINES = 'CLEAR_LINES';
|
||||
export const RESET = 'RESET';
|
||||
export const DROP = 'DROP';
|
||||
export const KEY_DROP = 'KEY_DROP';
|
||||
export const KEY_DOWN = 'KEY_DOWN';
|
||||
export const KEY_LEFT = 'KEY_LEFT';
|
||||
export const KEY_RIGHT = 'KEY_RIGHT';
|
||||
export const KEY_ROTATE = 'KEY_ROTATE';
|
||||
export const KEY_RESET = 'KEY_RESET';
|
||||
export const KEY_MUSIC = 'KEY_MUSIC';
|
||||
export const KEY_PAUSE = 'KEY_PAUSE';
|
||||
export const FOCUS = 'FOCUS';
|
58
webpack.config.js
Normal file
58
webpack.config.js
Normal file
@ -0,0 +1,58 @@
|
||||
var CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
var webpack = require('webpack');
|
||||
|
||||
// dev环境配置
|
||||
module.exports = {
|
||||
devtool: 'eval-source-map', // 生成source-map追踪js错误
|
||||
entry: __dirname + '/src/index.js', // 程序入口
|
||||
output: {
|
||||
path: __dirname + '/server',
|
||||
filename: 'app.js',
|
||||
},
|
||||
eslint: {
|
||||
configFile: __dirname + '/.eslintrc.js',
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.(json)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'json',
|
||||
},
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel!eslint-loader',
|
||||
},
|
||||
{
|
||||
test: /\.(?:png|jpg|gif)$/,
|
||||
loader: 'url',
|
||||
},
|
||||
{
|
||||
test: /\.css/,
|
||||
loader: 'style!css?localIdentName=[local]-[hash:base64:5]',
|
||||
},
|
||||
{
|
||||
test: /\.less/,
|
||||
loader: 'style!css?modules&localIdentName=[local]-[hash:base64:5]!less',
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin([
|
||||
{ from: './src/resource/music/music.mp3' },
|
||||
{ from: './src/resource/css/loader.css' },
|
||||
]),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
],
|
||||
devServer: {
|
||||
contentBase: './server',
|
||||
colors: true,
|
||||
historyApiFallback: false,
|
||||
port: 8080, // defaults to "8080"
|
||||
hot: true, // Hot Module Replacement
|
||||
inline: true, // Livereload
|
||||
host: '0.0.0.0',
|
||||
},
|
||||
};
|
55
webpack.production.config.js
Normal file
55
webpack.production.config.js
Normal file
@ -0,0 +1,55 @@
|
||||
var webpack = require('webpack');
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
var ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
var CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
var version = require('./package.json').version;
|
||||
|
||||
// production环境配置
|
||||
module.exports = {
|
||||
devtool: 'source-map',// 生成source-map追踪js错误
|
||||
entry: __dirname + '/src/index.js',// 程序入口
|
||||
output: {
|
||||
path: __dirname + '/build',
|
||||
filename: 'app-'+version+'.js',
|
||||
},
|
||||
eslint: {
|
||||
configFile: __dirname + '/.eslintrc.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.(json)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'json',
|
||||
},
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel!eslint-loader',
|
||||
},
|
||||
{
|
||||
test: /\.(?:png|jpg|gif)$/,
|
||||
loader: 'url?limit=8192', //小于8k,内嵌;大于8k生成文件
|
||||
},
|
||||
{
|
||||
test: /\.less/,
|
||||
loader: ExtractTextPlugin.extract('style', 'css?modules&localIdentName=[hash:base64:4]!less'),
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins:[
|
||||
new CopyWebpackPlugin([
|
||||
{ from: './src/resource/music/music.mp3' },
|
||||
{ from: './src/resource/css/loader.css' },
|
||||
]),
|
||||
new HtmlWebpackPlugin({
|
||||
template: __dirname + '/server/index.tmpl.html'
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin(),
|
||||
new ExtractTextPlugin('css-' + version + '.css'),
|
||||
new webpack.optimize.DedupePlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
}),
|
||||
],
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user