├── Android └── Majong-AutoScore │ ├── .gitignore │ ├── .idea │ ├── .name │ ├── compiler.xml │ ├── copyright │ │ └── profiles_settings.xml │ ├── encodings.xml │ ├── gradle.xml │ ├── misc.xml │ ├── modules.xml │ ├── scopes │ │ └── scope_settings.xml │ └── vcs.xml │ ├── Majong-AutoScore.iml │ ├── app │ ├── .gitignore │ ├── app.iml │ ├── build.gradle │ ├── proguard-rules.txt │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── mahosyojyo │ │ │ └── majong_autoscore │ │ │ └── app │ │ │ └── activities │ │ │ └── GameSettingsActivity.java │ │ └── res │ │ ├── drawable-hdpi │ │ └── ic_launcher.png │ │ ├── drawable-mdpi │ │ └── ic_launcher.png │ │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ │ ├── drawable-xxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values │ │ ├── dimens.xml │ │ ├── strings.xml │ │ ├── strings_activity_game_settings.xml │ │ └── styles.xml │ │ └── xml │ │ └── pref_game_settings.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── Design.md ├── JS ├── Majong.js ├── canvasjs-1.7.0 │ ├── canvasjs.min.js │ ├── jquery.canvasjs.min.js │ └── source │ │ ├── canvasjs.js │ │ ├── excanvas.js │ │ └── jquery.canvasjs.js ├── chart │ ├── Chart.js │ ├── Chart.min.js │ ├── chartline.css │ ├── charttips.js │ └── gulpfile.js ├── fanfuCal.js ├── img │ ├── dice.png │ ├── lichi.gif │ ├── rcode.png │ ├── saki.jpg │ └── tab_bg1.gif ├── index.html ├── jquery-2.1.4.min.js ├── linechartDraw.js ├── piechartDraw.js ├── src │ └── Chart.Line.js ├── style.css ├── support.js ├── switch.css ├── tab.js └── tabstyle.css ├── LICENSE ├── README.md └── iOS └── src ├── .DS_Store └── MahjongScoreCalculator ├── .DS_Store ├── MahjongScoreCalculator.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── fredfx.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── fredfx.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── MahjongScoreCalculator.xcscheme │ └── xcschememanagement.plist ├── MahjongScoreCalculator ├── .DS_Store ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-40.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-76.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-83.5@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-Small@2x.png │ │ └── Icon-Small@3x.png │ ├── Brand Assets.launchimage │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── GameCore │ ├── MMSCGame.h │ ├── MMSCGame.m │ ├── MMSCGameManager.h │ ├── MMSCGameManager.m │ ├── MMSCPlayer.h │ ├── MMSCPlayer.m │ ├── MMSCRound.h │ ├── MMSCRound.m │ ├── MMSCUtil.h │ ├── MMSCUtil.m │ └── RoundResult │ │ ├── MMSCDrawManganResult.h │ │ ├── MMSCDrawManganResult.m │ │ ├── MMSCDrawResult.h │ │ ├── MMSCDrawResult.m │ │ ├── MMSCNormalWinResult.h │ │ ├── MMSCNormalWinResult.m │ │ ├── MMSCRoundResult.h │ │ ├── MMSCRoundResult.m │ │ ├── MMSCTsuMoResult.h │ │ ├── MMSCTsuMoResult.m │ │ ├── MMSCTwoWinnerResult.h │ │ └── MMSCTwoWinnerResult.m ├── Info.plist ├── View │ ├── MMSCFontAndColorUtil.h │ ├── MMSCFontAndColorUtil.m │ ├── MMSCPlayerNameAndSeatController.h │ ├── MMSCPlayerNameAndSeatController.m │ ├── MMSCRandomSeatTipsController.h │ ├── MMSCRandomSeatTipsController.m │ ├── MMSCRichiController.h │ ├── MMSCRichiController.m │ ├── MMSCRoundResulltController.h │ ├── MMSCRoundResulltController.m │ ├── MMSCScoreTableCell.h │ ├── MMSCScoreTableCell.m │ ├── MMSCScoreTableController.h │ └── MMSCScoreTableController.m ├── main.m └── resource │ ├── StartPage_ButtonBG@2x.png │ ├── StartPage_ButtonBG_click@2x.png │ ├── mahjong-appicon.png │ ├── menu.png │ ├── more_icon@2x.png │ ├── more_icon_click@2x.png │ ├── richiIcon-horizontal.png │ └── richiIcon-vertical.png └── MahjongScoreCalculatorTests ├── Info.plist └── MahjongScoreCalculatorTests.m /Android/Majong-AutoScore/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/.idea/.name: -------------------------------------------------------------------------------- 1 | Majong-AutoScore -------------------------------------------------------------------------------- /Android/Majong-AutoScore/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Android API 19 Platform 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/Majong-AutoScore.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'android' 2 | 3 | android { 4 | compileSdkVersion 19 5 | buildToolsVersion '19.0.3' 6 | 7 | defaultConfig { 8 | minSdkVersion 11 9 | targetSdkVersion 19 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | runProguard false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(include: ['*.jar'], dir: 'libs') 23 | // compile 'com.android.support:support-v4:19.0.1' 24 | // compile 'com.android.support:appcompat-v7:19.0.1' 25 | // compile 'com.android.support:gridlayout-v7:19.0.1' 26 | } 27 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:/Program Files/AndroidStudio/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/src/main/java/com/mahosyojyo/majong_autoscore/app/activities/GameSettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.mahosyojyo.majong_autoscore.app.activities; 2 | 3 | import android.content.Context; 4 | import android.content.res.Configuration; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.preference.ListPreference; 8 | import android.preference.Preference; 9 | import android.preference.PreferenceActivity; 10 | import android.preference.PreferenceFragment; 11 | import android.preference.PreferenceManager; 12 | 13 | 14 | import com.mahosyojyo.majong_autoscore.app.R; 15 | 16 | /** 17 | * A {@link PreferenceActivity} that presents a set of application settings. On 18 | * handset devices, settings are presented as a single list. On tablets, 19 | * settings are split by category, with category headers shown to the left of 20 | * the list of settings. 21 | *

22 | * See 23 | * Android Design: Settings for design guidelines and the Settings 25 | * API Guide for more information on developing a Settings UI. 26 | */ 27 | public class GameSettingsActivity extends PreferenceActivity { 28 | public static String SHARED_PREFERENCE_NAME; 29 | 30 | //TODO-添加设置界面图标,加入右上方设置完成按钮 31 | /** 32 | * Determines whether to always show the simplified settings UI, where 33 | * settings are presented in a single list. When false, settings are shown 34 | * as a master/detail two-pane view on tablets. When true, a single pane is 35 | * shown on tablets. 36 | */ 37 | private static final boolean ALWAYS_SIMPLE_PREFS = false; 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | 43 | setupSimplePreferencesScreen(); 44 | 45 | SHARED_PREFERENCE_NAME = getPreferenceManager().getSharedPreferencesName(); 46 | } 47 | 48 | @Override 49 | protected boolean isValidFragment(String fragmentName) { 50 | return true; 51 | } 52 | 53 | /** 54 | * Shows the simplified settings UI if the device configuration if the 55 | * device configuration dictates that a simplified, single-pane UI should be 56 | * shown. 57 | */ 58 | private void setupSimplePreferencesScreen() { 59 | if (!isSimplePreferences(this)) { 60 | return; 61 | } 62 | 63 | addPreferencesFromResource(R.xml.pref_game_settings); 64 | 65 | bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_gameName_key))); 66 | bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_playerNumber_key))); 67 | bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_enterWest_endCondition_key))); 68 | bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_enterWest_westEnd_key))); 69 | 70 | //配置游戏长度选项,只有选择半庄时才有西入选项 71 | String gameLengthKey = getString(R.string.pref_gameLength_key); 72 | Preference gameLengthPref = findPreference(gameLengthKey); 73 | gameLengthPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { 74 | @Override 75 | public boolean onPreferenceChange(Preference preference, Object o) { 76 | return gameLengthChange(preference, o); 77 | } 78 | }); 79 | 80 | gameLengthChange(gameLengthPref, 81 | PreferenceManager 82 | .getDefaultSharedPreferences(gameLengthPref.getContext()) 83 | .getString(gameLengthKey, "")); 84 | } 85 | 86 | /*游戏长度选事件 87 | *除了基本的修改summary之外,添加对西入的控制 88 | * */ 89 | private boolean gameLengthChange(Preference preference, Object o) { 90 | String value = o.toString(); 91 | sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, o); 92 | findPreference(getString(R.string.pref_enterWestCategory_key)).setEnabled(value.equals("8")); 93 | return true; 94 | } 95 | 96 | /** {@inheritDoc} */ 97 | @Override 98 | public boolean onIsMultiPane() { 99 | return isXLargeTablet(this) && !isSimplePreferences(this); 100 | } 101 | 102 | /** 103 | * Helper method to determine if the device has an extra-large screen. For 104 | * example, 10" tablets are extra-large. 105 | */ 106 | private static boolean isXLargeTablet(Context context) { 107 | return (context.getResources().getConfiguration().screenLayout 108 | & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE; 109 | } 110 | 111 | /** 112 | * Determines whether the simplified settings UI should be shown. This is 113 | * true if this is forced via {@link #ALWAYS_SIMPLE_PREFS}, or the device 114 | * doesn't have newer APIs like {@link PreferenceFragment}, or the device 115 | * doesn't have an extra-large screen. In these cases, a single-pane 116 | * "simplified" settings UI should be shown. 117 | */ 118 | private static boolean isSimplePreferences(Context context) { 119 | return ALWAYS_SIMPLE_PREFS 120 | || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB 121 | || !isXLargeTablet(context); 122 | } 123 | 124 | /** 125 | * A preference value change listener that updates the preference's summary 126 | * to reflect its new value. 127 | */ 128 | private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() { 129 | @Override 130 | public boolean onPreferenceChange(Preference preference, Object value) { 131 | String stringValue = value.toString(); 132 | 133 | if (preference instanceof ListPreference) { 134 | // For list preferences, look up the correct display value in 135 | // the preference's 'entries' list. 136 | ListPreference listPreference = (ListPreference) preference; 137 | int index = listPreference.findIndexOfValue(stringValue); 138 | 139 | // Set the summary to reflect the new value. 140 | preference.setSummary( 141 | index >= 0 142 | ? listPreference.getEntries()[index] 143 | : null); 144 | } else { 145 | // For all other preferences, set the summary to the value's 146 | // simple string representation. 147 | preference.setSummary(stringValue); 148 | } 149 | return true; 150 | } 151 | }; 152 | 153 | /** 154 | * Binds a preference's summary to its value. More specifically, when the 155 | * preference's value is changed, its summary (line of text below the 156 | * preference title) is updated to reflect the value. The summary is also 157 | * immediately updated upon calling this method. The exact display format is 158 | * dependent on the type of preference. 159 | * 160 | * @see #sBindPreferenceSummaryToValueListener 161 | */ 162 | private static void bindPreferenceSummaryToValue(Preference preference) { 163 | // Set the listener to watch for value changes. 164 | preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener); 165 | 166 | // Trigger the listener immediately with the preference's 167 | // current value. 168 | sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, 169 | PreferenceManager 170 | .getDefaultSharedPreferences(preference.getContext()) 171 | .getString(preference.getKey(), "")); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/Android/Majong-AutoScore/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/Android/Majong-AutoScore/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/Android/Majong-AutoScore/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/Android/Majong-AutoScore/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Majong-AutoScore 5 | Hello world! 6 | Settings 7 | MainActivity 8 | 9 | 10 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/src/main/res/values/strings_activity_game_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | Game设定 3 | 4 | 5 | game_name 6 | player_number 7 | game_length 8 | zero_continue 9 | enterWest_page 10 | 11 | 12 | Game信息 13 | 名称 14 | 今天的对♂日♂ 15 | 人数 16 | 17 | 四人 18 | 三人 19 | 20 | 21 | 4 22 | 3 23 | 24 | 25 | 长度 26 | 27 | 一局战 28 | 东风战 29 | 半庄战 30 | 全庄战 31 | 没完没了地战 32 | 33 | 34 | 1 35 | 4 36 | 8 37 | 16 38 | -1 39 | 40 | 41 | 42 | 0分规则 43 | 44 | 0分继续 45 | 已经被婊到0分了,继续婊吗? 46 | 47 | 西入规则 48 | 49 | 无人超3W点西入 50 | 啊咧?不如一起战个痛 51 | enter_west 52 | 53 | 结束条件 54 | end_condition 55 | 56 | 一方达到3W点立即结束 57 | 当前一庄战结束 58 | 59 | 60 | 1 61 | 2 62 | 63 | 64 | west_end 65 | 西场结束无人超3W点判定 66 | 67 | 平局 68 | 战到底 69 | 北入,北场结束仍无人3W点平局 70 | 71 | 72 | 1 73 | 2 74 | 3 75 | 76 | 77 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/app/src/main/res/xml/pref_game_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 25 | 26 | 27 | 35 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 47 | 48 | 49 | 50 | 54 | 55 | 56 | 61 | 62 | 63 | 72 | 73 | 74 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:0.9.+' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | mavenCentral() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Settings specified in this file will override any Gradle settings 5 | # configured through the IDE. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /Android/Majong-AutoScore/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/Android/Majong-AutoScore/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Android/Majong-AutoScore/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 10 15:27:10 PDT 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip 7 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /Android/Majong-AutoScore/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /Design.md: -------------------------------------------------------------------------------- 1 | ###基本功能: 2 | * 自动计算庄家位置,本场数 3 | * 每一局需要输入谁自摸或者谁点谁的泡,或流听状况 4 | * 输入N番M符,自行计算所需点数分配(考虑庄家位置,本场数) 5 | 6 | 7 | ========= 8 | JS版预计/已实现功能: 9 | * ~~可以设置一炮三响属于流局还是给点(默认流局)~~ 10 | * (√2015-8-29)可以设置某方0分算结束还是继续(默认继续) 11 | * (√2015-8-29)可以设置玩家名称(默认东家起分别是Aさん,Bさん,Cさん,Dさん) 12 | * (√2015-8-29)可以设置模式是东风,半庄,全庄还是Endless(默认半庄),并且可以在结束后临时更改 13 | * (√2015-8-29)方桌界面,每一方显示自己的当前点数 14 | * (√2015-8-29)开关选择点差模式还是点数模式 15 | * (√2014–9-3)可以设置视角,(点差模式下)主视角方看到的是和别人之间的点差 16 | * (√2015–9-12)绘制比分趋势走向 17 | * 有列表查看比赛详情,包括胡牌状况,大小 18 | * (√2015–9-12)误操作允许撤销 19 | * ~~允许修改之前某一局的细节~~ 20 | * (√2014–9-6)对于符数计算,可以通过点击选择暗刻数,明刻数,是否嵌章等情况自行计算 21 | * 设置计算顺位马 22 | * 累积多次对局成绩 23 | * ~~写cookies保留当前数据~~ 24 | * (√2014-8-29)可以设置帮玩家随机抽位置(东南西北) 25 | * (√2015–9-13)增加辅助色子功能 26 | * 增加三麻模式 27 | * 大后期再研究团战模式 28 | * 可以任意设置其实比分(默认四麻25000,三麻35000,团战10000) 29 | * (√2015-8-30)半庄西入设置(默认西入) 30 | * (√2015–9-13)新手教程(copy from wiki) 31 | * ~~设置天风规则还是鸟笼规则~~ 32 | * (√2015–92)需要额外增加九九流局和四风连打选项 33 | 34 | 35 | 36 | 37 | 38 | 39 | ========= 40 | 安卓版预计/已实现功能: 41 | * 可以设置模式是东风,半庄,全庄还是Endless(默认半庄),并且可以在结束后临时更改 42 | * 可输入玩家名称(默认:天和、地和、人和、平和),点击确认后开始随机抽东南西北 43 | * 总览界面,界面显示当前场风、本场数以及每个玩家的自风,庄家高亮,显示从开局到当前的分数 44 | * 可以设置视角,主视角方看到的是和别人之间的点差 45 | * 可以设置4家皆不满3w点是否进入西风局(半庄) 46 | * 新手教程(链接到wiki) 47 | 48 | ========= 49 | Yuki建议: 50 | * 可以设置帮玩家随机抽位置(东南西北) 51 | -------------------------------------------------------------------------------- /JS/canvasjs-1.7.0/source/jquery.canvasjs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve CanvasJS jQuery Charting Plugin - http://canvasjs.com/ 3 | * Copyright 2013 fenopix 4 | */ 5 | 6 | /* 7 | * CanvasJS Charts follows Dual Licensing Model as mentioned below. 8 | * 9 | * ---------------------Free for Non-Commercial Use-------------------- 10 | * 11 | * For non-commercial purposes you can use the software for free under Creative Commons Attribution-NonCommercial 3.0 License. Refer to the following link for further details on the same. 12 | * http://creativecommons.org/licenses/by-nc/3.0/deed.en_US 13 | * 14 | * ---------------------Commercial License-------------------- 15 | * Commercial use of CanvasJS requires you to purchase a license. Without a commercial license you can use it for evaluation purposes only. Please refer to the following link for further details. 16 | * http://canvasjs.com/ 17 | * 18 | */ 19 | 20 | (function ($, window, document, undefined) { 21 | 22 | $.fn.CanvasJSChart = function (options) { 23 | 24 | if (options) { 25 | 26 | var $el = this.first(); 27 | var container = this[0]; 28 | var chart = new CanvasJS.Chart(container, options); 29 | 30 | $el.children(".canvasjs-chart-container").data("canvasjsChartRef", chart); 31 | 32 | chart.render(); 33 | 34 | return this; 35 | 36 | } else { 37 | 38 | return this.first().children(".canvasjs-chart-container").data("canvasjsChartRef"); 39 | 40 | } 41 | } 42 | 43 | }(jQuery, window, document)); -------------------------------------------------------------------------------- /JS/chart/chartline.css: -------------------------------------------------------------------------------- 1 | #chartjs-tooltip { 2 | opacity: 1; 3 | position: absolute; 4 | background: rgba(0, 0, 0, .7); 5 | color: white; 6 | padding: 3px; 7 | border-radius: 3px; 8 | -webkit-transition: all .1s ease; 9 | transition: all .1s ease; 10 | pointer-events: none; 11 | -webkit-transform: translate(-50%, 0); 12 | transform: translate(-50%, 0); 13 | } 14 | 15 | .chartjs-tooltip-key { 16 | display: inline-block; 17 | width: 10px; 18 | height: 10px; 19 | } 20 | 21 | #myChart { 22 | float: left; 23 | margin-left: 30px; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /JS/chart/charttips.js: -------------------------------------------------------------------------------- 1 | function set_tooltip() { 2 | Chart.defaults.global.pointHitDetectionRadius = 1; 3 | Chart.defaults.global.customTooltips = function (tooltip) { 4 | var tooltipEl = $('#chartjs-tooltip'); 5 | 6 | if (!tooltip) { 7 | tooltipEl.css({ 8 | opacity: 0 9 | }); 10 | return; 11 | } 12 | 13 | tooltipEl.removeClass('above below'); 14 | tooltipEl.addClass(tooltip.yAlign); 15 | 16 | var innerHtml = tooltip.title; 17 | for (var i = tooltip.labels.length - 1; i >= 0; i--) { 18 | innerHtml += [ 19 | '

', 20 | '' + player[i].playerName, 21 | ' ' + tooltip.labels[i] + '', 22 | '
' 23 | ].join(''); 24 | } 25 | tooltipEl.html(innerHtml); 26 | 27 | tooltipEl.css({ 28 | opacity: 1, 29 | left: tooltip.chart.canvas.offsetLeft + tooltip.x + 'px', 30 | top: tooltip.chart.canvas.offsetTop + tooltip.y + 'px', 31 | fontFamily: tooltip.fontFamily, 32 | fontSize: tooltip.fontSize, 33 | fontStyle: tooltip.fontStyle, 34 | }); 35 | }; 36 | } -------------------------------------------------------------------------------- /JS/chart/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | concat = require('gulp-concat'), 3 | uglify = require('gulp-uglify'), 4 | util = require('gulp-util'), 5 | jshint = require('gulp-jshint'), 6 | size = require('gulp-size'), 7 | connect = require('gulp-connect'), 8 | replace = require('gulp-replace'), 9 | htmlv = require('gulp-html-validator'), 10 | inquirer = require('inquirer'), 11 | semver = require('semver'), 12 | exec = require('child_process').exec, 13 | fs = require('fs'), 14 | package = require('./package.json'), 15 | bower = require('./bower.json'); 16 | 17 | var srcDir = './src/'; 18 | /* 19 | * Usage : gulp build --types=Bar,Line,Doughnut 20 | * Output: - A built Chart.js file with Core and types Bar, Line and Doughnut concatenated together 21 | * - A minified version of this code, in Chart.min.js 22 | */ 23 | 24 | gulp.task('build', function(){ 25 | 26 | // Default to all of the chart types, with Chart.Core first 27 | var srcFiles = [FileName('Core')], 28 | isCustom = !!(util.env.types), 29 | outputDir = (isCustom) ? 'custom' : '.'; 30 | if (isCustom){ 31 | util.env.types.split(',').forEach(function(type){ return srcFiles.push(FileName(type));}); 32 | } 33 | else{ 34 | // Seems gulp-concat remove duplicates - nice! 35 | // So we can use this to sort out dependency order - aka include Core first! 36 | srcFiles.push(srcDir+'*'); 37 | } 38 | 39 | return gulp.src(srcFiles) 40 | .pipe(concat('Chart.js')) 41 | .pipe(replace('{{ version }}', package.version)) 42 | .pipe(gulp.dest(outputDir)) 43 | .pipe(uglify({preserveComments:'some'})) 44 | .pipe(concat('Chart.min.js')) 45 | .pipe(gulp.dest(outputDir)); 46 | 47 | function FileName(moduleName){ 48 | return srcDir+'Chart.'+moduleName+'.js'; 49 | } 50 | }); 51 | 52 | /* 53 | * Usage : gulp bump 54 | * Prompts: Version increment to bump 55 | * Output: - New version number written into package.json & bower.json 56 | */ 57 | 58 | gulp.task('bump', function(complete){ 59 | util.log('Current version:', util.colors.cyan(package.version)); 60 | var choices = ['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease'].map(function(versionType){ 61 | return versionType + ' (v' + semver.inc(package.version, versionType) + ')'; 62 | }); 63 | inquirer.prompt({ 64 | type: 'list', 65 | name: 'version', 66 | message: 'What version update would you like?', 67 | choices: choices 68 | }, function(res){ 69 | var increment = res.version.split(' ')[0], 70 | newVersion = semver.inc(package.version, increment); 71 | 72 | // Set the new versions into the bower/package object 73 | package.version = newVersion; 74 | bower.version = newVersion; 75 | 76 | // Write these to their own files, then build the output 77 | fs.writeFileSync('package.json', JSON.stringify(package, null, 2)); 78 | fs.writeFileSync('bower.json', JSON.stringify(bower, null, 2)); 79 | 80 | complete(); 81 | }); 82 | }); 83 | 84 | gulp.task('release', ['build'], function(){ 85 | exec('git tag -a v' + package.version); 86 | }); 87 | 88 | gulp.task('jshint', function(){ 89 | return gulp.src(srcDir + '*.js') 90 | .pipe(jshint()) 91 | .pipe(jshint.reporter('default')); 92 | }); 93 | 94 | gulp.task('valid', function(){ 95 | return gulp.src('samples/*.html') 96 | .pipe(htmlv()); 97 | }); 98 | 99 | gulp.task('library-size', function(){ 100 | return gulp.src('Chart.min.js') 101 | .pipe(size({ 102 | gzip: true 103 | })); 104 | }); 105 | 106 | gulp.task('module-sizes', function(){ 107 | return gulp.src(srcDir + '*.js') 108 | .pipe(uglify({preserveComments:'some'})) 109 | .pipe(size({ 110 | showFiles: true, 111 | gzip: true 112 | })); 113 | }); 114 | 115 | gulp.task('watch', function(){ 116 | gulp.watch('./src/*', ['build']); 117 | }); 118 | 119 | gulp.task('test', ['jshint', 'valid']); 120 | 121 | gulp.task('size', ['library-size', 'module-sizes']); 122 | 123 | gulp.task('default', ['build', 'watch']); 124 | 125 | gulp.task('server', function(){ 126 | connect.server({ 127 | port: 8000 128 | }); 129 | }); 130 | 131 | // Convenience task for opening the project straight from the command line 132 | gulp.task('_open', function(){ 133 | exec('open http://localhost:8000'); 134 | exec('subl .'); 135 | }); 136 | 137 | gulp.task('dev', ['server', 'default']); 138 | -------------------------------------------------------------------------------- /JS/fanfuCal.js: -------------------------------------------------------------------------------- 1 | function fanshu_click(idx) { 2 | var cur_btn = $q('#fanshu_field .fanfu_btn', idx); 3 | if (cur_btn.hasClass('fanshu_clk')) { 4 | if (idx == 8) { //役满递增处理 5 | var cur_yiman = parseInt(cur_btn[0].value[0]); 6 | if (cur_yiman < 6) { 7 | cur_btn[0].value = (cur_yiman + 1) + "倍役满"; 8 | } else { 9 | cur_btn[0].value = "1倍役满"; 10 | cur_btn.removeClass('fanshu_clk'); 11 | ClearFushuSetting(false); 12 | } 13 | } else { 14 | cur_btn.removeClass('fanshu_clk'); 15 | ClearFushuSetting(false); 16 | } 17 | } else { 18 | cur_btn.addClass('fanshu_clk'); 19 | $('#fanshu_field .fanfu_btn').not(function (id) { 20 | return id == idx 21 | }).removeClass('fanshu_clk'); 22 | if (idx >= 4) { 23 | ClearFushuSetting(true); 24 | } else { 25 | ClearFushuSetting(false); 26 | } 27 | } 28 | } 29 | 30 | function ClearFushuSetting(IsClear) { 31 | $('#fushu_field .fanfu_btn').attr('disabled', IsClear); 32 | $('.fanfu_help_btn').attr('disabled', IsClear); 33 | $('tr.mianzi_tr td .fu_btn').attr('disabled', IsClear); 34 | $('#fushu_ok').attr('disabled', IsClear); 35 | 36 | if (IsClear) { 37 | $('#fushu_field .fanfu_btn').removeClass('fushu_clk'); 38 | $('.fanfu_help_btn').removeClass('fanfu_help_clk'); 39 | $('tr.mianzi_tr td .fu_btn').removeClass('mz_cal_clk'); 40 | $('tr.mianzi_tr td .fu_btn').each(function (idx) { 41 | $q('tr.mianzi_tr td .fu_btn', idx)[0].value = 0; 42 | }) 43 | } 44 | } 45 | 46 | 47 | function fushu_click(idx) { 48 | var cur_btn = $q('#fushu_field .fanfu_btn', idx); 49 | if (cur_btn.hasClass('fushu_clk')) { 50 | cur_btn.removeClass('fushu_clk'); 51 | } else { 52 | cur_btn.addClass('fushu_clk'); 53 | $('#fushu_field .fanfu_btn').not(function (id) { 54 | return id == idx 55 | }).removeClass('fushu_clk'); 56 | } 57 | } 58 | 59 | function toolhelp_clk(idx) { 60 | var cur_btn = $q('.fanfu_help_btn', idx); 61 | if (cur_btn.hasClass('fanfu_help_clk')) { 62 | cur_btn.removeClass('fanfu_help_clk'); 63 | } else { 64 | cur_btn.addClass('fanfu_help_clk'); 65 | } 66 | SetFushu(); 67 | } 68 | 69 | 70 | function mianzi_cal(idx1, idx2) { 71 | var validLeft = 4; 72 | $('tr.mianzi_tr td .fu_btn').each(function (i) { 73 | validLeft -= parseInt($q('tr.mianzi_tr td .fu_btn', i)[0].value); 74 | }); 75 | var idx = idx1 * 2 + idx2; 76 | var cur_btn = $q('tr.mianzi_tr td .fu_btn', idx); 77 | 78 | var cur_val = parseInt(cur_btn[0].value); 79 | cur_val = (validLeft == 0) ? 0 : cur_val + 1; 80 | cur_btn[0].value = '' + cur_val; 81 | if (cur_btn[0].value > 0) 82 | cur_btn.addClass('mz_cal_clk'); 83 | else 84 | cur_btn.removeClass('mz_cal_clk'); 85 | 86 | SetFushu(); 87 | } 88 | 89 | function CalFu() { //计算符数 90 | var fushu = 20; 91 | if ($q('.fanfu_help_btn', 0).hasClass('fanfu_help_clk')) 92 | fushu += 2; //听牌符数 93 | if ($q('.fanfu_help_btn', 1).hasClass('fanfu_help_clk')) 94 | fushu += 10; //听牌符数 95 | if ($q('.fanfu_help_btn', 2).hasClass('fanfu_help_clk')) 96 | fushu += 2; //听牌符数 97 | 98 | for (var idx1 = 0; idx1 < 4; idx1++) { 99 | for (var idx2 = 0; idx2 < 2; idx2++) { 100 | var t = Math.pow(2, idx1 + idx2 + 1); 101 | var idx = idx1 * 2 + idx2; 102 | var cur_btn = $q('tr.mianzi_tr td .fu_btn', idx); 103 | fushu += t * parseInt(cur_btn[0].value); 104 | } 105 | } 106 | return fushu; 107 | } 108 | 109 | function SetFushu() { 110 | var fu = CalFu(); 111 | var result_f = fu - fu % 10 + (fu % 10 == 0 ? 0 : 10); 112 | if (result_f > 120) 113 | result_f = 120; ///////////////最好考证一下!!! 114 | $('#fushu_field .fanfu_btn').each(function (idx) { 115 | $q('#fushu_field .fanfu_btn', idx)[0].value == result_f ? $q('#fushu_field .fanfu_btn', idx).addClass('fushu_clk') : $q('#fushu_field .fanfu_btn', idx).removeClass('fushu_clk'); 116 | }); 117 | } 118 | 119 | function liuju_clk(idx) { 120 | if ($q('.liuju_icon', idx).hasClass('liuju_noting')) { 121 | $q('.liuju_icon', idx).removeClass('liuju_noting'); 122 | $q('.liuju_icon', idx).addClass('liuju_ting'); 123 | } else { 124 | $q('.liuju_icon', idx).removeClass('liuju_ting'); 125 | $q('.liuju_icon', idx).addClass('liuju_noting'); 126 | } 127 | } 128 | 129 | function ScoreUpper(score) { 130 | return parseInt(score / 100) * 100 + (score % 100 == 0 ? 0 : 100); 131 | } -------------------------------------------------------------------------------- /JS/img/dice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/JS/img/dice.png -------------------------------------------------------------------------------- /JS/img/lichi.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/JS/img/lichi.gif -------------------------------------------------------------------------------- /JS/img/rcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/JS/img/rcode.png -------------------------------------------------------------------------------- /JS/img/saki.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/JS/img/saki.jpg -------------------------------------------------------------------------------- /JS/img/tab_bg1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/JS/img/tab_bg1.gif -------------------------------------------------------------------------------- /JS/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 日麻算分器 Version 2.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 | 33 |
34 |
35 |
36 |
37 | 游戏设置 38 |
39 | 姓名 40 |
41 | 东家: 42 | 43 |
南家: 44 | 45 |
西家: 46 | 47 |
北家: 48 | 49 |
50 |
51 | 52 | 53 |
54 | 55 | 56 |
57 | 58 | 59 |
60 |
61 |
62 |
63 |
64 |
65 | 66 |
67 |
68 |
还没想好这里要怎么弄啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
69 |
70 | 71 |
72 |
73 |
74 |

一番:

75 |
立直,一发,门清自摸,断幺,平和,一杯口,役牌,宝牌,领上,抢杠,海底,河底
76 |
77 |

两番:

78 |
79 |

三色*

, 80 |

一气*

,对对,三暗刻,七对, 81 |

混全*

,混老头,三色同刻,三杠子,小三元,W立 82 |
83 |
84 |

三番:

85 |
86 |

混一色*

, 87 |

纯全带幺*

,二杯口
88 |
89 |

满贯以上:

90 |
流满(满贯), 91 |

清一色*

(六番)
92 |
93 |

役满:

94 |
大三元, 95 |

四暗刻*

, 96 |

国士*

,字一色, 97 |

小四喜*

,绿一色,天地人胡, 98 |

九莲*

,四杠子,累计,大竹轮,大数临,大车轮,大七星
99 |
100 |
101 |

MORE:

日麻役详细, 102 | 日麻规则算分表 103 |
104 |
105 | Version: 2.0 106 |
版权归 107 | Vespa 所有 108 |
项目主页: 109 | https://github.com/Mahosyojyo 110 |
反馈: 111 | 点击提BUG 112 |
113 | 114 |
115 |
116 | 117 |
118 | 119 | 120 |
121 |
122 |
123 |
124 | 125 | 129 |
130 |
131 | 场况: 132 |

133 |
本场数: 134 |

135 | × 136 |

0

137 |
138 |
139 | 140 | 141 |
142 |
143 |
144 |
145 |
146 |
0
147 |
148 |
149 |
150 | 151 | 152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
0
161 |
162 |
163 |
164 | 165 | 166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
0
175 |
176 |
177 |
178 | 179 | 180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
0
189 |
190 |
191 |
192 | 193 | 194 |
195 |
196 |
197 |
198 | 199 | 200 |
201 |
202 | 203 |
204 |
205 | 番数 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 |
216 |
217 | 符数 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 |
231 |
232 | 符数计算工具 233 | 234 | 235 | 236 |
237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 249 | 252 | 253 | 254 | 255 | 258 | 261 | 262 | 263 | 264 | 267 | 270 | 271 | 272 | 273 | 276 | 279 | 280 |
面子计算
中章幺九
明刻 247 | 248 | 250 | 251 |
暗刻 256 | 257 | 259 | 260 |
明杠 265 | 266 | 268 | 269 |
暗杠 274 | 275 | 277 | 278 |
281 |
282 |
283 | 284 |
285 |
286 | 287 | 288 | 289 | -------------------------------------------------------------------------------- /JS/linechartDraw.js: -------------------------------------------------------------------------------- 1 | function DrawLine() { 2 | // var data = { 3 | // labels: [], 4 | // datasets: [ 5 | // { 6 | // fillColor: "rgba(255,255,255,0)", 7 | // strokeColor: "rgb(220,0,220)", //线 8 | // pointColor: "rgb(203, 22, 29)", //点 9 | // pointStrokeColor: "#f00", //圈 10 | // data: [] 11 | // }, 12 | // { 13 | // fillColor: "rgba(255,255,255,0)", 14 | // strokeColor: "rgb(18, 235, 60)", 15 | // pointColor: "rgb(0, 250, 135)", 16 | // pointStrokeColor: "#21fa7d", 17 | // data: [] 18 | // } 19 | // , 20 | // { 21 | // fillColor: "rgba(255,255,255,0)", 22 | // strokeColor: "rgb(25, 41, 234)", 23 | // pointColor: "rgb(14, 148, 198)", 24 | // pointStrokeColor: "#4e60c1", 25 | // data: [] 26 | // } 27 | // , 28 | // { 29 | // fillColor: "rgba(255,255,255,0)", 30 | // strokeColor: "rgb(237, 128, 109)", 31 | // pointColor: "rgb(226, 93, 34)", 32 | // pointStrokeColor: "#c9861c", 33 | // data: [] 34 | // } 35 | // ] 36 | // }; 37 | // for (var i = 0; i < game_state.length; i++) { 38 | // data['labels'].push(game_state[i].game.changfeng + game_state[i].game.jushu + '-' + game_state[i].game.benchang); 39 | // for (var player_idx = 0; player_idx < 4; player_idx++) { 40 | // data['datasets'][player_idx]['data'].push(game_state[i].player[player_idx].Point); 41 | // } 42 | // } 43 | // var ctx = document.getElementById("myChart").getContext("2d"); 44 | // window.myLine = new Chart(ctx).Line(data, { 45 | // scaleOverlay: true, 46 | // showScale: true, 47 | // scaleLineColor: "rgba(0,0,0,1)", 48 | // scaleLineWidth: 1, 49 | // scaleShowLabels: true, 50 | // scaleLabel: "<%=value%>", 51 | // scaleFontFamily: "'Arial'", 52 | // scaleFontSize: 20, 53 | // scaleFontStyle: "normal", 54 | // scaleFontColor: "#666", 55 | // scaleShowGridLines: true, 56 | // scaleGridLineColor: "rgba(0,0,0,.1)", 57 | // scaleGridLineWidth: 3, 58 | // bezierCurve: Draw_Line_Curl, 59 | // pointDot: true, 60 | // pointDotRadius: 5, 61 | // pointDotStrokeWidth: 1, 62 | // datasetStrokeWidth: 2, 63 | // datasetFill: false, 64 | // }); 65 | 66 | var chart = new CanvasJS.Chart("linechartContainer", { 67 | animationEnabled: true, 68 | animationDuration: 500, 69 | title: { 70 | //text: "分数走势图" 71 | }, 72 | axisY: { 73 | valueFormatString: "#######", 74 | labelFontSize: 20, 75 | gridDashType: "dot", 76 | gridThickness: 2, 77 | minimum: 0, 78 | stripLines: [ 79 | { 80 | startValue: 25000, 81 | endValue: 100000, 82 | color: "#f0ddba" 83 | }, 84 | { 85 | startValue: 0, 86 | endValue: 25000, 87 | color: "#e0d6d6" 88 | } 89 | ] 90 | }, 91 | axisX: { 92 | labelAngle: -45, 93 | labelFontSize: 20, 94 | minimum: 0, 95 | gridDashType: "dot", 96 | gridThickness: 2, 97 | gridColor: "orange", 98 | }, 99 | legend: { 100 | fontSize: 18, 101 | horizontalAlign: "right", 102 | verticalAlign: "top", 103 | }, 104 | toolTip: { 105 | shared: true, 106 | contentFormatter: function (e) { 107 | var content = "
" + e.entries[0].dataPoint.label + "
"; 108 | for (var i = 0; i < e.entries.length; i++) { 109 | content += "" + e.entries[i].dataSeries.name + ":" + "" + e.entries[i].dataPoint.y + ""; 110 | content += "
"; 111 | } 112 | return content; 113 | } 114 | }, 115 | data: [ 116 | { 117 | name: player[0].playerName, 118 | showInLegend: true, 119 | highlightEnabled: true, 120 | type: Draw_Line_Curl ? 'spline' : 'line', 121 | dataPoints: [] 122 | }, 123 | { 124 | name: player[1].playerName, 125 | showInLegend: true, 126 | highlightEnabled: true, 127 | type: Draw_Line_Curl ? 'spline' : 'line', 128 | dataPoints: [] 129 | }, 130 | { 131 | name: player[2].playerName, 132 | showInLegend: true, 133 | highlightEnabled: true, 134 | type: Draw_Line_Curl ? 'spline' : 'line', 135 | dataPoints: [] 136 | }, { 137 | name: player[3].playerName, 138 | showInLegend: true, 139 | highlightEnabled: true, 140 | type: Draw_Line_Curl ? 'spline' : 'line', 141 | dataPoints: [] 142 | } 143 | ] 144 | 145 | }); 146 | 147 | var last_score = [InitScore, InitScore, InitScore, InitScore]; 148 | var min_score = 100000; 149 | for (var i = 0; i < game_state.length; i++) { 150 | for (var player_idx = 0; player_idx < 4; player_idx++) { 151 | chart.options.data[player_idx].dataPoints.push({ 152 | y: game_state[i].player[player_idx].Point, 153 | label: game_state[i].game.changfeng + game_state[i].game.jushu + '-' + game_state[i].game.benchang, 154 | markerSize: 12, 155 | markerColor: game_state[i].player[player_idx].Point > last_score[player_idx] ? "#2defd8" : (game_state[i].player[player_idx].Point < last_score[player_idx] ? "tomato" : "green"), 156 | markerType: game_state[i].player[player_idx].Point > last_score[player_idx] ? "triangle" : (game_state[i].player[player_idx].Point < last_score[player_idx] ? "cross" : "square") 157 | }) 158 | last_score[player_idx] = game_state[i].player[player_idx].Point; 159 | if (game_state[i].player[player_idx].Point < min_score) 160 | min_score = game_state[i].player[player_idx].Point; 161 | } 162 | } 163 | chart.options.axisY.minimum = min_score - 1000; 164 | chart.render(); 165 | } -------------------------------------------------------------------------------- /JS/piechartDraw.js: -------------------------------------------------------------------------------- 1 | function FigureStartAngel(maxidx) { 2 | var player1Rate = player[maxidx].Point / (player[0].Point + player[1].Point + player[2].Point + player[3].Point); 3 | return ((5-maxidx)%4)*90-player1Rate/2*360; 4 | } 5 | 6 | function DrawPieChart() { 7 | var maxidx = 0; 8 | for (var i = 1; i < 4; i++) { 9 | if (player[i].Point > player[maxidx].Point) 10 | maxidx = i; 11 | } 12 | var chart = new CanvasJS.Chart("chartContainer", { 13 | theme: "theme2", 14 | animationEnabled: true, 15 | animationDuration: 500, 16 | title: { 17 | text: "" 18 | }, 19 | data: [ 20 | { 21 | type: "pie", 22 | showInLegend: true, 23 | toolTipContent: "{indexLabel}
{y}", 24 | indexLabelPlacement: "inside", 25 | indexLabelFontColor: "black", 26 | indexLabelFontSize: 18, 27 | showInLegend: false, 28 | startAngle: FigureStartAngel(maxidx), 29 | dataPoints: [ 30 | { 31 | y: player[maxidx].Point, 32 | indexLabel: player[maxidx].playerName 33 | }, 34 | { 35 | y: player[(maxidx+3)%4].Point, 36 | indexLabel: player[(maxidx+3)%4].playerName 37 | }, 38 | { 39 | y: player[(maxidx+2)%4].Point, 40 | indexLabel: player[(maxidx+2)%4].playerName 41 | }, 42 | { 43 | y: player[(maxidx+1)%4].Point, 44 | indexLabel: player[(maxidx+1)%4].playerName 45 | } 46 | ] 47 | } 48 | ] 49 | }); 50 | chart.render(); 51 | } -------------------------------------------------------------------------------- /JS/src/Chart.Line.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | "use strict"; 3 | 4 | var root = this, 5 | Chart = root.Chart, 6 | helpers = Chart.helpers; 7 | 8 | var defaultConfig = { 9 | 10 | ///Boolean - Whether grid lines are shown across the chart 11 | scaleShowGridLines : true, 12 | 13 | //String - Colour of the grid lines 14 | scaleGridLineColor : "rgba(0,0,0,.05)", 15 | 16 | //Number - Width of the grid lines 17 | scaleGridLineWidth : 1, 18 | 19 | //Boolean - Whether to show horizontal lines (except X axis) 20 | scaleShowHorizontalLines: true, 21 | 22 | //Boolean - Whether to show vertical lines (except Y axis) 23 | scaleShowVerticalLines: true, 24 | 25 | //Boolean - Whether the line is curved between points 26 | bezierCurve : true, 27 | 28 | //Number - Tension of the bezier curve between points 29 | bezierCurveTension : 0.4, 30 | 31 | //Boolean - Whether to show a dot for each point 32 | pointDot : true, 33 | 34 | //Number - Radius of each point dot in pixels 35 | pointDotRadius : 4, 36 | 37 | //Number - Pixel width of point dot stroke 38 | pointDotStrokeWidth : 1, 39 | 40 | //Number - amount extra to add to the radius to cater for hit detection outside the drawn point 41 | pointHitDetectionRadius : 20, 42 | 43 | //Boolean - Whether to show a stroke for datasets 44 | datasetStroke : true, 45 | 46 | //Number - Pixel width of dataset stroke 47 | datasetStrokeWidth : 2, 48 | 49 | //Boolean - Whether to fill the dataset with a colour 50 | datasetFill : true, 51 | 52 | //String - A legend template 53 | legendTemplate : "", 54 | 55 | //Boolean - Whether to horizontally center the label and point dot inside the grid 56 | offsetGridLines : false 57 | 58 | }; 59 | 60 | 61 | Chart.Type.extend({ 62 | name: "Line", 63 | defaults : defaultConfig, 64 | initialize: function(data){ 65 | //Declare the extension of the default point, to cater for the options passed in to the constructor 66 | this.PointClass = Chart.Point.extend({ 67 | offsetGridLines : this.options.offsetGridLines, 68 | strokeWidth : this.options.pointDotStrokeWidth, 69 | radius : this.options.pointDotRadius, 70 | display: this.options.pointDot, 71 | hitDetectionRadius : this.options.pointHitDetectionRadius, 72 | ctx : this.chart.ctx, 73 | inRange : function(mouseX){ 74 | return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2)); 75 | } 76 | }); 77 | 78 | this.datasets = []; 79 | 80 | //Set up tooltip events on the chart 81 | if (this.options.showTooltips){ 82 | helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ 83 | var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : []; 84 | this.eachPoints(function(point){ 85 | point.restore(['fillColor', 'strokeColor']); 86 | }); 87 | helpers.each(activePoints, function(activePoint){ 88 | activePoint.fillColor = activePoint.highlightFill; 89 | activePoint.strokeColor = activePoint.highlightStroke; 90 | }); 91 | this.showTooltip(activePoints); 92 | }); 93 | } 94 | 95 | //Iterate through each of the datasets, and build this into a property of the chart 96 | helpers.each(data.datasets,function(dataset){ 97 | 98 | var datasetObject = { 99 | label : dataset.label || null, 100 | fillColor : dataset.fillColor, 101 | strokeColor : dataset.strokeColor, 102 | pointColor : dataset.pointColor, 103 | pointStrokeColor : dataset.pointStrokeColor, 104 | points : [] 105 | }; 106 | 107 | this.datasets.push(datasetObject); 108 | 109 | 110 | helpers.each(dataset.data,function(dataPoint,index){ 111 | //Add a new point for each piece of data, passing any required data to draw. 112 | datasetObject.points.push(new this.PointClass({ 113 | value : dataPoint, 114 | label : data.labels[index], 115 | datasetLabel: dataset.label, 116 | strokeColor : dataset.pointStrokeColor, 117 | fillColor : dataset.pointColor, 118 | highlightFill : dataset.pointHighlightFill || dataset.pointColor, 119 | highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor 120 | })); 121 | },this); 122 | 123 | this.buildScale(data.labels); 124 | 125 | 126 | this.eachPoints(function(point, index){ 127 | helpers.extend(point, { 128 | x: this.scale.calculateX(index), 129 | y: this.scale.endPoint 130 | }); 131 | point.save(); 132 | }, this); 133 | 134 | },this); 135 | 136 | 137 | this.render(); 138 | }, 139 | update : function(){ 140 | this.scale.update(); 141 | // Reset any highlight colours before updating. 142 | helpers.each(this.activeElements, function(activeElement){ 143 | activeElement.restore(['fillColor', 'strokeColor']); 144 | }); 145 | this.eachPoints(function(point){ 146 | point.save(); 147 | }); 148 | this.render(); 149 | }, 150 | eachPoints : function(callback){ 151 | helpers.each(this.datasets,function(dataset){ 152 | helpers.each(dataset.points,callback,this); 153 | },this); 154 | }, 155 | getPointsAtEvent : function(e){ 156 | var pointsArray = [], 157 | eventPosition = helpers.getRelativePosition(e); 158 | helpers.each(this.datasets,function(dataset){ 159 | helpers.each(dataset.points,function(point){ 160 | if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point); 161 | }); 162 | },this); 163 | return pointsArray; 164 | }, 165 | buildScale : function(labels){ 166 | var self = this; 167 | 168 | var dataTotal = function(){ 169 | var values = []; 170 | self.eachPoints(function(point){ 171 | values.push(point.value); 172 | }); 173 | 174 | return values; 175 | }; 176 | 177 | var scaleOptions = { 178 | templateString : this.options.scaleLabel, 179 | height : this.chart.height, 180 | width : this.chart.width, 181 | ctx : this.chart.ctx, 182 | textColor : this.options.scaleFontColor, 183 | offsetGridLines : this.options.offsetGridLines, 184 | fontSize : this.options.scaleFontSize, 185 | fontStyle : this.options.scaleFontStyle, 186 | fontFamily : this.options.scaleFontFamily, 187 | valuesCount : labels.length, 188 | beginAtZero : this.options.scaleBeginAtZero, 189 | integersOnly : this.options.scaleIntegersOnly, 190 | calculateYRange : function(currentHeight){ 191 | var updatedRanges = helpers.calculateScaleRange( 192 | dataTotal(), 193 | currentHeight, 194 | this.fontSize, 195 | this.beginAtZero, 196 | this.integersOnly 197 | ); 198 | helpers.extend(this, updatedRanges); 199 | }, 200 | xLabels : labels, 201 | font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), 202 | lineWidth : this.options.scaleLineWidth, 203 | lineColor : this.options.scaleLineColor, 204 | showHorizontalLines : this.options.scaleShowHorizontalLines, 205 | showVerticalLines : this.options.scaleShowVerticalLines, 206 | gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, 207 | gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", 208 | padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth, 209 | showLabels : this.options.scaleShowLabels, 210 | display : this.options.showScale 211 | }; 212 | 213 | if (this.options.scaleOverride){ 214 | helpers.extend(scaleOptions, { 215 | calculateYRange: helpers.noop, 216 | steps: this.options.scaleSteps, 217 | stepValue: this.options.scaleStepWidth, 218 | min: this.options.scaleStartValue, 219 | max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) 220 | }); 221 | } 222 | 223 | 224 | this.scale = new Chart.Scale(scaleOptions); 225 | }, 226 | addData : function(valuesArray,label){ 227 | //Map the values array for each of the datasets 228 | 229 | helpers.each(valuesArray,function(value,datasetIndex){ 230 | //Add a new point for each piece of data, passing any required data to draw. 231 | this.datasets[datasetIndex].points.push(new this.PointClass({ 232 | value : value, 233 | label : label, 234 | datasetLabel: this.datasets[datasetIndex].label, 235 | x: this.scale.calculateX(this.scale.valuesCount+1), 236 | y: this.scale.endPoint, 237 | strokeColor : this.datasets[datasetIndex].pointStrokeColor, 238 | fillColor : this.datasets[datasetIndex].pointColor 239 | })); 240 | },this); 241 | 242 | this.scale.addXLabel(label); 243 | //Then re-render the chart. 244 | this.update(); 245 | }, 246 | removeData : function(){ 247 | this.scale.removeXLabel(); 248 | //Then re-render the chart. 249 | helpers.each(this.datasets,function(dataset){ 250 | dataset.points.shift(); 251 | },this); 252 | this.update(); 253 | }, 254 | reflow : function(){ 255 | var newScaleProps = helpers.extend({ 256 | height : this.chart.height, 257 | width : this.chart.width 258 | }); 259 | this.scale.update(newScaleProps); 260 | }, 261 | draw : function(ease){ 262 | var easingDecimal = ease || 1; 263 | this.clear(); 264 | 265 | var ctx = this.chart.ctx; 266 | 267 | // Some helper methods for getting the next/prev points 268 | var hasValue = function(item){ 269 | return item.value !== null; 270 | }, 271 | nextPoint = function(point, collection, index){ 272 | return helpers.findNextWhere(collection, hasValue, index) || point; 273 | }, 274 | previousPoint = function(point, collection, index){ 275 | return helpers.findPreviousWhere(collection, hasValue, index) || point; 276 | }; 277 | 278 | if (!this.scale) return; 279 | this.scale.draw(easingDecimal); 280 | 281 | 282 | helpers.each(this.datasets,function(dataset){ 283 | var pointsWithValues = helpers.where(dataset.points, hasValue); 284 | 285 | //Transition each point first so that the line and point drawing isn't out of sync 286 | //We can use this extra loop to calculate the control points of this dataset also in this loop 287 | 288 | helpers.each(dataset.points, function(point, index){ 289 | if (point.hasValue()){ 290 | point.transition({ 291 | y : this.scale.calculateY(point.value), 292 | x : this.scale.calculateX(index) 293 | }, easingDecimal); 294 | } 295 | },this); 296 | 297 | 298 | // Control points need to be calculated in a separate loop, because we need to know the current x/y of the point 299 | // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed 300 | if (this.options.bezierCurve){ 301 | helpers.each(pointsWithValues, function(point, index){ 302 | var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0; 303 | point.controlPoints = helpers.splineCurve( 304 | previousPoint(point, pointsWithValues, index), 305 | point, 306 | nextPoint(point, pointsWithValues, index), 307 | tension 308 | ); 309 | 310 | // Prevent the bezier going outside of the bounds of the graph 311 | 312 | // Cap puter bezier handles to the upper/lower scale bounds 313 | if (point.controlPoints.outer.y > this.scale.endPoint){ 314 | point.controlPoints.outer.y = this.scale.endPoint; 315 | } 316 | else if (point.controlPoints.outer.y < this.scale.startPoint){ 317 | point.controlPoints.outer.y = this.scale.startPoint; 318 | } 319 | 320 | // Cap inner bezier handles to the upper/lower scale bounds 321 | if (point.controlPoints.inner.y > this.scale.endPoint){ 322 | point.controlPoints.inner.y = this.scale.endPoint; 323 | } 324 | else if (point.controlPoints.inner.y < this.scale.startPoint){ 325 | point.controlPoints.inner.y = this.scale.startPoint; 326 | } 327 | },this); 328 | } 329 | 330 | 331 | //Draw the line between all the points 332 | ctx.lineWidth = this.options.datasetStrokeWidth; 333 | ctx.strokeStyle = dataset.strokeColor; 334 | ctx.beginPath(); 335 | 336 | helpers.each(pointsWithValues, function(point, index){ 337 | if (index === 0){ 338 | ctx.moveTo(point.x, point.y); 339 | } 340 | else{ 341 | if(this.options.bezierCurve){ 342 | var previous = previousPoint(point, pointsWithValues, index); 343 | 344 | ctx.bezierCurveTo( 345 | previous.controlPoints.outer.x, 346 | previous.controlPoints.outer.y, 347 | point.controlPoints.inner.x, 348 | point.controlPoints.inner.y, 349 | point.x, 350 | point.y 351 | ); 352 | } 353 | else{ 354 | ctx.lineTo(point.x,point.y); 355 | } 356 | } 357 | }, this); 358 | 359 | if (this.options.datasetStroke) { 360 | ctx.stroke(); 361 | } 362 | 363 | if (this.options.datasetFill && pointsWithValues.length > 0){ 364 | //Round off the line by going to the base of the chart, back to the start, then fill. 365 | ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint); 366 | ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint); 367 | ctx.fillStyle = dataset.fillColor; 368 | ctx.closePath(); 369 | ctx.fill(); 370 | } 371 | 372 | //Now draw the points over the line 373 | //A little inefficient double looping, but better than the line 374 | //lagging behind the point positions 375 | helpers.each(pointsWithValues,function(point){ 376 | point.draw(); 377 | }); 378 | },this); 379 | } 380 | }); 381 | 382 | 383 | }).call(this); 384 | -------------------------------------------------------------------------------- /JS/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: arial; 3 | font-size: 12px; 4 | text-align: center; 5 | margin: 0; 6 | ; 7 | width: 100%; 8 | } 9 | 10 | .clear { 11 | clear: both; 12 | } 13 | 14 | fieldset { 15 | height: 90%; 16 | } 17 | 18 | fieldset#playerName { 19 | width: 80px; 20 | float: left; 21 | height: 180px; 22 | } 23 | 24 | fieldset#gamemode { 25 | width: 60px; 26 | float: left; 27 | } 28 | 29 | fieldset#zeropointsetting { 30 | width: 60px; 31 | float: left; 32 | } 33 | 34 | #gamearea { 35 | /*background:待添加*/ 36 | width: 500px; 37 | height: 500px; 38 | border: 3px solid #0F0; 39 | border-radius: 10px; 40 | position: relative; 41 | float: left; 42 | } 43 | 44 | .playerinfoarea { 45 | width: 31%; 46 | height: 31%; 47 | border: 3px solid black; 48 | border-radius: 3px; 49 | position: absolute; 50 | text-align: left; 51 | } 52 | 53 | #player1 { 54 | left: 35%; 55 | top: 69%; 56 | } 57 | 58 | #player2 { 59 | left: 69%; 60 | top: 35%; 61 | } 62 | 63 | #player3 { 64 | left: 35%; 65 | } 66 | 67 | #player4 { 68 | top: 35%; 69 | } 70 | 71 | .playerpos { 72 | font-family: 'Tangerine', serif; 73 | font-size: 30px; 74 | text-shadow: 4px 4px 4px #aaa; 75 | text-align: right; 76 | float: left; 77 | } 78 | 79 | .playername { 80 | font-family: 'Droid Serif', serif; 81 | font-size: 30px; 82 | float: right; 83 | width: 100px 84 | } 85 | 86 | .playerscore { 87 | font-family: "Comic Sans MS"; 88 | font-size: 24px; 89 | } 90 | 91 | .playerscorediff { 92 | width: auto; 93 | height: 18px; 94 | font-size: 15px; 95 | text-align: center; 96 | background: url(img/lichi.gif) no-repeat; 97 | background-size: 100% 100%; 98 | } 99 | 100 | .weici { 101 | font-size: 24px; 102 | text-align: center; 103 | } 104 | 105 | #xiru_field { 106 | float: left; 107 | width: 60px; 108 | } 109 | 110 | #game_process { 111 | width: 33%; 112 | height: 100px; 113 | float: right; 114 | font-size: 24px; 115 | text-align: left; 116 | border: 2px solid #80abde; 117 | border-radius: 0 0 0 20px; 118 | } 119 | 120 | .inline_text { 121 | display: inline; 122 | color: forestgreen; 123 | } 124 | 125 | .rong_btn { 126 | width: 45%; 127 | font-size: 28px; 128 | height: 40px; 129 | border: 2px #9999FF dashed; 130 | background-color: red; 131 | -webkit-appearance: none; 132 | bottom: 0px; 133 | position: absolute; 134 | left: 0px; 135 | } 136 | 137 | .dianpao_btn { 138 | width: 45%; 139 | font-size: 28px; 140 | height: 40px; 141 | background-color: pink; 142 | border: 2px #9999FF dashed; 143 | -webkit-appearance: none; 144 | bottom: 0px; 145 | position: absolute; 146 | right: 0px; 147 | } 148 | 149 | .t_btn_click { 150 | color: aliceblue; 151 | background-color: black; 152 | border: 5px solid #F0e; 153 | border-radius: 10px; 154 | //font-size: 30px; 155 | height: 50px; 156 | } 157 | 158 | .mainviewcl { 159 | color: dodgerblue; 160 | } 161 | 162 | .corner_btn { 163 | float: right; 164 | position: absolute; 165 | width: 100px; 166 | height: 40px; 167 | border: 2px dashed green; 168 | font-size: 20px; 169 | right: 5px; 170 | -webkit-appearance: none; 171 | } 172 | 173 | .corner_btn_4win { 174 | bottom: 50px; 175 | } 176 | 177 | .end99 { 178 | bottom: 5px; 179 | } 180 | 181 | .end4win { 182 | bottom: 50px; 183 | -webkit-appearance: none; 184 | } 185 | 186 | .lichi { 187 | border: 5px dashed #F00; 188 | border-radius: 15px; 189 | } 190 | 191 | #cal_area { 192 | margin-left: 30px; 193 | border: 2px solid #000; 194 | border-radius: 20px; 195 | margin-left: 510px; 196 | margin-right: 2px; 197 | padding: 5px; 198 | //max-width: 500px; 199 | } 200 | 201 | #fanshu_field { 202 | float: left; 203 | width: 70px; 204 | } 205 | 206 | #fushu_field { 207 | float: left; 208 | width: 70px; 209 | } 210 | 211 | .fanfu_btn { 212 | width: 70px; 213 | height: 25px; 214 | margin-top: 5px; 215 | margin-bottom: 5px; 216 | border-left: 1px solid #C0C0C0; 217 | border-right: 1px solid #6C6C6C; 218 | border-top: 1px solid #C0C0C0; 219 | border-bottom: 1px solid #6C6C6C; 220 | background: white; 221 | border-radius: 8px; 222 | } 223 | 224 | .fu_btn { 225 | width: 80%; 226 | margin-top: 5px; 227 | margin-bottom: 5px; 228 | border-left: 1px solid #C0C0C0; 229 | border-right: 1px solid #6C6C6C; 230 | border-top: 1px solid #C0C0C0; 231 | border-bottom: 1px solid #6C6C6C; 232 | background: white; 233 | border-radius: 8px; 234 | } 235 | 236 | .fanfu_help_btn { 237 | width: 90%; 238 | height: 40px; 239 | margin: 3px; 240 | border-left: 1px solid #C0C0C0; 241 | border-right: 1px solid #6C6C6C; 242 | border-top: 1px solid #C0C0C0; 243 | border-bottom: 1px solid #6C6C6C; 244 | background: white; 245 | font-size: 18px; 246 | border-radius: 8px; 247 | } 248 | 249 | .fanshu_clk { 250 | border: 2px dashed #F00; 251 | } 252 | 253 | .fushu_clk { 254 | border: 2px dashed #F00; 255 | } 256 | 257 | .fanfu_help_clk { 258 | border: 2px dashed #F00; 259 | width: 90%; 260 | font-size: 18px; 261 | } 262 | 263 | .mz_cal_clk { 264 | border: 2px dashed #F00; 265 | width: 60%; 266 | } 267 | 268 | .set_fu { 269 | width: 150px; 270 | height: 30px; 271 | margin: 3px; 272 | border-left: 1px solid #C0C0C0; 273 | border-right: 1px solid #6C6C6C; 274 | border-top: 1px solid #C0C0C0; 275 | border-bottom: 1px solid #6C6C6C; 276 | background: white; 277 | font-size: 18px; 278 | border-radius: 8px; 279 | } 280 | 281 | .fanshu_ok_btn { 282 | width: 80%; 283 | height: 40px; 284 | margin: 3px; 285 | border-left: 1px solid #C0C0C0; 286 | border-right: 1px solid #6C6C6C; 287 | border-top: 1px solid #C0C0C0; 288 | border-bottom: 1px solid #6C6C6C; 289 | background: white; 290 | font-size: 30px; 291 | border-radius: 8px; 292 | } 293 | 294 | #liuju { 295 | bottom: 0px; 296 | position: absolute; 297 | } 298 | 299 | #liuju_btn { 300 | width: 60px; 301 | height: 60px; 302 | margin: 5px; 303 | border-left: 1px solid #C0C0C0; 304 | border-right: 1px solid #6C6C6C; 305 | border-top: 1px solid #C0C0C0; 306 | border-bottom: 1px solid #6C6C6C; 307 | background: green; 308 | border-radius: 20px; 309 | font-size: 36px; 310 | -webkit-appearance: none; 311 | } 312 | 313 | .tab_cls { 314 | height: 390px; 315 | } 316 | 317 | .liuju_icon { 318 | border: 4px solid grey; 319 | border-radius: 40px; 320 | position: absolute; 321 | font-size: 80px; 322 | color: grey; 323 | right: -40px; 324 | top: -40px; 325 | z-index: 100; 326 | font-family: cursive; 327 | line-height: 100%; 328 | margin: 20px; 329 | -webkit-transform: rotate(-30deg); 330 | -moz-transform: rotate(-30deg); 331 | -o-transform: rotate(-30deg); 332 | -ms-transform: rotate(-30deg); 333 | transform: rotate(-30deg); 334 | opacity: 0.4; 335 | filter: alpha(opacity=40); 336 | visibility: hidden; 337 | } 338 | 339 | .liuju_icon_inner { 340 | position: relative; 341 | left: -9px; 342 | } 343 | 344 | .liuju_noting { 345 | border: 4px solid grey; 346 | color: grey; 347 | } 348 | 349 | .liuju_ting { 350 | border: 4px solid crimson; 351 | color: crimson; 352 | opacity: 0.9; 353 | filter: alpha(opacity=90); 354 | } 355 | 356 | #recovery_btn { 357 | width: 60px; 358 | height: 60px; 359 | margin: 5px; 360 | border-left: 1px solid #C0C0C0; 361 | border-right: 1px solid #6C6C6C; 362 | border-top: 1px solid #C0C0C0; 363 | border-bottom: 1px solid #6C6C6C; 364 | border-radius: 20px; 365 | font-size: 36px; 366 | -webkit-appearance: none; 367 | } 368 | 369 | .player_name_edit { 370 | width: 70px; 371 | } 372 | 373 | button { 374 | font-size: 0; 375 | padding: 0; 376 | -webkit-appearance: none; 377 | } 378 | 379 | button span { 380 | font-size: 36px; 381 | margin: 0 -1em; 382 | padding: 0.4em 0.8em; 383 | } 384 | 385 | button.rong_btn span { 386 | font-size: 28px; 387 | margin: 0 -1em; 388 | padding: 0.4em 0.8em; 389 | } 390 | 391 | button.dianpao_btn span { 392 | font-size: 28px; 393 | margin: 0 -1em; 394 | padding: 0.4em 0.8em; 395 | } 396 | 397 | button.corner_btn span { 398 | font-size: 20px; 399 | margin: 0 -1em; 400 | padding: 0.4em 0.8em; 401 | } 402 | 403 | .setting_btn { 404 | float: left; 405 | display: block; 406 | width: 100px; 407 | height: 40px; 408 | border-left: 2px solid #C0C0C0; 409 | border-right: 2px solid #6C6C6C; 410 | border-top: 2px solid #C0C0C0; 411 | border-bottom: 2px solid #6C6C6C; 412 | background: white; 413 | margin: 5px; 414 | border-radius: 20px; 415 | } 416 | 417 | button.setting_btn span { 418 | font-size: 20px; 419 | margin: 0 -1em; 420 | padding: 0.4em 0.8em; 421 | } 422 | 423 | .playerscore_animate { 424 | position: absolute; 425 | font-size: 40px; 426 | color: firebrick; 427 | top: 40px; 428 | left: 30px; 429 | } 430 | 431 | .dice { 432 | position: relative; 433 | float: left; 434 | width: 45px; 435 | height: 45px; 436 | background: url("img/dice.png"); 437 | background-size: 320px, 320px; 438 | background-repeat: no-repeat; 439 | background-position: -0px; 440 | margin-left: 80px; 441 | margin-top: 20px; 442 | } 443 | 444 | #dice_area { 445 | position: relative; 446 | float: left; 447 | height: 50px; 448 | width: 250px; 449 | } 450 | 451 | #myTab0_Content2 { 452 | text-align: left; 453 | font-size: 15px; 454 | } 455 | 456 | .fan_num_desc { 457 | display: inline; 458 | float: left; 459 | margin: 0px; 460 | color: blue 461 | } 462 | 463 | .fan_desc { 464 | float: left; 465 | width: 303px; 466 | } 467 | 468 | .red_star { 469 | color: red; 470 | display: inline; 471 | } 472 | 473 | #chartContainer { 474 | position: absolute; 475 | width: 37%; 476 | height: 37%; 477 | left: 32%; 478 | top: 32%; 479 | // border:1px solid #f00; 480 | } 481 | 482 | #linechartContainer { 483 | float: left; 484 | margin-left: 30px; 485 | } 486 | 487 | #expand_panel_btn { 488 | margin-top: 10px; 489 | float: left; 490 | width: 30px; 491 | height: 400px; 492 | background: white; 493 | border-left: 1px solid #C0C0C0; 494 | border-right: 1px solid #6C6C6C; 495 | border-top: 1px solid #C0C0C0; 496 | border-bottom: 1px solid #6C6C6C; 497 | border-radius: 10px; 498 | position: absolute; 499 | left: 0px; 500 | } 501 | 502 | #myTab0_Content3 { 503 | font-size: 15px; 504 | } -------------------------------------------------------------------------------- /JS/support.js: -------------------------------------------------------------------------------- 1 | function $q(pattern, idx) { //jQuery辅助函数 2 | return $(pattern + ":eq(" + idx + ')'); 3 | } 4 | 5 | function clone(myObj) { //deep copy of object 6 | if (typeof (myObj) != 'object' || myObj == null) return myObj; 7 | var newObj = new Object(); 8 | for (var i in myObj) { 9 | newObj[i] = clone(myObj[i]); 10 | } 11 | return newObj; 12 | } 13 | 14 | function randomOrder(targetArray) { 15 | var arrayLength = targetArray.length 16 | var tempArray1 = new Array(); 17 | for (var i = 0; i < arrayLength; i++) { 18 | tempArray1[i] = i 19 | } 20 | var tempArray2 = new Array(); 21 | for (var i = 0; i < arrayLength; i++) { 22 | tempArray2[i] = tempArray1.splice(Math.floor(Math.random() * tempArray1.length), 1) 23 | } 24 | var tempArray3 = new Array(); 25 | for (var i = 0; i < arrayLength; i++) { 26 | tempArray3[i] = targetArray[tempArray2[i]] 27 | } 28 | return tempArray3 29 | } -------------------------------------------------------------------------------- /JS/switch.css: -------------------------------------------------------------------------------- 1 | .onoffswitch { 2 | position: relative; 3 | float:left; 4 | width: 90px; 5 | -webkit-user-select:none; 6 | -moz-user-select:none; 7 | -ms-user-select: none; 8 | } 9 | .onoffswitch-checkbox { 10 | display: none; 11 | } 12 | .onoffswitch-label { 13 | display: block; overflow: hidden; cursor: pointer; 14 | border: 2px solid #999999; border-radius: 20px; 15 | } 16 | .onoffswitch-inner { 17 | display: block; 18 | width: 200%; 19 | margin-left: -100%; 20 | transition: margin 0.3s ease-in 0s; 21 | } 22 | .onoffswitch-inner:before, .onoffswitch-inner:after { 23 | display: block; float: left; width: 50%; height: 30px; padding: 0; line-height: 30px; 24 | font-size: 14px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold; 25 | box-sizing: border-box; 26 | } 27 | .onoffswitch-inner:before { 28 | content: "点数"; 29 | background-color: #34A7C1; color: #FFFFFF; 30 | } 31 | .onoffswitch-inner:after { 32 | content: "点差"; 33 | padding-right: 20px; 34 | background-color: #EEEEEE; color: #999999; 35 | text-align: right; 36 | } 37 | .onoffswitch-switch { 38 | display: block; width: 18px; margin: 6px; 39 | background: #FFFFFF; 40 | position: absolute; top: 0; bottom: 0; 41 | right: 56px; 42 | border: 2px solid #999999; border-radius: 20px; 43 | transition: all 0.3s ease-in 0s; 44 | } 45 | .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner { 46 | margin-left: 0; 47 | } 48 | .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch { 49 | right: 0px; 50 | } -------------------------------------------------------------------------------- /JS/tab.js: -------------------------------------------------------------------------------- 1 | function nTabs(thisObj, Num) { 2 | if (thisObj.className == "active") return; 3 | var tabObj = thisObj.parentNode.id; 4 | var tabList = document.getElementById(tabObj).getElementsByTagName("li"); 5 | for (var i = 0; i < tabList.length; i++) { 6 | if (i == Num) { 7 | thisObj.className = "active"; 8 | document.getElementById(tabObj + "_Content" + i).style.display = "block"; 9 | } else { 10 | tabList[i].className = "normal"; 11 | document.getElementById(tabObj + "_Content" + i).style.display = "none"; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /JS/tabstyle.css: -------------------------------------------------------------------------------- 1 | .nTab { 2 | float: left; 3 | position: relative; 4 | margin: 0 auto; 5 | border-bottom: 1px #C7C7CD solid; 6 | /*background: #d5d5d5;*/ 7 | background-position: left; 8 | background-repeat: repeat-y; 9 | margin-bottom: 2px; 10 | left:-0px; 11 | } 12 | .nTab .TabTitle { 13 | clear: both; 14 | height: 26px; 15 | overflow: hidden; 16 | } 17 | .nTab .TabTitle ul { 18 | margin: 0; 19 | padding: 0; 20 | } 21 | .nTab .TabTitle li { 22 | float: left; 23 | width: 94px; 24 | cursor: pointer; 25 | padding-top: 6px; 26 | padding-right: 0px; 27 | padding-left: 0px; 28 | padding-bottom: 7px; 29 | list-style-type: none; 30 | } 31 | .nTab .TabTitle .active { 32 | background: url(./img/tab_bg1.gif) left -25px no-repeat; 33 | border-left: 1px #C7C7CD solid; 34 | border-top: 1px #C7C7CD solid; 35 | border-bottom: 1px #fff solid; 36 | } 37 | .nTab .TabTitle .normal { 38 | background: url(./img/tab_bg1.gif); 39 | border-top: 1px #C7C7CD solid; 40 | border-bottom: 1px #C7C7CD solid; 41 | } 42 | .nTab .TabContent { 43 | width: auto; 44 | background: #fff; 45 | margin: 0px auto; 46 | 47 | border-right: 1px #C7C7CD solid; 48 | border-left: 1px #C7C7CD solid; 49 | } 50 | .none { 51 | display: none; 52 | } 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mahosyojyo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Majong-AutoScore 2 | ================ 3 | 4 | 日麻自动算分器 5 | 6 | #开坑申明 7 | 8 | 目标:实现一个手麻日麻辅助软件。 9 | 10 | 主要是两个版本 11 | 12 | * JS版本,由Vespa完成 13 | * iOS版本,由Fredfx完成 14 | * Android版本,再说吧,呵呵 15 | 16 | 人员: 17 | * Vespa 18 | * Fredfx 19 | * Yuki(测试阶段使用用户) 20 | 21 | ================ 22 | ###文件说明: 23 | >./iOS/: 24 | >iOS版 25 | 26 | >./Andriod/: 27 | >安卓版 28 | 29 | >./JS/: 30 | >Js版本 31 | 32 | >./Design.md: 33 | >设计思路和功能完善 34 | -------------------------------------------------------------------------------- /iOS/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/.DS_Store -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/.DS_Store -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator.xcodeproj/project.xcworkspace/xcuserdata/fredfx.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator.xcodeproj/project.xcworkspace/xcuserdata/fredfx.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator.xcodeproj/xcuserdata/fredfx.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator.xcodeproj/xcuserdata/fredfx.xcuserdatad/xcschemes/MahjongScoreCalculator.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator.xcodeproj/xcuserdata/fredfx.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MahjongScoreCalculator.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C8E211FD1C3817160007CAA7 16 | 17 | primary 18 | 19 | 20 | C8E212161C3817160007CAA7 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/.DS_Store -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/2. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/2. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "MMSCPlayerNameAndSeatController.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | 21 | MMSCPlayerNameAndSeatController *nameAndSeatController = [[MMSCPlayerNameAndSeatController alloc] init]; 22 | 23 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 24 | [self.window setBackgroundColor:[UIColor clearColor]]; 25 | self.window.rootViewController = nameAndSeatController; 26 | [self.window makeKeyAndVisible]; 27 | 28 | return YES; 29 | } 30 | 31 | - (void)applicationWillResignActive:(UIApplication *)application { 32 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 33 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 34 | } 35 | 36 | - (void)applicationDidEnterBackground:(UIApplication *)application { 37 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 38 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 39 | } 40 | 41 | - (void)applicationWillEnterForeground:(UIApplication *)application { 42 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 43 | } 44 | 45 | - (void)applicationDidBecomeActive:(UIApplication *)application { 46 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 47 | } 48 | 49 | - (void)applicationWillTerminate:(UIApplication *)application { 50 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x", 7 | "filename" : "Icon-Small@2x.png" 8 | }, 9 | { 10 | "idiom" : "iphone", 11 | "size" : "29x29", 12 | "scale" : "3x", 13 | "filename" : "Icon-Small@3x.png" 14 | }, 15 | { 16 | "idiom" : "iphone", 17 | "size" : "40x40", 18 | "scale" : "2x", 19 | "filename" : "Icon-40@2x.png" 20 | }, 21 | { 22 | "idiom" : "iphone", 23 | "size" : "40x40", 24 | "scale" : "3x", 25 | "filename" : "Icon-40@3x.png" 26 | }, 27 | { 28 | "idiom" : "iphone", 29 | "size" : "60x60", 30 | "scale" : "2x", 31 | "filename" : "Icon-60@2x.png" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "3x", 37 | "filename" : "Icon-60@3x.png" 38 | }, 39 | { 40 | "idiom" : "ipad", 41 | "size" : "29x29", 42 | "scale" : "1x", 43 | "filename" : "Icon-Small.png" 44 | }, 45 | { 46 | "idiom" : "ipad", 47 | "size" : "29x29", 48 | "scale" : "2x", 49 | "filename" : "Icon-Small@2x.png" 50 | }, 51 | { 52 | "idiom" : "ipad", 53 | "size" : "40x40", 54 | "scale" : "1x", 55 | "filename" : "Icon-40.png" 56 | }, 57 | { 58 | "idiom" : "ipad", 59 | "size" : "40x40", 60 | "scale" : "2x", 61 | "filename" : "Icon-40@2x.png" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "76x76", 66 | "scale" : "1x", 67 | "filename" : "Icon-76.png" 68 | }, 69 | { 70 | "idiom" : "ipad", 71 | "size" : "76x76", 72 | "scale" : "2x", 73 | "filename" : "Icon-76@2x.png" 74 | }, 75 | { 76 | "idiom" : "ipad", 77 | "size" : "83.5x83.5", 78 | "scale" : "2x", 79 | "filename" : "Icon-83.5@2x.png" 80 | } 81 | ], 82 | "info" : { 83 | "version" : 1, 84 | "author" : "makeappicon" 85 | } 86 | } -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/Brand Assets.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "minimum-system-version" : "7.0", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "orientation" : "portrait", 11 | "idiom" : "iphone", 12 | "minimum-system-version" : "7.0", 13 | "subtype" : "retina4", 14 | "scale" : "2x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/MMSCGame.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCGame.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/2. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MMSCRound.h" 11 | 12 | @class MMSCRoundResult; 13 | @class MMSCPlayer; 14 | 15 | @interface MMSCGame : NSObject 16 | 17 | // 玩家们 18 | @property(nonatomic, strong) NSArray *players; 19 | // 庄家索引 20 | @property(nonatomic, assign) NSInteger oyaIndex; 21 | // 对局们 22 | @property(nonatomic, strong) NSMutableArray *rounds; 23 | 24 | // 初始化游戏 25 | - (void)gameInit; 26 | 27 | // 随机座位 28 | - (void)randomSeat; 29 | 30 | // 结束该局 31 | - (void)endCurrentRoundWithResult:(MMSCRoundResult *)result; 32 | 33 | // 判断对局是否结束 34 | - (BOOL)isGameEnded; 35 | 36 | // 立直 37 | - (void)richiAtPlayerIndex:(NSUInteger)playerIndex; 38 | 39 | // 取消立直 40 | - (void)cancelRichiAtPlayerIndex:(NSUInteger)playerIndex; 41 | 42 | // 获取庄家 43 | - (MMSCPlayer *)getOYA; 44 | 45 | // 获取某个玩家 46 | - (MMSCPlayer *)getPlayerAtIndex:(NSInteger)index; 47 | 48 | // 获取当前的一局 49 | - (MMSCRound *)currentRound; 50 | 51 | // 排位 52 | - (NSArray *)playerRank; 53 | 54 | // 撤销上一局 55 | - (void)cancelLastRound; 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/MMSCGame.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCGame.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/2. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCGame.h" 10 | #import "MMSCRound.h" 11 | #import "MMSCRoundResult.h" 12 | 13 | @implementation MMSCGame 14 | 15 | - (void)randomSeat { 16 | 17 | NSMutableArray *temp = [NSMutableArray arrayWithArray:self.players]; 18 | 19 | for (int i = 0; i < 4; i++) { 20 | int index = rand() % (4 - i); 21 | 22 | [temp exchangeObjectAtIndex:i withObjectAtIndex:i + index]; 23 | } 24 | 25 | self.players = [NSArray arrayWithArray:temp]; 26 | 27 | [self gameInit]; 28 | } 29 | 30 | - (void)gameInit { 31 | 32 | self.oyaIndex = 0; 33 | 34 | //分配下风位 35 | for (int i = 0; i < 4; i++) { 36 | MMSCPlayer *player = (MMSCPlayer *)self.players[i]; 37 | player.wind = (MMSCWind)(MMSCWindEast + i); 38 | } 39 | 40 | self.rounds = [NSMutableArray array]; 41 | MMSCRound *firstRound = [MMSCRound new]; 42 | firstRound.roundWind = MMSCWindEast; 43 | firstRound.roundNumeber = 1; 44 | 45 | [self.rounds addObject:firstRound]; 46 | } 47 | 48 | - (void)endCurrentRoundWithResult:(MMSCRoundResult *)result { 49 | 50 | MMSCRound *currentRound = [self currentRound]; 51 | [currentRound setResult:result]; 52 | 53 | // 结束该局,分配分数 54 | [currentRound endRoundWithPlayers:self.players]; 55 | 56 | if (![self isGameEnded]) { 57 | [self setNextRound:currentRound]; 58 | } 59 | } 60 | 61 | - (void)setNextRound:(MMSCRound *)currentRound { 62 | MMSCRoundResult *currentResult = [currentRound result]; 63 | MMSCRound *nextRound = [MMSCRound new]; 64 | 65 | BOOL isDraw = [currentResult isDraw]; 66 | BOOL changeOYA = [currentResult changeOYA:self.oyaIndex]; 67 | currentRound.oyaChanged = changeOYA; 68 | 69 | // 设置场风和局数,换庄 70 | if (!changeOYA) { 71 | nextRound.roundWind = currentRound.roundWind; 72 | nextRound.roundNumeber = currentRound.roundNumeber; 73 | } else { 74 | [self setNextOYAAndPlayersWind]; 75 | [self setNextWindAndRoundNumber:nextRound currentRound:currentRound]; 76 | } 77 | 78 | // 本场数 79 | if (!changeOYA || isDraw) { 80 | nextRound.bonbanNumber = currentRound.bonbanNumber + 1; 81 | } 82 | 83 | // 立直棒处理 84 | if (isDraw) { 85 | nextRound.richiScore = currentRound.richiScore; 86 | } 87 | 88 | [self.rounds addObject:nextRound]; 89 | } 90 | 91 | - (void)setNextOYAAndPlayersWind { 92 | 93 | self.oyaIndex++; 94 | 95 | if (self.oyaIndex >= 4) { 96 | self.oyaIndex = 0; 97 | } 98 | 99 | for (MMSCPlayer *player in self.players) { 100 | player.wind = [self previousWind:player.wind]; 101 | } 102 | } 103 | 104 | - (void)setPreviousOYAAndPlayersWind { 105 | self.oyaIndex--; 106 | 107 | if (self.oyaIndex < 0) { 108 | self.oyaIndex = 3; 109 | } 110 | 111 | for (MMSCPlayer *player in self.players) { 112 | player.wind = [self nextWind:player.wind]; 113 | } 114 | } 115 | 116 | - (void)setNextWindAndRoundNumber:(MMSCRound *)nextRound currentRound:(MMSCRound *)currentRound { 117 | NSInteger nextRoundNumber = currentRound.roundNumeber + 1; 118 | MMSCWind nextRoundWind = currentRound.roundWind; 119 | 120 | if (nextRoundNumber > 4) { 121 | nextRoundWind = [self nextWind:nextRoundWind]; 122 | nextRoundNumber = 1; 123 | } 124 | 125 | nextRound.roundWind = nextRoundWind; 126 | nextRound.roundNumeber = nextRoundNumber; 127 | } 128 | 129 | - (MMSCWind)nextWind:(MMSCWind)currentWind { 130 | MMSCWind result = currentWind + 1; 131 | 132 | if (result > MMSCWindNorth) { 133 | result = MMSCWindEast; 134 | } 135 | 136 | return result; 137 | } 138 | 139 | - (MMSCWind)previousWind:(MMSCWind)currentWind { 140 | MMSCWind result = currentWind - 1; 141 | 142 | if (result < 0) { 143 | result = MMSCWindNorth; 144 | } 145 | 146 | return result; 147 | } 148 | 149 | - (BOOL)isGameEnded { 150 | 151 | __block NSInteger topIndex = 0; 152 | __block BOOL result = NO; 153 | 154 | [self.players enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 155 | MMSCPlayer *player = (MMSCPlayer *)obj; 156 | 157 | if (player.score < 0) { 158 | result = YES; 159 | *stop = YES; 160 | return; 161 | } 162 | 163 | MMSCPlayer *topPlayer = (MMSCPlayer *)self.players[topIndex]; 164 | if (player.score >= topPlayer.score) { 165 | topIndex = idx; 166 | } 167 | }]; 168 | 169 | if (result) { // 有人被飞 170 | return YES; 171 | } 172 | 173 | MMSCRound *currentRound = [self currentRound]; 174 | MMSCPlayer *topPlayer = (MMSCPlayer *)self.players[topIndex]; 175 | BOOL isAllLast = (currentRound.roundWind == MMSCWindSouth && currentRound.roundNumeber >= 4) || currentRound.roundWind > MMSCWindSouth; 176 | 177 | if (!isAllLast || (isAllLast && topPlayer.score < 30000)) { // 不是all last 或者 没人上三万分(可能要西入) 178 | return NO; 179 | } 180 | 181 | if ([currentRound.result changeOYA:self.oyaIndex]) { 182 | return YES; 183 | } 184 | 185 | return topIndex != self.oyaIndex; 186 | } 187 | 188 | - (void)richiAtPlayerIndex:(NSUInteger)playerIndex { 189 | MMSCRound *currentRound = [self currentRound]; 190 | NSAssert(playerIndex < 4, @"这是什么鬼?!"); 191 | 192 | MMSCPlayer *richiPlayer = (MMSCPlayer *)self.players[playerIndex]; 193 | [currentRound richiAtPlayer:richiPlayer index:playerIndex]; 194 | } 195 | 196 | - (void)cancelRichiAtPlayerIndex:(NSUInteger)playerIndex { 197 | MMSCRound *currentRound = [self currentRound]; 198 | NSAssert(playerIndex < 4, @"这是什么鬼?!"); 199 | 200 | MMSCPlayer *richiPlayer = (MMSCPlayer *)self.players[playerIndex]; 201 | [currentRound cancelRichiAtPlayer:richiPlayer index:playerIndex]; 202 | } 203 | 204 | - (MMSCPlayer *)getOYA { 205 | return (MMSCPlayer *)self.players[self.oyaIndex]; 206 | } 207 | 208 | - (MMSCPlayer *)getPlayerAtIndex:(NSInteger)index { 209 | return (MMSCPlayer *)self.players[index]; 210 | } 211 | 212 | - (MMSCRound *)currentRound { 213 | return [self.rounds lastObject]; 214 | } 215 | 216 | - (NSArray *)playerRank { 217 | NSArray *sortedPlayers = [self.players sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { 218 | MMSCPlayer *player1 = (MMSCPlayer *)obj1; 219 | MMSCPlayer *player2 = (MMSCPlayer *)obj2; 220 | 221 | if (player1.score == player2.score) { 222 | return NSOrderedSame; 223 | } 224 | 225 | if (player1.score < player2.score) { 226 | return NSOrderedDescending; 227 | } 228 | 229 | return NSOrderedAscending; 230 | }]; 231 | 232 | return sortedPlayers; 233 | } 234 | 235 | - (void)cancelLastRound { 236 | if (![self isGameEnded]) { 237 | // 当前未完的一局 238 | MMSCRound *currentRound = [self currentRound]; 239 | [self.rounds removeObject:currentRound]; 240 | } 241 | 242 | // 真正要取消的一局 243 | MMSCRound *roundToCancel = [self currentRound]; 244 | [self.rounds removeObject:roundToCancel]; 245 | 246 | // 恢复分数 247 | NSArray *scoreChanges = [roundToCancel roundScoreChanges]; 248 | for (int i = 0; i < 4; i++) { 249 | NSInteger scoreChange = [scoreChanges[i] integerValue]; 250 | MMSCPlayer *player = self.players[i]; 251 | [player decreaseScore:scoreChange]; 252 | } 253 | 254 | // 恢复风位 255 | BOOL changeOYA = roundToCancel.oyaChanged; 256 | if (changeOYA) { 257 | [self setPreviousOYAAndPlayersWind]; 258 | } 259 | MMSCRound *currentRound = [self currentRound]; 260 | if (!currentRound) { 261 | [self gameInit]; 262 | return; 263 | } 264 | if (currentRound.oyaChanged) { // 先恢复到结算之前的状态 265 | [self setPreviousOYAAndPlayersWind]; 266 | } 267 | 268 | [self setNextRound:currentRound]; // 重新结算下风位 269 | } 270 | 271 | @end 272 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/MMSCGameManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCGameManager.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/2/11. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MMSCGameManager : NSObject 12 | 13 | + (MMSCGameManager *)instance; 14 | 15 | // 初始化 16 | - (void)initGameWithPlayers:(NSArray *)playerNames; 17 | 18 | // 随机座位,初始化并返回结果 19 | - (NSArray *)randomSeatWithPlayers:(NSArray *)playerNames; 20 | 21 | // 局数 22 | - (NSInteger)roundCount; 23 | 24 | // 当前局名 25 | - (NSString *)currentRoundName; 26 | 27 | // 当前庄家 28 | - (NSString *)currentOYAName; 29 | 30 | // 当前OYAIndex 31 | - (NSUInteger)currentOYAIndex; 32 | 33 | // 玩家的名称 34 | - (NSArray *)playerNames; 35 | 36 | // 玩家的风位 37 | - (NSArray *)currentPlayerWinds; 38 | 39 | // 玩家的分数 40 | - (NSArray *)currentPlayerScores; 41 | 42 | // 某一局的名称 43 | - (NSString *)roundNameAtIndex:(NSUInteger)index; 44 | 45 | // 某一局的分数变化 46 | - (NSArray *)roundScoreChangesAtIndex:(NSUInteger)index; 47 | 48 | // 当前局的分数变化 49 | - (NSArray *)currentRoundScoreChanges; 50 | 51 | // 当前局中立直的人 52 | - (NSArray *)currentRichiPlayers; 53 | 54 | // 立直 55 | - (void)richiAtPlayers:(NSArray *)richiPlayerIndexes; 56 | 57 | // 当前的立直棒数 58 | - (NSInteger)currentRichiCount; 59 | 60 | // 自摸 61 | - (void)tsumoAtPlayer:(NSUInteger)playerIndex fan:(NSInteger)fan fu:(NSInteger)fu; 62 | 63 | // 放铳 64 | - (void)ronAtPlayers:(NSArray *)winners loser:(NSUInteger)loserIndex fan:(NSArray *)fan fu:(NSArray *)fu; 65 | 66 | // 流局 67 | - (void)drawForType:(NSInteger)type players:(NSArray *)players oyaTenpai:(BOOL)oyaTenpai; 68 | 69 | // 游戏是否结束 70 | - (BOOL)isGameEnded; 71 | 72 | // 玩家排位 73 | - (NSArray *)sortedPlayers; 74 | 75 | // 撤销上一局 76 | - (void)cancelLastRound; 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/MMSCGameManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCGameManager.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/2/11. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCGameManager.h" 10 | #import "MMSCGame.h" 11 | #import "MMSCPlayer.h" 12 | #import "MMSCTsuMoResult.h" 13 | #import "MMSCTwoWinnerResult.h" 14 | #import "MMSCNormalWinResult.h" 15 | #import "MMSCDrawManganResult.h" 16 | 17 | @interface MMSCGameManager () 18 | 19 | @property (nonatomic, strong) MMSCGame *game; 20 | 21 | @end 22 | 23 | @implementation MMSCGameManager 24 | 25 | + (MMSCGameManager *)instance { 26 | static MMSCGameManager *instance = nil; 27 | static dispatch_once_t onceToken; 28 | dispatch_once(&onceToken, ^{ 29 | instance = [[MMSCGameManager alloc] init]; 30 | }); 31 | 32 | return instance; 33 | } 34 | 35 | - (instancetype)init { 36 | if (self = [super init]) { 37 | _game = [[MMSCGame alloc] init]; 38 | } 39 | 40 | return self; 41 | } 42 | 43 | - (void)initGameWithPlayers:(NSArray *)playerNames { 44 | self.game.players = [self createPlayersWithNames:playerNames]; 45 | 46 | [self.game gameInit]; 47 | } 48 | 49 | - (NSArray *)randomSeatWithPlayers:(NSArray *)playerNames { 50 | 51 | self.game.players = [self createPlayersWithNames:playerNames]; 52 | [self.game randomSeat]; 53 | 54 | return [self playerNames]; 55 | } 56 | 57 | - (NSArray *)createPlayersWithNames:(NSArray *)playerNames { 58 | NSMutableArray *players = [NSMutableArray array]; 59 | 60 | for (NSString *playerName in playerNames) { 61 | MMSCPlayer *player = [[MMSCPlayer alloc] initWithName:playerName]; 62 | [players addObject:player]; 63 | } 64 | 65 | return [players copy]; 66 | } 67 | 68 | - (NSInteger)roundCount { 69 | return self.game.rounds.count; 70 | } 71 | 72 | - (NSString *)currentRoundName { 73 | MMSCRound *currentRound = [self.game currentRound]; 74 | return [currentRound roundName]; 75 | } 76 | 77 | - (NSString *)currentOYAName { 78 | MMSCPlayer *oya = [self.game getOYA]; 79 | return oya.name; 80 | } 81 | 82 | - (NSUInteger)currentOYAIndex { 83 | return self.game.oyaIndex; 84 | } 85 | 86 | - (NSArray *)playerNames { 87 | NSMutableArray *playerNames = [NSMutableArray array]; 88 | for (MMSCPlayer *player in self.game.players) { 89 | [playerNames addObject:player.name]; 90 | } 91 | 92 | return [playerNames copy]; 93 | } 94 | 95 | - (NSArray *)currentPlayerWinds { 96 | NSMutableArray *playerWinds = [NSMutableArray array]; 97 | for (MMSCPlayer *player in self.game.players) { 98 | [playerWinds addObject:[self convertWindEnumToCharacter:player.wind]]; 99 | } 100 | 101 | return playerWinds; 102 | } 103 | 104 | - (NSArray *)currentPlayerScores { 105 | NSMutableArray *playerScores = [NSMutableArray array]; 106 | for (MMSCPlayer *player in self.game.players) { 107 | [playerScores addObject:@(player.score)]; 108 | } 109 | 110 | return playerScores; 111 | } 112 | 113 | - (NSString *)convertWindEnumToCharacter:(MMSCWind)wind { 114 | switch (wind) { 115 | case MMSCWindEast: 116 | return @"東"; 117 | case MMSCWindSouth: 118 | return @"南"; 119 | case MMSCWindWest: 120 | return @"西"; 121 | case MMSCWindNorth: 122 | return @"北"; 123 | } 124 | } 125 | 126 | - (NSString *)roundNameAtIndex:(NSUInteger)index { 127 | NSAssert(index < self.game.rounds.count, @"round index out of bound"); 128 | 129 | MMSCRound *round = self.game.rounds[index]; 130 | return round.roundName; 131 | } 132 | 133 | - (NSArray *)roundScoreChangesAtIndex:(NSUInteger)index { 134 | NSAssert(index < self.game.rounds.count, @"round index out of bound"); 135 | 136 | MMSCRound *round = self.game.rounds[index]; 137 | return [round roundScoreChanges]; 138 | } 139 | 140 | - (NSArray *)currentRoundScoreChanges { 141 | MMSCRound *round = [self.game currentRound]; 142 | return [round roundScoreChanges]; 143 | } 144 | 145 | - (NSArray *)currentRichiPlayers { 146 | MMSCRound *currentRound = [self.game currentRound]; 147 | return currentRound.richiPlayerIndexes; 148 | } 149 | 150 | - (void)richiAtPlayers:(NSArray *)richiPlayerIndexes { 151 | NSMutableArray *currentRichiPlayers = [[self currentRichiPlayers] mutableCopy]; 152 | for (NSNumber *playerIndex in richiPlayerIndexes) { 153 | if ([currentRichiPlayers containsObject:playerIndex]) { // 之前已经立直过了 154 | [currentRichiPlayers removeObject:playerIndex]; 155 | } else { 156 | [self.game richiAtPlayerIndex:playerIndex.unsignedIntegerValue]; 157 | } 158 | } 159 | 160 | if (currentRichiPlayers.count > 0) { // 有取消的情况发生 161 | for (NSNumber *playerIndex in currentRichiPlayers) { 162 | [self.game cancelRichiAtPlayerIndex:playerIndex.unsignedIntegerValue]; 163 | } 164 | } 165 | } 166 | 167 | - (NSInteger)currentRichiCount { 168 | NSInteger currentRichiScore = [self.game currentRound].richiScore; 169 | return currentRichiScore / 1000; 170 | } 171 | 172 | - (void)tsumoAtPlayer:(NSUInteger)playerIndex fan:(NSInteger)fan fu:(NSInteger)fu { 173 | 174 | MMSCTsuMoResult *tsumoResult = [[MMSCTsuMoResult alloc] init]; 175 | tsumoResult.winnerIndex = playerIndex; 176 | tsumoResult.fan = fan; 177 | tsumoResult.fu = fu; 178 | 179 | [self.game endCurrentRoundWithResult:tsumoResult]; 180 | } 181 | 182 | - (void)ronAtPlayers:(NSArray *)winners loser:(NSUInteger)loserIndex fan:(NSArray *)fan fu:(NSArray *)fu { 183 | MMSCRoundResult *result = nil; 184 | if (winners.count > 1) { 185 | MMSCTwoWinnerResult *twoWinResult = [[MMSCTwoWinnerResult alloc] init]; 186 | twoWinResult.winner1Index = [winners[0] unsignedIntegerValue]; 187 | twoWinResult.winner2Index = [winners[1] unsignedIntegerValue]; 188 | 189 | twoWinResult.fanOfWiiner1 = [fan[0] integerValue]; 190 | twoWinResult.fanOfWinner2 = [fan[1] integerValue]; 191 | 192 | twoWinResult.fuOfWinner1 = [fu[0] integerValue]; 193 | twoWinResult.fuOfWinner2 = [fu[1] integerValue]; 194 | 195 | twoWinResult.payerIndex = loserIndex; 196 | result = twoWinResult; 197 | } else { 198 | MMSCNormalWinResult *normalResult = [[MMSCNormalWinResult alloc] init]; 199 | normalResult.winnerIndex = [winners[0] unsignedIntegerValue]; 200 | normalResult.fan = [fan[0] integerValue]; 201 | normalResult.fu = [fu[0] integerValue]; 202 | 203 | normalResult.payerIndex = loserIndex; 204 | result= normalResult; 205 | } 206 | 207 | [self.game endCurrentRoundWithResult:result]; 208 | } 209 | 210 | - (void)drawForType:(NSInteger)type players:(NSArray *)players oyaTenpai:(BOOL)oyaTenpai { 211 | MMSCRoundResult *result = nil; 212 | if (type == MMSCDrawType_DrawMangan) { 213 | MMSCDrawManganResult *drawMangan = [[MMSCDrawManganResult alloc] init]; 214 | drawMangan.type = type; 215 | drawMangan.manganPlayerIndexes = players; 216 | drawMangan.oyaTenpai = oyaTenpai; 217 | result = drawMangan; 218 | } else { 219 | MMSCDrawResult *drawResult = [[MMSCDrawResult alloc] init]; 220 | drawResult.type = type; 221 | drawResult.tenpaiPlayerIndexes = players; 222 | result = drawResult; 223 | } 224 | 225 | [self.game endCurrentRoundWithResult:result]; 226 | } 227 | 228 | - (BOOL)isGameEnded { 229 | return [self.game isGameEnded]; 230 | } 231 | 232 | - (NSArray *)sortedPlayers { 233 | NSArray *sortedPlayers = [self.game playerRank]; 234 | 235 | NSMutableArray *playerNames = [NSMutableArray array]; 236 | for (MMSCPlayer *player in sortedPlayers) { 237 | [playerNames addObject:player.name]; 238 | } 239 | 240 | return [playerNames copy]; 241 | } 242 | 243 | - (void)cancelLastRound { 244 | [self.game cancelLastRound]; 245 | } 246 | 247 | @end 248 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/MMSCPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCPlayer.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/2. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MMSCUtil.h" 11 | 12 | @interface MMSCPlayer : NSObject 13 | 14 | // 名字 15 | @property(nonatomic, strong) NSString *name; 16 | // 风位 17 | @property(nonatomic, assign) MMSCWind wind; 18 | // 分数 19 | @property(nonatomic, assign) NSInteger score; 20 | 21 | // 初始化 22 | - (instancetype)initWithName:(NSString *)playerName; 23 | 24 | // 加分 25 | - (void)increaseScore:(NSInteger)increment; 26 | 27 | // 扣分 28 | - (void)decreaseScore:(NSInteger)decrement; 29 | 30 | // 是不是庄家 31 | - (BOOL)isOYA; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/MMSCPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCPlayer.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/2. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCPlayer.h" 10 | 11 | @implementation MMSCPlayer 12 | 13 | - (instancetype)initWithName:(NSString *)playerName { 14 | if (self = [self init]) { 15 | _name = playerName; 16 | _score = 25000; 17 | } 18 | return self; 19 | } 20 | 21 | - (void)increaseScore:(NSInteger)increment { 22 | self.score += increment; 23 | } 24 | 25 | - (void)decreaseScore:(NSInteger)decrement { 26 | self.score -= decrement; 27 | } 28 | 29 | - (BOOL)isOYA { 30 | return self.wind == MMSCWindEast; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/MMSCRound.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCRound.h 3 | // MahjongScoreCalculator 4 | // 5 | // 一场游戏中的一局 6 | // 7 | // Created by fredfx on 16/1/2. 8 | // Copyright © 2016年 fredfx. All rights reserved. 9 | // 10 | 11 | #import 12 | #import "MMSCUtil.h" 13 | 14 | @class MMSCRoundResult; 15 | @class MMSCPlayer; 16 | 17 | @interface MMSCRound : NSObject 18 | 19 | // 场风 20 | @property(nonatomic, assign) MMSCWind roundWind; 21 | 22 | // 局数(1,2,3,4) 23 | @property(nonatomic, assign) NSInteger roundNumeber; 24 | 25 | // 本场数 26 | @property(nonatomic, assign) NSInteger bonbanNumber; 27 | 28 | // 结果 29 | @property(nonatomic, strong) MMSCRoundResult *result; 30 | 31 | // 场上的立直棒 32 | @property(nonatomic, assign) NSInteger richiScore; 33 | 34 | // 本场立直的玩家 35 | @property(nonatomic, strong) NSMutableArray *richiPlayerIndexes; 36 | 37 | // 换庄 38 | @property(nonatomic, assign) BOOL oyaChanged; 39 | 40 | // 立直 41 | - (void)richiAtPlayer:(MMSCPlayer *)player index:(NSUInteger)index; 42 | 43 | // 取消立直 44 | - (void)cancelRichiAtPlayer:(MMSCPlayer *)player index:(NSUInteger)index; 45 | 46 | // 结束该局 47 | - (void)endRoundWithPlayers:(NSArray *)players; 48 | 49 | // 该局的分数变化 50 | - (NSArray *)roundScoreChanges; 51 | 52 | // 此局名称 53 | - (NSString *)roundName; 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/MMSCRound.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCRound.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/2. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCRound.h" 10 | #import "MMSCPlayer.h" 11 | #import "MMSCRoundResult.h" 12 | 13 | @implementation MMSCRound 14 | 15 | - (instancetype)init { 16 | if (self = [super init]) { 17 | _richiPlayerIndexes = [NSMutableArray array]; 18 | } 19 | return self; 20 | } 21 | 22 | - (void)richiAtPlayer:(MMSCPlayer *)player index:(NSUInteger)index { 23 | self.richiScore += 1000; 24 | [self.richiPlayerIndexes addObject:@(index)]; 25 | 26 | [player decreaseScore:1000]; 27 | } 28 | 29 | - (void)cancelRichiAtPlayer:(MMSCPlayer *)player index:(NSUInteger)index { 30 | NSNumber *indexNum = @(index); 31 | if (![self.richiPlayerIndexes containsObject:indexNum]) { 32 | // 没有立直过的不处理 33 | return; 34 | } 35 | 36 | self.richiScore -= 1000; 37 | [self.richiPlayerIndexes removeObject:indexNum]; 38 | 39 | [player increaseScore:1000]; 40 | } 41 | 42 | - (void)endRoundWithPlayers:(NSArray *)players { 43 | if (!self.result) { 44 | NSAssert(NO, @"未设置本局结果"); 45 | } 46 | 47 | [self.result recordRichiChangeWithRichiPlayers:self.richiPlayerIndexes]; 48 | [self.result assignScoreWithPlayers:players bonban:self.bonbanNumber richiScore:self.richiScore]; 49 | } 50 | 51 | - (NSString *)roundName { 52 | NSString *wind = [MMSCUtil convertWindEnumToCharacter:self.roundWind]; 53 | NSString *rounNumber = [MMSCUtil convertToCharacterWithNumber:self.roundNumeber]; 54 | 55 | if (self.bonbanNumber <= 0) { 56 | return [NSString stringWithFormat:@"%@%@局", wind, rounNumber]; 57 | } 58 | 59 | return [NSString stringWithFormat:@"%@%@局 %zd本场", wind, rounNumber, self.bonbanNumber]; 60 | } 61 | 62 | - (NSArray *)roundScoreChanges { 63 | if (self.result) { // 已经设置了结果并结算完了就直接返回 64 | return [self.result.scoreChanges copy]; 65 | } 66 | 67 | NSMutableArray *scoreChanges = [NSMutableArray arrayWithArray:@[@(0), @(0), @(0), @(0)]]; 68 | for (NSNumber *index in self.richiPlayerIndexes) { 69 | scoreChanges[index.unsignedIntegerValue] = @(-1000); 70 | } 71 | 72 | return [scoreChanges copy]; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/MMSCUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCUtil.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/2. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, MMSCWind) { 12 | MMSCWindEast = 0, 13 | MMSCWindSouth, 14 | MMSCWindWest, 15 | MMSCWindNorth 16 | }; 17 | 18 | #define PLAYER_NUMBER 4 19 | 20 | @interface MMSCUtil : NSObject 21 | 22 | // 风名 23 | + (NSString *)convertWindEnumToCharacter:(MMSCWind)wind; 24 | 25 | // 转换为中文 26 | + (NSString *)convertToCharacterWithNumber:(NSInteger)number; 27 | 28 | // 取到100 29 | + (NSInteger)increaseToHundredForScore:(NSInteger)score; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/MMSCUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCUtil.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/2. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCUtil.h" 10 | 11 | @implementation MMSCUtil 12 | 13 | + (NSString *)convertWindEnumToCharacter:(MMSCWind)wind { 14 | switch (wind) { 15 | case MMSCWindEast: 16 | return @"东"; 17 | case MMSCWindSouth: 18 | return @"南"; 19 | case MMSCWindWest: 20 | return @"西"; 21 | case MMSCWindNorth: 22 | return @"北"; 23 | } 24 | } 25 | 26 | + (NSString *)convertToCharacterWithNumber:(NSInteger)number { 27 | 28 | static NSArray *unitCharacter; 29 | static dispatch_once_t unitOnce; 30 | dispatch_once(&unitOnce, ^{ 31 | unitCharacter = @[@"一",@"二",@"三",@"四",@"五",@"六",@"七",@"八",@"九"]; 32 | }); 33 | 34 | static NSArray *tenAndHundredCharacter; 35 | static dispatch_once_t tenAndHundredOnce; 36 | dispatch_once(&tenAndHundredOnce, ^{ 37 | tenAndHundredCharacter = @[@"十",@"百",@"千",@"万"]; 38 | }); 39 | 40 | NSMutableString *result = [[NSMutableString alloc] init]; 41 | 42 | NSInteger tensIndex = 0; 43 | NSInteger unit = number % 10; 44 | NSInteger temp = number; 45 | 46 | while (temp != 0) { 47 | temp = temp / 10; 48 | 49 | if (unit != 0) { 50 | 51 | //0-9 52 | if (!(tensIndex == 1 && unit == 1 && temp == 0)) { 53 | [result insertString:unitCharacter[unit - 1] atIndex:0]; 54 | } 55 | 56 | unit = temp % 10; 57 | 58 | if (temp != 0 && unit != 0) { 59 | [result insertString:tenAndHundredCharacter[tensIndex] atIndex:0]; 60 | } 61 | 62 | tensIndex ++; 63 | } 64 | } 65 | 66 | return result; 67 | } 68 | 69 | + (NSInteger)increaseToHundredForScore:(NSInteger)score { 70 | NSInteger temp = score % 100; 71 | 72 | if (temp != 0) { 73 | score = score - temp + 100; 74 | } 75 | 76 | return score; 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/RoundResult/MMSCDrawManganResult.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCDrawManganResult.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/3. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCRoundResult.h" 10 | #import "MMSCDrawResult.h" 11 | 12 | @interface MMSCDrawManganResult : MMSCDrawResult 13 | 14 | @property(nonatomic, strong) NSArray *manganPlayerIndexes; 15 | 16 | @property(nonatomic, assign) BOOL oyaTenpai; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/RoundResult/MMSCDrawManganResult.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCDrawManganResult.m 3 | // MahjongScoreCalculator 4 | // 5 | // 流局满贯 6 | // 7 | // Created by fredfx on 16/1/3. 8 | // Copyright © 2016年 fredfx. All rights reserved. 9 | // 10 | 11 | #import "MMSCDrawManganResult.h" 12 | 13 | @implementation MMSCDrawManganResult 14 | 15 | - (instancetype)init { 16 | if (self = [super init]) { 17 | self.type = MMSCDrawType_DrawMangan; 18 | } 19 | 20 | return self; 21 | } 22 | 23 | - (void)assignScoreWithPlayers:(NSArray *)players bonban:(NSInteger)bonban richiScore:(NSInteger)richiScore { 24 | 25 | 26 | for (int i = 0; i < _manganPlayerIndexes.count; i++) { 27 | 28 | NSInteger playerIndex = [_manganPlayerIndexes[i] integerValue]; 29 | MMSCPlayer *winner = (MMSCPlayer *)players[playerIndex]; 30 | 31 | if ([winner isOYA]) { 32 | [self recordScoreChangeAtIndex:playerIndex player:winner change:12000]; 33 | [players enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 34 | MMSCPlayer *player = (MMSCPlayer *)obj; 35 | if (player != winner) { 36 | [self recordScoreChangeAtIndex:idx player:player change:-4000]; 37 | } 38 | }]; 39 | } else { 40 | [self recordScoreChangeAtIndex:playerIndex player:winner change:8000]; 41 | [players enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 42 | MMSCPlayer *player = (MMSCPlayer *)obj; 43 | if (player != winner) { 44 | NSInteger decresement = [player isOYA] ? 4000 : 2000; 45 | [self recordScoreChangeAtIndex:idx player:player change:-1 * decresement]; 46 | } 47 | }]; 48 | } 49 | } 50 | } 51 | 52 | - (BOOL)changeOYA:(NSUInteger)currentOYAIndex { 53 | return !self.oyaTenpai; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/RoundResult/MMSCDrawResult.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCDrawResult.h 3 | // MahjongScoreCalculator 4 | // 5 | // 流局 6 | // 7 | // Created by fredfx on 16/1/3. 8 | // Copyright © 2016年 fredfx. All rights reserved. 9 | // 10 | 11 | #import "MMSCRoundResult.h" 12 | 13 | typedef NS_ENUM(NSInteger, MMSCDrawType) { 14 | MMSCDrawType_Normal, // 普通流局 15 | MMSCDrawType_NineHonor, // 九种九牌 16 | MMSCDrawType_FourWind, // 四风连打 17 | MMSCDrawType_FourKan, // 四杠流局 18 | MMSCDrawType_FourRichi, // 四家立直 19 | MMSCDrawType_ThreeWin, // 一炮三响 20 | MMSCDrawType_DrawMangan, // 流局满贯 21 | }; 22 | 23 | @interface MMSCDrawResult : MMSCRoundResult 24 | 25 | // 流局的类型 26 | @property(nonatomic, assign) MMSCDrawType type; 27 | 28 | // 听牌的人 29 | @property(nonatomic, strong) NSArray *tenpaiPlayerIndexes; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/RoundResult/MMSCDrawResult.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCDrawResult.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/3. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCDrawResult.h" 10 | #import "MMSCUtil.h" 11 | 12 | @implementation MMSCDrawResult 13 | 14 | - (void)assignScoreWithPlayers:(NSArray *)players bonban:(NSInteger)bonban richiScore:(NSInteger)richiScore { 15 | 16 | if (_type != MMSCDrawType_Normal) { 17 | return; 18 | } 19 | 20 | if (_tenpaiPlayerIndexes.count == PLAYER_NUMBER || _tenpaiPlayerIndexes.count == 0) { 21 | return; 22 | } 23 | 24 | NSInteger incresement = 3000 / _tenpaiPlayerIndexes.count; 25 | NSInteger decresement = 3000 / (PLAYER_NUMBER - _tenpaiPlayerIndexes.count); 26 | 27 | [players enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 28 | MMSCPlayer *player = (MMSCPlayer *)obj; 29 | 30 | if ([_tenpaiPlayerIndexes containsObject:@(idx)]) { 31 | [self recordScoreChangeAtIndex:idx player:player change:incresement]; 32 | } else { 33 | [self recordScoreChangeAtIndex:idx player:player change:-1 * decresement]; 34 | } 35 | }]; 36 | } 37 | 38 | - (BOOL)isDraw { 39 | return YES; 40 | } 41 | 42 | - (BOOL)changeOYA:(NSUInteger)currentOYAIndex { 43 | switch (self.type) { 44 | case MMSCDrawType_FourWind: 45 | return YES; 46 | case MMSCDrawType_DrawMangan: 47 | case MMSCDrawType_Normal: 48 | return ![self.tenpaiPlayerIndexes containsObject:@(currentOYAIndex)]; 49 | default: 50 | return NO; 51 | } 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/RoundResult/MMSCNormalWinResult.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCNormalWinResult.h 3 | // MahjongScoreCalculator 4 | // 5 | // 普通的点炮 6 | // 7 | // Created by fredfx on 16/1/3. 8 | // Copyright © 2016年 fredfx. All rights reserved. 9 | // 10 | 11 | #import "MMSCRoundResult.h" 12 | 13 | @interface MMSCNormalWinResult : MMSCRoundResult 14 | 15 | // 胡了的人 16 | @property(nonatomic, assign) NSUInteger winnerIndex; 17 | // 番 18 | @property(nonatomic, assign) NSInteger fan; 19 | // 符 20 | @property(nonatomic, assign) NSInteger fu; 21 | 22 | // 点炮的 23 | @property(nonatomic, assign) NSUInteger payerIndex; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/RoundResult/MMSCNormalWinResult.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCNormalWinResult.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/3. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCNormalWinResult.h" 10 | 11 | @implementation MMSCNormalWinResult 12 | 13 | - (void)assignScoreWithPlayers:(NSArray *)players bonban:(NSInteger)bonban richiScore:(NSInteger)richiScore { 14 | 15 | MMSCPlayer *winner = players[_winnerIndex]; 16 | MMSCPlayer *payer = players[_payerIndex]; 17 | 18 | NSInteger basicScore = [self calculateScoreAtFan:_fan fu:_fu isOYAWin:[winner isOYA]]; 19 | 20 | [self recordScoreChangeAtIndex:_winnerIndex player:winner change:basicScore + (300 * bonban) + richiScore]; 21 | 22 | [self recordScoreChangeAtIndex:_payerIndex player:payer change:-1 * (basicScore + 300 * bonban)]; 23 | } 24 | 25 | - (BOOL)changeOYA:(NSUInteger)currentOYAIndex { 26 | return currentOYAIndex != self.winnerIndex; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/RoundResult/MMSCRoundResult.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCRoundResult.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/2. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MMSCPlayer.h" 11 | 12 | @interface MMSCRoundResult : NSObject 13 | 14 | // 分数的变化情况 15 | @property(nonatomic, strong) NSMutableArray *scoreChanges; 16 | 17 | // 是否流局 18 | - (BOOL)isDraw; 19 | 20 | // 是否换庄 21 | - (BOOL)changeOYA:(NSUInteger)currentOYAIndex; 22 | 23 | // 分配分数 24 | - (void)assignScoreWithPlayers:(NSArray *)players bonban:(NSInteger)bonban richiScore:(NSInteger)richiScore; 25 | 26 | // 记录分数变化 27 | - (void)recordScoreChangeAtIndex:(NSUInteger)index player:(MMSCPlayer *)player change:(NSInteger)change; 28 | 29 | // 记录立直棒 30 | - (void)recordRichiChangeWithRichiPlayers:(NSArray *)playerIdxs; 31 | 32 | // 计算分数 33 | - (NSInteger)calculateScoreAtFan:(NSInteger)fan fu:(NSInteger)fu isOYAWin:(BOOL)isOYAWin; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/RoundResult/MMSCRoundResult.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCRoundResult.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/2. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCRoundResult.h" 10 | 11 | @implementation MMSCRoundResult 12 | 13 | - (BOOL)isDraw { 14 | return NO; 15 | } 16 | 17 | - (BOOL)changeOYA:(NSUInteger)currentOYAIndex { 18 | return YES; 19 | } 20 | 21 | - (void)assignScoreWithPlayers:(NSArray *)players bonban:(NSInteger)bonban richiScore:(NSInteger)richiScore { 22 | NSAssert(NO, @"父类方法不该被直接调用"); 23 | } 24 | 25 | - (void)recordScoreChangeAtIndex:(NSUInteger)index player:(MMSCPlayer *)player change:(NSInteger)change { 26 | NSInteger currentChange = [self.scoreChanges[index] integerValue]; 27 | 28 | currentChange += change; 29 | 30 | self.scoreChanges[index] = @(currentChange); 31 | 32 | if (change > 0) { 33 | [player increaseScore:change]; 34 | } else { 35 | [player decreaseScore:-1 * change]; 36 | } 37 | } 38 | 39 | - (void)recordRichiChangeWithRichiPlayers:(NSArray *)playerIdxs { 40 | for (NSNumber *idxNum in playerIdxs) { 41 | NSUInteger idx = idxNum.unsignedIntegerValue; 42 | NSInteger currentChange = [self.scoreChanges[idx] integerValue]; 43 | 44 | currentChange -= 1000; 45 | 46 | self.scoreChanges[idx] = @(currentChange); 47 | } 48 | } 49 | 50 | - (instancetype)init { 51 | if (self = [super init]) { 52 | _scoreChanges = [NSMutableArray arrayWithArray:@[@(0), @(0), @(0), @(0)]]; 53 | } 54 | return self; 55 | } 56 | 57 | - (NSInteger)calculateScoreAtFan:(NSInteger)fan fu:(NSInteger)fu isOYAWin:(BOOL)isOYAWin { 58 | 59 | NSInteger score; 60 | 61 | if (fan >= 5 || (fan == 4 && fu >= 40) || (fan == 3 && fu >= 70)) { 62 | score = [self calculateManganAtFan:fan isOYAWin:isOYAWin]; 63 | } else { 64 | score = [self calculateNormalAtFan:fan fu:fu isOYAWin:isOYAWin]; 65 | } 66 | 67 | return score; 68 | } 69 | 70 | - (NSInteger)calculateManganAtFan:(NSInteger)fan isOYAWin:(BOOL)isOYAWin { 71 | 72 | NSAssert(fan >= 3, @"小于3番做什么满贯"); 73 | 74 | NSInteger result; 75 | 76 | switch (fan) { 77 | case 3: 78 | case 4: 79 | case 5: 80 | result = 8000; 81 | break; 82 | case 6: 83 | case 7: 84 | result = 12000; 85 | break; 86 | case 8: 87 | case 9: 88 | case 10: 89 | result = 16000; 90 | break; 91 | case 11: 92 | case 12: 93 | result = 24000; 94 | break; 95 | default: 96 | result = 32000; 97 | break; 98 | } 99 | 100 | if (isOYAWin) { 101 | result *= 1.5; 102 | } 103 | 104 | return result; 105 | } 106 | 107 | - (NSInteger)calculateNormalAtFan:(NSInteger)fan fu:(NSInteger)fu isOYAWin:(BOOL)isOYAWin { 108 | 109 | NSInteger basic = fu * (NSInteger)pow(2, fan + 2); 110 | 111 | if (isOYAWin) { 112 | return [MMSCUtil increaseToHundredForScore:basic * 6]; 113 | } else { 114 | return [MMSCUtil increaseToHundredForScore:basic * 4]; 115 | } 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/RoundResult/MMSCTsuMoResult.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCTsuMoResult.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/3. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCRoundResult.h" 10 | 11 | @interface MMSCTsuMoResult : MMSCRoundResult 12 | 13 | // 自摸的玩家的索引 14 | @property(nonatomic, assign) NSUInteger winnerIndex; 15 | 16 | // 番 17 | @property(nonatomic, assign) NSInteger fan; 18 | 19 | // 符 20 | @property(nonatomic, assign) NSInteger fu; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/RoundResult/MMSCTsuMoResult.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCTsuMoResult.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/3. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCTsuMoResult.h" 10 | #import "MMSCUtil.h" 11 | 12 | @implementation MMSCTsuMoResult 13 | 14 | - (void)assignScoreWithPlayers:(NSArray *)players bonban:(NSInteger)bonban richiScore:(NSInteger)richiScore { 15 | 16 | MMSCPlayer *winner = players[_winnerIndex]; 17 | 18 | NSInteger basicScore = [self calculateScoreAtFan:_fan fu:_fu isOYAWin:[winner isOYA]]; 19 | 20 | //立直棒 21 | [self recordScoreChangeAtIndex:_winnerIndex player:winner change:richiScore]; 22 | 23 | if ([winner isOYA]) { 24 | // OYA自摸 25 | NSInteger payment = [MMSCUtil increaseToHundredForScore:(basicScore / 3) + (100 * bonban)]; 26 | [self recordScoreChangeAtIndex:_winnerIndex player:winner change:payment * 3]; 27 | 28 | [players enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 29 | MMSCPlayer *player = (MMSCPlayer *)obj; 30 | 31 | if (player != winner) { 32 | [self recordScoreChangeAtIndex:idx player:player change:-1 * payment]; 33 | } 34 | }]; 35 | } else { 36 | // 炸庄 37 | NSInteger oyaPayment = [MMSCUtil increaseToHundredForScore:(basicScore / 2)] + (100 * bonban); 38 | NSInteger normalPayment = [MMSCUtil increaseToHundredForScore:(basicScore / 4)] + (100 * bonban); 39 | 40 | [self recordScoreChangeAtIndex:_winnerIndex player:winner change:oyaPayment + 2 * normalPayment]; 41 | 42 | [players enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 43 | MMSCPlayer *player = (MMSCPlayer *)obj; 44 | 45 | if (player != winner) { 46 | NSInteger change = -1 * ([player isOYA] ? oyaPayment : normalPayment); 47 | [self recordScoreChangeAtIndex:idx player:player change:change]; 48 | } 49 | }]; 50 | } 51 | } 52 | 53 | - (BOOL)changeOYA:(NSUInteger)currentOYAIndex { 54 | return currentOYAIndex != self.winnerIndex; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/RoundResult/MMSCTwoWinnerResult.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCTwoWinnerResult.h 3 | // MahjongScoreCalculator 4 | // 5 | // 一炮双响 6 | // 7 | // Created by fredfx on 16/1/3. 8 | // Copyright © 2016年 fredfx. All rights reserved. 9 | // 10 | 11 | #import "MMSCRoundResult.h" 12 | 13 | @interface MMSCTwoWinnerResult : MMSCRoundResult 14 | 15 | // 第一个 16 | @property(nonatomic, assign) NSInteger winner1Index; 17 | @property(nonatomic, assign) NSInteger fanOfWiiner1; 18 | @property(nonatomic, assign) NSInteger fuOfWinner1; 19 | 20 | // 第二个 21 | @property(nonatomic, assign) NSInteger winner2Index; 22 | @property(nonatomic, assign) NSInteger fanOfWinner2; 23 | @property(nonatomic, assign) NSInteger fuOfWinner2; 24 | 25 | // 点炮的 26 | @property(nonatomic, assign) NSInteger payerIndex; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/GameCore/RoundResult/MMSCTwoWinnerResult.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCTwoWinnerResult.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/3. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCTwoWinnerResult.h" 10 | 11 | @implementation MMSCTwoWinnerResult 12 | 13 | - (void)assignScoreWithPlayers:(NSArray *)players bonban:(NSInteger)bonban richiScore:(NSInteger)richiScore { 14 | 15 | // 立直棒和本场分数 16 | NSUInteger extraScoreIndex = [self getBonbanAndRichiScorePlayerIndex]; 17 | MMSCPlayer *extraScorePlayer = players[extraScoreIndex]; 18 | 19 | [self recordScoreChangeAtIndex:extraScoreIndex player:extraScorePlayer change:richiScore + (300 * bonban)]; 20 | 21 | MMSCPlayer *winner1 = players[_winner1Index]; 22 | MMSCPlayer *winner2 = players[_winner2Index]; 23 | MMSCPlayer *payer = players[_payerIndex]; 24 | 25 | NSInteger basicScore1 = [self calculateScoreAtFan:_fanOfWiiner1 fu:_fuOfWinner1 isOYAWin:[winner1 isOYA]]; 26 | [self recordScoreChangeAtIndex:_winner1Index player:winner1 change:basicScore1]; 27 | 28 | NSInteger basicScore2 = [self calculateScoreAtFan:_fanOfWinner2 fu:_fuOfWinner2 isOYAWin:[winner2 isOYA]]; 29 | [self recordScoreChangeAtIndex:_winner2Index player:winner2 change:basicScore2]; 30 | 31 | NSInteger payment = basicScore1 + basicScore2 + 300 * bonban; 32 | [self recordScoreChangeAtIndex:_payerIndex player:payer change:-1 * payment]; 33 | } 34 | 35 | // 计算拿点棒和本场分数的人:点炮者的下家->对家->上家 36 | - (NSUInteger)getBonbanAndRichiScorePlayerIndex { 37 | 38 | NSInteger distance1 = _winner1Index - _payerIndex; 39 | NSInteger distance2 = _winner2Index - _payerIndex; 40 | 41 | if (distance1 * distance2 < 0) { // 一个是点炮的下家,另一个是上家 42 | return distance1 > 0 ? _winner1Index : _winner2Index; 43 | } 44 | 45 | return distance1 < distance2 ? _winner1Index : _winner2Index; //总之选距离点炮者比较近的 46 | } 47 | 48 | - (BOOL)changeOYA:(NSUInteger)currentOYAIndex { 49 | return currentOYAIndex != self.winner1Index && currentOYAIndex != self.winner2Index; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIcons 10 | 11 | CFBundleIcons~ipad 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1 27 | LSRequiresIPhoneOS 28 | 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/View/MMSCFontAndColorUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCFontAndColorUtil.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/30. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef NS_ENUM(NSInteger, MMSCPlayerViewTag) { 13 | MMSCPlayerViewTagPlayer1 = 0x666, 14 | MMSCPlayerViewTagPlayer2, 15 | MMSCPlayerViewTagPlayer3, 16 | MMSCPlayerViewTagPlayer4, 17 | }; 18 | 19 | @interface MMSCFontAndColorUtil : NSObject 20 | 21 | // 欢迎页顶层字体 22 | + (UIFont *)titleSummaryFont; 23 | 24 | // 大号风向字体 25 | + (UIFont *)windBigLabelFont; 26 | 27 | // 欢迎页普通字体 28 | + (UIFont *)startpageFont; 29 | 30 | // 欢迎页背景颜色 31 | + (UIColor *)startPageBackgroundColor; 32 | 33 | // 欢迎页Label字体颜色 34 | + (UIColor *)startPageLabelFontColor; 35 | 36 | // Color2Image 37 | + (UIImage *)imageWithColor:(UIColor *)color; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/View/MMSCFontAndColorUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCFontAndColorUtil.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/30. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCFontAndColorUtil.h" 10 | 11 | @implementation MMSCFontAndColorUtil 12 | 13 | #pragma mark ---------------Font---------------- 14 | 15 | // TODO 重构成KeyValue对 16 | 17 | + (UIFont *)titleSummaryFont { 18 | static UIFont *titleSummaryFont = nil; 19 | static dispatch_once_t titleOnce; 20 | dispatch_once(&titleOnce, ^{ 21 | titleSummaryFont = [UIFont systemFontOfSize:30]; 22 | }); 23 | 24 | return titleSummaryFont; 25 | } 26 | 27 | + (UIFont *)windBigLabelFont { 28 | static UIFont *windBigFont = nil; 29 | static dispatch_once_t windBigOnce; 30 | dispatch_once(&windBigOnce, ^{ 31 | windBigFont = [UIFont systemFontOfSize:50 weight:5]; 32 | }); 33 | return windBigFont; 34 | } 35 | 36 | + (UIFont *)startpageFont { 37 | static UIFont *playerNameFont = nil; 38 | static dispatch_once_t playerNameOnce; 39 | dispatch_once(&playerNameOnce, ^{ 40 | playerNameFont = [UIFont systemFontOfSize:25]; 41 | }); 42 | return playerNameFont; 43 | } 44 | 45 | #pragma mark ---------------Color----------------- 46 | 47 | + (UIColor *)startPageBackgroundColor { 48 | static UIColor *startPageBGColor = nil; 49 | static dispatch_once_t startBGCOnce; 50 | dispatch_once(&startBGCOnce, ^{ 51 | startPageBGColor = [[UIColor alloc] initWithRed:99/255.f green:133/255.f blue:167/255.f alpha:1]; 52 | }); 53 | return startPageBGColor; 54 | } 55 | 56 | + (UIColor *)startPageLabelFontColor { 57 | static UIColor *spLabelFC = nil; 58 | static dispatch_once_t spLabelOnce; 59 | dispatch_once(&spLabelOnce, ^{ 60 | spLabelFC = [[UIColor alloc] initWithRed:217/255.f green:208/255.f blue:199/255.f alpha:1]; 61 | }); 62 | return spLabelFC; 63 | } 64 | 65 | + (UIImage *)imageWithColor:(UIColor *)color { 66 | CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); 67 | UIGraphicsBeginImageContext(rect.size); 68 | CGContextRef context = UIGraphicsGetCurrentContext(); 69 | 70 | CGContextSetFillColorWithColor(context, [color CGColor]); 71 | CGContextFillRect(context, rect); 72 | 73 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 74 | UIGraphicsEndImageContext(); 75 | 76 | UIImage *resizeImage = [image resizableImageWithCapInsets:UIEdgeInsetsMake(0.5f, 0.5f, 0.5f, 0.5f)]; 77 | 78 | return resizeImage; 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/View/MMSCPlayerNameAndSeatController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCPlayerNameAndSeatController.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/30. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MMSCPlayerNameAndSeatController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/View/MMSCPlayerNameAndSeatController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCPlayerNameAndSeatController.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/30. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCPlayerNameAndSeatController.h" 10 | #import "MMSCFontAndColorUtil.h" 11 | #import "MMSCRandomSeatTipsController.h" 12 | #import "MMSCScoreTableController.h" 13 | #import "MMSCGameManager.h" 14 | 15 | @interface MMSCPlayerNameAndSeatController () 16 | 17 | @property (nonatomic, strong) NSArray *playerNames; 18 | 19 | @end 20 | 21 | @implementation MMSCPlayerNameAndSeatController 22 | 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | 26 | [self.view setBackgroundColor:[MMSCFontAndColorUtil startPageBackgroundColor]]; 27 | 28 | CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; 29 | 30 | UILabel *summaryLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 30, screenWidth, 60)]; 31 | [summaryLabel setFont:[MMSCFontAndColorUtil titleSummaryFont]]; 32 | [summaryLabel setTextAlignment:NSTextAlignmentCenter]; 33 | [summaryLabel setText:@"打麻将真TM开心"]; 34 | 35 | [self.view addSubview:summaryLabel]; 36 | 37 | CGFloat windLabelCenterX = 80; 38 | CGFloat windLabelCenterY = 165; 39 | CGFloat windLabelInterval = 80; 40 | CGFloat textFieldCenterX = windLabelCenterX + 126; 41 | [self generateLabelWind:@"東" center:CGPointMake(windLabelCenterX, windLabelCenterY)]; 42 | UITextField *player1Field = [self generatePlayerNameFieldAtPoint:CGPointMake(textFieldCenterX, windLabelCenterY) tintText:@"天和"]; 43 | player1Field.tag = MMSCPlayerViewTagPlayer1; 44 | [self.view addSubview:player1Field]; 45 | 46 | windLabelCenterY += windLabelInterval; 47 | [self generateLabelWind:@"南" center:CGPointMake(windLabelCenterX, windLabelCenterY)]; 48 | UITextField *player2Field = [self generatePlayerNameFieldAtPoint:CGPointMake(textFieldCenterX, windLabelCenterY) tintText:@"地和"]; 49 | player2Field.tag = MMSCPlayerViewTagPlayer2; 50 | [self.view addSubview:player2Field]; 51 | 52 | windLabelCenterY += windLabelInterval; 53 | [self generateLabelWind:@"西" center:CGPointMake(windLabelCenterX, windLabelCenterY)]; 54 | UITextField *player3Field = [self generatePlayerNameFieldAtPoint:CGPointMake(textFieldCenterX, windLabelCenterY) tintText:@"人和"]; 55 | player3Field.tag = MMSCPlayerViewTagPlayer3; 56 | [self.view addSubview:player3Field]; 57 | 58 | windLabelCenterY += windLabelInterval; 59 | [self generateLabelWind:@"北" center:CGPointMake(windLabelCenterX, windLabelCenterY)]; 60 | UITextField *player4Field = [self generatePlayerNameFieldAtPoint:CGPointMake(textFieldCenterX, windLabelCenterY) tintText:@"炸和"]; 61 | player4Field.tag = MMSCPlayerViewTagPlayer4; 62 | [self.view addSubview:player4Field]; 63 | 64 | UIButton * randomSeatButton = [self generateStartPageButtonAtCenter:CGPointMake(screenWidth / 2, windLabelCenterY + 80) text:@"随机座位"]; 65 | [randomSeatButton addTarget:self action:@selector(waitForRandomSeat) forControlEvents:UIControlEventTouchUpInside]; 66 | [self.view addSubview:randomSeatButton]; 67 | 68 | UIButton * startBattleButton = [self generateStartPageButtonAtCenter:CGPointMake(screenWidth / 2, randomSeatButton.center.y + 60) text:@"赌上性命开始对日"]; 69 | [startBattleButton addTarget:self action:@selector(startBattle:) forControlEvents:UIControlEventTouchUpInside]; 70 | [self.view addSubview:startBattleButton]; 71 | } 72 | 73 | - (void)generateLabelWind:(NSString *)wind center:(CGPoint)center { 74 | UILabel *windLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 40, 40)]; 75 | [windLabel setFont:[MMSCFontAndColorUtil windBigLabelFont]]; 76 | [windLabel setTextColor:[MMSCFontAndColorUtil startPageLabelFontColor]]; 77 | [windLabel setText:wind]; 78 | [windLabel sizeToFit]; 79 | windLabel.center = center; 80 | 81 | CGFloat seperaterCenterY = center.y + windLabel.frame.size.height / 2; 82 | [self generateSeperateLineAtCenter:CGPointMake(self.view.frame.size.width / 2, seperaterCenterY)]; 83 | 84 | [self.view addSubview:windLabel]; 85 | } 86 | 87 | - (void)generateSeperateLineAtCenter:(CGPoint)center { 88 | UIView *seperater = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 255, 0.5f)]; 89 | seperater.backgroundColor = [UIColor lightGrayColor]; 90 | seperater.center = center; 91 | [self.view addSubview:seperater]; 92 | } 93 | 94 | - (UITextField *)generatePlayerNameFieldAtPoint:(CGPoint)center tintText:(NSString *)tintText { 95 | UITextField *playerTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 195, 35)]; 96 | [playerTextField setFont:[MMSCFontAndColorUtil startpageFont]]; 97 | playerTextField.placeholder = tintText; 98 | playerTextField.textAlignment = NSTextAlignmentCenter; 99 | playerTextField.center = center; 100 | playerTextField.clearButtonMode = UITextFieldViewModeAlways; 101 | playerTextField.delegate = self; 102 | playerTextField.returnKeyType = UIReturnKeyDone; 103 | 104 | return playerTextField; 105 | } 106 | 107 | - (UIButton *)generateStartPageButtonAtCenter:(CGPoint)center text:(NSString *)text { 108 | UIButton *button = [[UIButton alloc] init]; 109 | UIImage *origImage = [UIImage imageNamed:@"resource/StartPage_ButtonBG.png"]; 110 | UIImage *origClickImage = [UIImage imageNamed:@"resource/StartPage_ButtonBG_click.png"]; 111 | 112 | CGFloat resizableWidth = origImage.size.width / 2.f; 113 | CGFloat resizableHeight = origImage.size.height / 2.f; 114 | 115 | UIImage *backgroundImage = [origImage resizableImageWithCapInsets:UIEdgeInsetsMake(resizableHeight, resizableWidth, resizableHeight, resizableWidth)]; 116 | [button setBackgroundImage:backgroundImage forState:UIControlStateNormal]; 117 | 118 | UIImage *backgroundClickImage = [origClickImage resizableImageWithCapInsets:UIEdgeInsetsMake(resizableHeight, resizableWidth, resizableHeight, resizableWidth)]; 119 | [button setBackgroundImage:backgroundClickImage forState:UIControlStateSelected]; 120 | 121 | [button.titleLabel setFont:[UIFont systemFontOfSize:20]]; 122 | [button setTitle:text forState:UIControlStateNormal]; 123 | [button setTitleColor:[MMSCFontAndColorUtil startPageLabelFontColor] forState:UIControlStateNormal]; 124 | [button sizeToFit]; 125 | 126 | button.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width - 2 * 40, button.frame.size.height + 5); 127 | button.center = center; 128 | 129 | return button; 130 | } 131 | 132 | - (void)viewWillAppear:(BOOL)animated { 133 | if (self.playerNames) { 134 | for (int i = 0; i < 4; i++) { 135 | MMSCPlayerViewTag tag = (MMSCPlayerViewTag)(MMSCPlayerViewTagPlayer1 + i); 136 | UITextField *playerField = [self.view viewWithTag:tag]; 137 | NSString *playerName = (NSString *)self.playerNames[i]; 138 | playerField.text = playerName; 139 | } 140 | } 141 | } 142 | 143 | #pragma mark ----------TextFieldDelegate---------- 144 | 145 | - (BOOL)textFieldShouldReturn:(UITextField *)textField { 146 | [textField resignFirstResponder]; 147 | return YES; 148 | } 149 | 150 | #pragma mark ----------button action-------------- 151 | 152 | - (void)waitForRandomSeat { 153 | MMSCRandomSeatTipsController *controller = [[MMSCRandomSeatTipsController alloc] init]; 154 | controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal; 155 | 156 | [self presentViewController:controller animated:YES completion:nil]; 157 | 158 | self.playerNames = [[MMSCGameManager instance] randomSeatWithPlayers:[self getPlayerNames]]; 159 | } 160 | 161 | - (void)startBattle:(id)sender { 162 | if (!self.playerNames) { 163 | [[MMSCGameManager instance] initGameWithPlayers:[self getPlayerNames]]; 164 | } 165 | 166 | MMSCScoreTableController *scoreTableController = [[MMSCScoreTableController alloc] init]; 167 | UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:scoreTableController]; 168 | navController.navigationBar.barTintColor = [UIColor whiteColor]; 169 | 170 | [self presentViewController:navController animated:YES completion:nil]; 171 | } 172 | 173 | - (NSArray *)getPlayerNames { 174 | NSMutableArray *playerNames = [NSMutableArray array]; 175 | for (MMSCPlayerViewTag tag = MMSCPlayerViewTagPlayer1; tag <= MMSCPlayerViewTagPlayer4; tag++) { 176 | UITextField *playerTextField = [self.view viewWithTag:tag]; 177 | NSString *playerName = playerTextField.text.length > 0 ? playerTextField.text : playerTextField.placeholder; 178 | [playerNames addObject:playerName]; 179 | } 180 | 181 | return [playerNames copy]; 182 | } 183 | 184 | @end 185 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/View/MMSCRandomSeatTipsController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCRandomSeatTipsController.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/2/1. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MMSCRandomSeatTipsController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/View/MMSCRandomSeatTipsController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCRandomSeatTipsController.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/2/1. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCRandomSeatTipsController.h" 10 | #import "MMSCFontAndColorUtil.h" 11 | 12 | @interface MMSCRandomSeatTipsController () 13 | 14 | @property (nonatomic, strong) NSTimer *turnBackTimer; 15 | 16 | @end 17 | 18 | @implementation MMSCRandomSeatTipsController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | 23 | self.view.backgroundColor = [MMSCFontAndColorUtil startPageBackgroundColor]; 24 | 25 | CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; 26 | CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height; 27 | 28 | UILabel *tip1 = [[UILabel alloc] init]; 29 | [tip1 setText:@"对日之前要抽风"]; 30 | [tip1 setTextColor:[MMSCFontAndColorUtil startPageLabelFontColor]]; 31 | [tip1 setFont:[UIFont systemFontOfSize:30 weight:4]]; 32 | [tip1 sizeToFit]; 33 | tip1.frame = CGRectMake(0, 0, screenWidth - 20, tip1.frame.size.height); 34 | tip1.center = CGPointMake(screenWidth / 2, screenHeight - 75); 35 | [self.view addSubview:tip1]; 36 | 37 | UILabel *tip2 = [[UILabel alloc] init]; 38 | [tip2 setText:@"随机座位中..."]; 39 | [tip2 setTextColor:[UIColor colorWithRed:153/255.f green:156/255.f blue:168/255.f alpha:0.8]]; 40 | [tip2 setFont:[UIFont systemFontOfSize:22]]; 41 | [tip2 sizeToFit]; 42 | tip2.frame = CGRectMake(0, 0, screenWidth - 20, tip2.frame.size.height); 43 | tip2.center = CGPointMake(screenWidth / 2, screenHeight - 40); 44 | [self.view addSubview:tip2]; 45 | } 46 | 47 | - (void)viewDidAppear:(BOOL)animated { 48 | if (!_turnBackTimer) { 49 | _turnBackTimer = [NSTimer timerWithTimeInterval:1.f target:self selector:@selector(onTurnBackTimerTick) userInfo:nil repeats:NO]; 50 | [[NSRunLoop mainRunLoop] addTimer:_turnBackTimer forMode:NSRunLoopCommonModes]; 51 | } 52 | } 53 | 54 | - (void)onTurnBackTimerTick { 55 | [self dismissViewControllerAnimated:YES completion:nil]; 56 | 57 | if (_turnBackTimer) { 58 | [_turnBackTimer invalidate]; 59 | } 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/View/MMSCRichiController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCRichiController.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/2/13. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol MMSCRichiDelegate 12 | 13 | - (void)richiPlayersSelected:(NSArray *)richiPlayerIndexes; 14 | 15 | @end 16 | 17 | @interface MMSCRichiController : UIViewController 18 | 19 | @property (nonatomic, weak) id delegate; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/View/MMSCRichiController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCRichiController.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/2/13. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCRichiController.h" 10 | #import "MMSCGameManager.h" 11 | #import "MMSCFontAndColorUtil.h" 12 | 13 | @implementation MMSCRichiController 14 | 15 | - (void)viewDidLoad { 16 | [super viewDidLoad]; 17 | 18 | UIColor *backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.6f]; 19 | self.view.backgroundColor = backgroundColor; 20 | 21 | CGFloat viewWidth = 270; 22 | CGFloat viewHeight = 260; 23 | CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; 24 | CGFloat screenHeigt = [UIScreen mainScreen].bounds.size.height; 25 | 26 | UIView *dialogView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, viewWidth, viewHeight)]; 27 | dialogView.center = CGPointMake(screenWidth / 2.f, screenHeigt / 2.f); 28 | dialogView.backgroundColor = [UIColor whiteColor]; 29 | dialogView.layer.cornerRadius = 20.f; 30 | [self.view addSubview:dialogView]; 31 | 32 | CGFloat buttonHeight = 44; 33 | CGFloat seperatorX = dialogView.frame.origin.x; 34 | CGFloat seperatorWidth = dialogView.frame.size.width; 35 | CGFloat seperatorY = dialogView.frame.origin.y + viewHeight - buttonHeight; 36 | UIView *seperator = [[UIView alloc] initWithFrame:CGRectMake(seperatorX, seperatorY, seperatorWidth, 0.5f)]; 37 | seperator.backgroundColor = [UIColor lightGrayColor]; 38 | [self.view addSubview:seperator]; 39 | 40 | UIButton *okButton = [[UIButton alloc] initWithFrame:CGRectMake(seperatorX, seperatorY, seperatorWidth, buttonHeight)]; 41 | [okButton setTitle:@"ok" forState:UIControlStateNormal]; 42 | [okButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 43 | [okButton addTarget:self action:@selector(okButtonClicked) forControlEvents:UIControlEventTouchUpInside]; 44 | 45 | [self.view addSubview:okButton]; 46 | 47 | NSArray *playerNames = [[MMSCGameManager instance] playerNames]; 48 | NSArray *currentRichiPlayers = [[MMSCGameManager instance] currentRichiPlayers]; 49 | CGFloat labelInterval = 45; 50 | CGFloat labelX = dialogView.frame.origin.x + 40; 51 | CGFloat switchX = dialogView.frame.origin.x + 150; 52 | CGFloat labelY = dialogView.frame.origin.y + 30; 53 | CGFloat swithcY = dialogView.frame.origin.y + 25; 54 | for (int i = 0; i < 4; i++) { 55 | UILabel *playerLabel = [[UILabel alloc] initWithFrame:CGRectMake(labelX, labelY + labelInterval * i, 100, 20)]; 56 | NSString *playerName = playerNames[i]; 57 | playerLabel.backgroundColor = [UIColor clearColor]; 58 | playerLabel.text = playerName; 59 | playerLabel.textAlignment = NSTextAlignmentCenter; 60 | [self.view addSubview:playerLabel]; 61 | 62 | UISwitch *richiSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(switchX, swithcY + labelInterval * i, 40, 20)]; 63 | richiSwitch.tag = MMSCPlayerViewTagPlayer1 + i; 64 | if ([currentRichiPlayers containsObject:@(i)]) { 65 | [richiSwitch setOn:YES]; 66 | } 67 | [self.view addSubview:richiSwitch]; 68 | } 69 | } 70 | 71 | - (void)okButtonClicked { 72 | NSMutableArray *richiPlayers = [NSMutableArray array]; 73 | 74 | for (int i = 0; i < 4; i++) { 75 | UISwitch *richiSwitch = [self.view viewWithTag:MMSCPlayerViewTagPlayer1 + i]; 76 | if ([richiSwitch isOn]) { 77 | [richiPlayers addObject:@(i)]; 78 | } 79 | } 80 | 81 | if ([self.delegate respondsToSelector:@selector(richiPlayersSelected:)] ) { 82 | [self.delegate richiPlayersSelected:richiPlayers]; 83 | } 84 | 85 | [self dismissViewControllerAnimated:YES completion:nil]; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/View/MMSCRoundResulltController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCRoundResulltController.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/2/14. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, MMSCRoundResultType) { 12 | MMSCRoundResultTypeTsumo = 0x888, 13 | MMSCRoundResultTypeRon, 14 | MMSCRoundResultTypeDraw 15 | }; 16 | 17 | @protocol MMSCRoundResultDelegate 18 | 19 | - (void)tsumoResultSelected:(NSUInteger)playerIndex fan:(NSInteger)fan fu:(NSInteger)fu; 20 | 21 | - (void)ronResultSelected:(NSArray *)winnerArray loser:(NSUInteger)loserIndex fan:(NSArray *)fan fu:(NSArray *)fu; 22 | 23 | - (void)drawResultSelected:(NSInteger)drawType players:(NSArray *)players oyaTenpai:(BOOL)oyaTenpai; 24 | 25 | @end 26 | 27 | @interface MMSCRoundResulltController : UIViewController 28 | 29 | @property (nonatomic, assign) MMSCRoundResultType type; 30 | 31 | @property (nonatomic, weak) id delegate; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/View/MMSCScoreTableCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCScoreTableCell.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/2/10. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MMSCScoreTableCell : UITableViewCell 12 | 13 | - (void)setScoreChange:(NSArray *)scoreChanges; 14 | 15 | - (void)markRichiAtIndexes:(NSArray *)playerIndexes currentRichiPlayers:(NSArray *)currentRichiPlayer; 16 | 17 | - (void)clearRichiMark; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/View/MMSCScoreTableCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCScoreTableCell.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/2/10. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import "MMSCScoreTableCell.h" 10 | #import "MMSCScoreTableController.h" 11 | #import "MMSCFontAndColorUtil.h" 12 | 13 | #define MMSCRICHIMASKVIEWTAGOFFSET 0x233 14 | 15 | @implementation MMSCScoreTableCell 16 | 17 | - (void)setScoreChange:(NSArray *)scoreChanges { 18 | if ([self shouldCreateNewLabel]) { 19 | [self createScoreChangeLabels]; 20 | } 21 | 22 | for (int i = 0; i < 4; i++) { 23 | UILabel *scoreLabel = [self.contentView viewWithTag:MMSCPlayerViewTagPlayer1 + i]; 24 | NSInteger scoreChange = [scoreChanges[i] integerValue]; 25 | NSString *scoreText = [NSString stringWithFormat:scoreChange > 0 ? @"+%zd" : @"%zd", scoreChange]; 26 | scoreLabel.text = scoreText; 27 | } 28 | } 29 | 30 | - (void)createScoreChangeLabels { 31 | CGFloat interval = [UIScreen mainScreen].bounds.size.width / 4; 32 | CGFloat windLabelOffset = 0; 33 | for (int i = 0; i < 4; i++) { 34 | UILabel *scoreLabel = [[UILabel alloc] initWithFrame:CGRectMake(windLabelOffset, 2, interval, self.frame.size.height)]; 35 | scoreLabel.font = [UIFont systemFontOfSize:15]; 36 | scoreLabel.textAlignment = NSTextAlignmentCenter; 37 | scoreLabel.tag = (MMSCPlayerViewTag)(MMSCPlayerViewTagPlayer1 + i); 38 | [self.contentView addSubview:scoreLabel]; 39 | 40 | UIView *sLine = [[UIView alloc] initWithFrame:CGRectMake(windLabelOffset + interval, 5, 0.5f, self.frame.size.height - 6)]; 41 | sLine.backgroundColor = [UIColor lightGrayColor]; 42 | [self.contentView addSubview:sLine]; 43 | 44 | windLabelOffset += interval; 45 | } 46 | } 47 | 48 | - (BOOL)shouldCreateNewLabel { 49 | UIView *playerView = [self.contentView viewWithTag:MMSCPlayerViewTagPlayer1]; 50 | return !playerView; 51 | } 52 | 53 | - (void)markRichiAtIndexes:(NSArray *)playerIndexes currentRichiPlayers:(NSArray *)currentRichiPlayerIndexes { 54 | NSMutableArray *currentRichiPlayers = [currentRichiPlayerIndexes mutableCopy]; 55 | for (NSNumber *playerIndex in playerIndexes) { 56 | if ([currentRichiPlayers containsObject:playerIndex]) { // 之前已经立直过了 57 | [currentRichiPlayers removeObject:playerIndex]; 58 | } 59 | 60 | [self markRichiAtIndex:playerIndex.unsignedIntegerValue]; 61 | } 62 | 63 | if (currentRichiPlayers.count > 0) { // 有取消的情况发生 64 | for (NSNumber *playerIndex in currentRichiPlayers) { 65 | [self cancelRichiAtIndex:playerIndex.unsignedIntegerValue]; 66 | } 67 | } 68 | } 69 | 70 | - (void)markRichiAtIndex:(NSUInteger)playerIndex { 71 | NSAssert(playerIndex < 4, @"playerIndex out of bound"); 72 | 73 | MMSCPlayerViewTag labelTag = MMSCPlayerViewTagPlayer1 + playerIndex; 74 | UILabel *playerLabel = [self.contentView viewWithTag:labelTag]; 75 | UIImageView *richiMaskView = [self.contentView viewWithTag:labelTag + MMSCRICHIMASKVIEWTAGOFFSET]; 76 | 77 | if (!richiMaskView) { 78 | richiMaskView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, playerLabel.frame.size.width - 5, self.frame.size.height - 14)]; 79 | richiMaskView.backgroundColor = [UIColor clearColor]; 80 | richiMaskView.image = [UIImage imageNamed:@"resource/richiIcon-horizontal.png"]; 81 | richiMaskView.alpha = 0.6f; 82 | richiMaskView.tag = labelTag + MMSCRICHIMASKVIEWTAGOFFSET; 83 | [self.contentView addSubview:richiMaskView]; 84 | } 85 | 86 | richiMaskView.hidden = NO; 87 | richiMaskView.center = playerLabel.center; 88 | [self.contentView bringSubviewToFront:playerLabel]; 89 | } 90 | 91 | - (void)cancelRichiAtIndex:(NSInteger)playerIndex { 92 | NSAssert(playerIndex < 4, @"playerIndex out of bound"); 93 | 94 | UIImageView *richiMaskView = [self.contentView viewWithTag:MMSCPlayerViewTagPlayer1 + playerIndex + MMSCRICHIMASKVIEWTAGOFFSET]; 95 | 96 | richiMaskView.hidden = YES; 97 | } 98 | 99 | - (void)clearRichiMark { 100 | for (int i = 0; i < 4; i++) { 101 | [self cancelRichiAtIndex:i]; 102 | } 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/View/MMSCScoreTableController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMSCScoreTableController.h 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/2/7. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MMSCScoreTableController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // MahjongScoreCalculator 4 | // 5 | // Created by fredfx on 16/1/2. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/StartPage_ButtonBG@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/StartPage_ButtonBG@2x.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/StartPage_ButtonBG_click@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/StartPage_ButtonBG_click@2x.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/mahjong-appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/mahjong-appicon.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/menu.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/more_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/more_icon@2x.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/more_icon_click@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/more_icon_click@2x.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/richiIcon-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/richiIcon-horizontal.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/richiIcon-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mahosyojyo/Majong-AutoScore/a4ec15c2172f5dcf26d37c17631a9bd7b76f9a17/iOS/src/MahjongScoreCalculator/MahjongScoreCalculator/resource/richiIcon-vertical.png -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculatorTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /iOS/src/MahjongScoreCalculator/MahjongScoreCalculatorTests/MahjongScoreCalculatorTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // MahjongScoreCalculatorTests.m 3 | // MahjongScoreCalculatorTests 4 | // 5 | // Created by fredfx on 16/1/2. 6 | // Copyright © 2016年 fredfx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MahjongScoreCalculatorTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation MahjongScoreCalculatorTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | --------------------------------------------------------------------------------