game finished

This commit is contained in:
城危 2016-12-20 23:48:46 +08:00
commit 60703933a3
100 changed files with 5307 additions and 0 deletions

21
.babelrc Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

1
build/css-1.0.0.css.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"css-1.0.0.css","sourceRoot":""}

20
build/index.html Normal file
View 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
View 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

Binary file not shown.

90
i18n.json Normal file
View 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
View 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
View 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
View 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
View 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
View 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,
};

View 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>
);
}
}

View 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;
}
}
}

View 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>
);
}
}

View 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;
}
}

View 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,
};

View 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;
}
}
}

View 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,
};

View File

@ -0,0 +1,6 @@
.keyboard{
width: 580px;
height: 330px;
margin: 20px auto 0;
position:relative;
}

View 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,
};

View 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);
}
}
}

View 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,
};

View File

@ -0,0 +1,9 @@
.matrix{
border:2px solid #000;
padding:3px 1px 1px 3px;
width:228px;
p{
width:220px;
height:22px;
}
}

View 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,
};

View 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;
}
}

View 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,
};

View File

@ -0,0 +1,7 @@
.next{
div{
height: 22px;
width: 88px;
float: right;
}
}

View 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,
};

View 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;}
}

View 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,
};

View 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;
}
}

View 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
View 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
View 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;
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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,
};

View 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
View 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
View 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
View 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')
);

View 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
View 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;

View 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;

View 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
View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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
View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
src/resource/image/share.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Binary file not shown.

6
src/store/index.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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',
},
};

View 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"',
}),
],
};