├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── account.sql ├── common ├── config.go ├── generate.go ├── model.go ├── poker.go ├── protocol.go ├── rule.go └── rule.json ├── conf └── app.conf ├── controllers ├── Index.go ├── Login.go ├── Logout.go └── Register.go ├── db └── landlord.db ├── go.mod ├── go.sum ├── main.exe ├── main ├── config.go ├── init.go ├── main.go └── rule.json ├── router └── router.go ├── rule.json ├── service ├── client.go ├── request.go ├── robot.go ├── room.go └── table.go ├── static ├── 1.png ├── 2.png ├── 3.png ├── audio │ ├── bg_game.ogg │ ├── bg_room.mp3 │ ├── deal.mp3 │ ├── end_lose.mp3 │ ├── end_win.mp3 │ ├── f_score_0.mp3 │ ├── f_score_1.mp3 │ ├── f_score_2.mp3 │ ├── f_score_3.mp3 │ ├── m_score_0.mp3 │ ├── m_score_1.mp3 │ ├── m_score_2.mp3 │ └── m_score_3.mp3 ├── generator.html ├── i │ ├── bg.png │ ├── btn.json │ ├── btn.png │ ├── btn │ │ ├── alarm.png │ │ ├── btn.tps │ │ ├── exit.png │ │ ├── hint.png │ │ ├── icon_default.png │ │ ├── icon_farmer.png │ │ ├── icon_landlord.png │ │ ├── pass.png │ │ ├── quick.png │ │ ├── register.png │ │ ├── score_0.png │ │ ├── score_1.png │ │ ├── score_2.png │ │ ├── score_3.png │ │ ├── setting.png │ │ ├── shot.png │ │ ├── start.png │ │ └── table.png │ ├── poker.png │ └── preload.png ├── js │ ├── boot.js │ ├── game.js │ ├── generator.js │ ├── net.js │ ├── phaser-input.js │ ├── phaser-input.min.js │ ├── phaser.min.js │ ├── player.js │ ├── player_net.js │ └── rule.js ├── rule.json └── s │ └── bg3.ogg └── templates └── poker.html /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=go -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.exe 3 | *.exe~ 4 | *.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2018 GaoYC 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 斗地主 2 | 斗地主, golang 1.12, sqlite3, go module 3 | 4 | 说明:go modules管理依赖包,执行编译自动下载依赖;使用sqlite数据库,无需配置;自带无脑出牌AI;服务端经过压力测试,客户端代码来自https://github.com/mailgyc/doudizhu 。 5 | 6 | 烦请请各位老爷点个star。。。 7 | 8 | 9 | 运行步骤: 10 | 11 | 1.下载源码: 12 | 13 | git clone https://github.com/dwg255/landlord.git 14 | 15 | 2.编译: 16 | 17 | cd landlord/main 18 | go build -o ../main.exe 19 | cd .. 20 | main.exe 21 | 22 | 3.启动: 23 | 浏览器访问 http://localhost 24 | 25 | 4.效果展示: 26 | http://blzz.shop:8080 27 | 28 | 29 | --- 30 | 31 | License 32 | 33 | This project is released under the terms of the MIT license. See [LICENSE](LICENSE) for more 34 | information or see https://opensource.org/licenses/MIT. 35 | 36 | 37 | --- 38 | 39 | ![](https://raw.githubusercontent.com/dwg255/landlord/master/static/1.png) 40 | ![](https://raw.githubusercontent.com/dwg255/landlord/master/static/2.png) 41 | ![](https://raw.githubusercontent.com/dwg255/landlord/master/static/3.png) 42 | -------------------------------------------------------------------------------- /account.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS landlord.account ( 2 | id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 | email VARCHAR(20) NOT NULL UNIQUE, 4 | username VARCHAR(10) NOT NULL, 5 | password VARCHAR(100) NOT NULL, 6 | coin INT default 4000, 7 | created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 8 | updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 9 | ); -------------------------------------------------------------------------------- /common/config.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "database/sql" 5 | ) 6 | 7 | var GameConfInfo GameConf 8 | 9 | type GameConf struct { 10 | HttpPort int 11 | LogPath string 12 | LogLevel string 13 | AppSecret string 14 | DbPath string 15 | Db *sql.DB 16 | } 17 | -------------------------------------------------------------------------------- /common/generate.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "os" 5 | "github.com/astaxie/beego/logs" 6 | "strconv" 7 | "encoding/json" 8 | ) 9 | 10 | func write() { 11 | path := "rule.json" 12 | _, err := os.Stat(path) 13 | if err != nil { 14 | if os.IsNotExist(err) { 15 | generate() 16 | } else { 17 | logs.Error("rule.json err:%v", err) 18 | return 19 | } 20 | } 21 | } 22 | 23 | //生成连续num个的单牌的顺子 24 | func generateSeq(num int, seq []string) (res []string) { 25 | for i, _ := range seq { 26 | if i+num > 12 { 27 | break 28 | } 29 | var sec string 30 | for j := i; j < i+num; j++ { 31 | sec += seq[j] 32 | } 33 | res = append(res, sec) 34 | } 35 | return 36 | } 37 | 38 | //生成num个不同单的组合 39 | func combination(seq []string, num int) (comb []string) { 40 | if num == 0 { 41 | panic("generate err , combination count can not be 0") 42 | } 43 | if len(seq) < num { 44 | logs.Error("seq: %v,num:%d",seq,num) 45 | return 46 | //panic("generate err , seq length less than num") 47 | } 48 | if num == 1 { 49 | return seq 50 | } 51 | if len(seq) == num { 52 | allSingle := "" 53 | for _,single := range seq{ 54 | allSingle += single 55 | } 56 | return []string{allSingle} 57 | } 58 | noFirst := combination(seq[1:],num) 59 | hasFirst := []string(nil) 60 | for _,comb := range combination(seq[1:],num-1) { 61 | hasFirst = append(hasFirst, string(seq[0])+comb) 62 | } 63 | comb = append(comb, noFirst...) 64 | comb = append(comb, hasFirst...) 65 | return 66 | } 67 | 68 | func generate() { 69 | CARDS := "34567890JQKA2" 70 | RULE := map[string][]string{} 71 | RULE["single"] = []string{} 72 | RULE["pair"] = []string{} 73 | RULE["trio"] = []string{} 74 | RULE["bomb"] = []string{} 75 | for _, c := range CARDS { 76 | card := string(c) 77 | RULE["single"] = append(RULE["single"], card) 78 | RULE["pair"] = append(RULE["pair"], card+card) 79 | RULE["trio"] = append(RULE["trio"], card+card+card) 80 | RULE["bomb"] = append(RULE["bomb"], card+card+card+card) 81 | } 82 | for _, num := range []int{5, 6, 7, 8, 9, 10, 11, 12} { 83 | RULE["seq_single"+strconv.Itoa(num)] = generateSeq(num, RULE["single"]) 84 | } 85 | for _, num := range []int{3, 4, 5, 6, 7, 8, 9, 10} { 86 | RULE["seq_pair"+strconv.Itoa(num)] = generateSeq(num, RULE["pair"]) 87 | } 88 | for _, num := range []int{2, 3, 4, 5, 6} { 89 | RULE["seq_trio"+strconv.Itoa(num)] = generateSeq(num, RULE["trio"]) 90 | } 91 | RULE["single"] = append(RULE["single"], "w") 92 | RULE["single"] = append(RULE["single"], "W") 93 | RULE["rocket"] = append(RULE["rocket"], "Ww") 94 | 95 | RULE["trio_single"] = make([]string, 0) 96 | RULE["trio_pair"] = make([]string, 0) 97 | 98 | for _, t := range RULE["trio"] { 99 | for _, s := range RULE["single"] { 100 | if s[0] != t[0] { 101 | RULE["trio_single"] = append(RULE["trio_single"], t+s) 102 | } 103 | } 104 | for _, p := range RULE["pair"] { 105 | if p[0] != t[0] { 106 | RULE["trio_pair"] = append(RULE["trio_pair"], t+p) 107 | } 108 | } 109 | } 110 | for _, num := range []int{2, 3, 4, 5} { 111 | seqTrioSingle := []string(nil) 112 | seqTrioPair := []string(nil) 113 | for _, seqTrio := range RULE["seq_trio"+strconv.Itoa(num)] { 114 | seq := make([]string, len(RULE["single"])) 115 | copy(seq, RULE["single"]) 116 | for i := 0; i < len(seqTrio); i = i + 3 { 117 | for k, v := range seq { 118 | if v[0] == seqTrio[i] { 119 | copy(seq[k:], seq[k+1:]) 120 | seq = seq[:len(seq)-1 ] 121 | break 122 | } 123 | } 124 | } 125 | for _, singleCombination := range combination(seq, len(seqTrio)/3) { 126 | seqTrioSingle = append(seqTrioSingle, seqTrio+singleCombination) 127 | var hasJoker bool 128 | for _, single := range singleCombination { 129 | if single == 'w' || single == 'W' { 130 | hasJoker = true 131 | } 132 | } 133 | if !hasJoker { 134 | seqTrioPair = append(seqTrioPair, seqTrio+singleCombination+singleCombination) 135 | } 136 | } 137 | } 138 | RULE["seq_trio_single"+strconv.Itoa(num)] = seqTrioSingle 139 | RULE["seq_trio_pair"+strconv.Itoa(num)] = seqTrioPair 140 | } 141 | 142 | RULE["bomb_single"] = []string(nil) 143 | RULE["bomb_pair"] = []string(nil) 144 | for _, b := range RULE["bomb"] { 145 | seq := make([]string, len(RULE["single"])) 146 | copy(seq, RULE["single"]) 147 | for i, single := range seq { 148 | if single[0] == b[0] { 149 | copy(seq[i:], seq[i+1:]) 150 | seq = seq[:len(seq)-1] 151 | } 152 | } 153 | for _,comb := range combination(seq,2){ 154 | RULE["bomb_single"] = append(RULE["bomb_single"],b+comb) 155 | if comb[0] != 'w' && comb[0] != 'W' && comb[1] != 'w' && comb[1] != 'W' { 156 | RULE["bomb_pair"] = append(RULE["bomb_pair"],b+comb+comb) 157 | } 158 | } 159 | } 160 | 161 | res,err := json.Marshal(RULE) 162 | if err != nil { 163 | panic("json marsha1 RULE err :" + err.Error()) 164 | } 165 | file, err := os.Create("rule.json") 166 | defer func(){ 167 | err = file.Close() 168 | if err != nil { 169 | logs.Error("generate err: %v",err) 170 | } 171 | }() 172 | if err != nil { 173 | panic("create rule.json err:" + err.Error()) 174 | } 175 | _,err = file.Write(res) 176 | if err != nil { 177 | panic("create rule.json err:" + err.Error()) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /common/model.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type Account struct { 4 | Id int `json:"id" db:"id"` 5 | Email string `json:"email" db:"email"` 6 | Username string `json:"username" db:"username"` 7 | Password string `json:"password" db:"password"` 8 | Coin int `json:"coin" db:"coin"` 9 | CreatedDate string `json:"created_date" db:"created_date"` 10 | UpdateDate string `json:"updated_date" db:"updated_date"` 11 | } 12 | -------------------------------------------------------------------------------- /common/poker.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "os" 5 | "io" 6 | "encoding/json" 7 | "fmt" 8 | ) 9 | 10 | var ( 11 | Pokers = make(map[string]*Combination, 16384) 12 | TypeToPokers = make(map[string][]*Combination, 38) 13 | ) 14 | 15 | type Combination struct { 16 | Type string 17 | Score int 18 | Poker string 19 | } 20 | 21 | func init() { 22 | path := "rule.json" 23 | _, err := os.Stat(path) 24 | if os.IsNotExist(err) { 25 | write() 26 | } 27 | file, err := os.Open(path) 28 | if err != nil { 29 | panic(err) 30 | } 31 | defer file.Close() 32 | 33 | var jsonStrByte []byte 34 | for { 35 | buf := make([]byte, 1024) 36 | readNum, err := file.Read(buf) 37 | if err != nil && err != io.EOF { 38 | panic(err) 39 | } 40 | for i := 0; i < readNum; i++ { 41 | jsonStrByte = append(jsonStrByte, buf[i]) 42 | } 43 | if 0 == readNum { 44 | break 45 | } 46 | } 47 | var rule = make(map[string][]string) 48 | err = json.Unmarshal(jsonStrByte, &rule) 49 | if err != nil { 50 | fmt.Printf("json unmarsha1 err:%v \n", err) 51 | return 52 | } 53 | for pokerType, pokers := range rule { 54 | for score, poker := range pokers { 55 | cards := SortStr(poker) 56 | p := &Combination{ 57 | Type: pokerType, 58 | Score: score, 59 | Poker: cards, 60 | } 61 | Pokers[cards] = p 62 | TypeToPokers[pokerType] = append(TypeToPokers[pokerType], p) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /common/protocol.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | const ( 4 | ReqCheat = 1 5 | ResCheat = 2 6 | 7 | ReqLogin = 11 8 | ResLogin = 12 9 | 10 | ReqRoomList = 13 11 | ResRoomList = 14 12 | 13 | ReqTableList = 15 14 | ResTableList = 16 15 | 16 | ReqJoinRoom = 17 17 | ResJoinRoom = 18 18 | 19 | ReqJoinTable = 19 20 | ResJoinTable = 20 21 | 22 | ReqNewTable = 21 23 | ResNewTable = 22 24 | 25 | ReqDealPoker = 31 26 | ResDealPoker = 32 27 | 28 | ReqCallScore = 33 29 | ResCallScore = 34 30 | 31 | ReqShowPoker = 35 32 | ResShowPoker = 36 33 | 34 | ReqShotPoker = 37 35 | ResShotPoker = 38 36 | 37 | ReqGameOver = 41 38 | ResGameOver = 42 39 | 40 | ReqChat = 43 41 | ResChat = 44 42 | 43 | ReqRestart = 45 44 | ResRestart = 46 45 | ) 46 | -------------------------------------------------------------------------------- /common/rule.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "sort" 5 | "github.com/astaxie/beego/logs" 6 | ) 7 | 8 | func SortStr(pokers string) (sortPokers string) { 9 | runeArr := make([]int, 0) 10 | for _, s := range pokers { 11 | runeArr = append(runeArr, int(s)) 12 | } 13 | sort.Ints(runeArr) 14 | res := make([]byte, 0) 15 | for _, v := range runeArr { 16 | res = append(res, byte(v)) 17 | } 18 | return string(res) 19 | } 20 | 21 | // 出的牌是否在手牌中存在 22 | func IsContains(parent, child string) (result bool) { 23 | for _, childCard := range child { 24 | inHand := false 25 | for i, parentCard := range parent { 26 | if childCard == parentCard { 27 | inHand = true 28 | tmp := []byte(parent) 29 | copy(tmp[i:], tmp[i+1:]) 30 | tmp = tmp[:len(tmp)-1] 31 | parent = string(tmp) 32 | break 33 | } 34 | } 35 | if !inHand { 36 | return 37 | } 38 | } 39 | return true 40 | } 41 | 42 | //将牌编号转换为扑克牌 43 | func ToPokers(num []int) (string) { 44 | totalCards := "A234567890JQK" 45 | res := make([]byte, 0) 46 | for _, poker := range num { 47 | if poker == 52 { 48 | res = append(res, 'W') 49 | } else if poker == 53 { 50 | res = append(res, 'w') 51 | } else { 52 | res = append(res, totalCards[poker%13]) 53 | } 54 | } 55 | return string(res) 56 | } 57 | 58 | //将牌转换为编号 59 | func ToPoker(card byte) (poker []int) { 60 | if card == 'W' { 61 | return []int{52} 62 | } 63 | if card == 'w' { 64 | return []int{53} 65 | } 66 | cards := "A234567890JQK" 67 | for i, c := range []byte(cards) { 68 | if c == card { 69 | return []int{i, i + 13, i + 13*2, i + 13*3} 70 | } 71 | } 72 | return []int{54} 73 | } 74 | 75 | //将机器人要出的牌转换为编号 76 | func pokersInHand(num []int, findPokers string) (pokers []int) { 77 | var isInResPokers = func(poker int) bool { 78 | for _, p := range pokers { 79 | if p == poker { 80 | return true 81 | } 82 | } 83 | return false 84 | } 85 | 86 | for _, poker := range findPokers { 87 | poker := ToPoker(byte(poker)) 88 | out: 89 | for _,pItem := range poker { 90 | for _, n := range num { 91 | if pItem == n && !isInResPokers(n) { 92 | pokers = append(pokers, pItem) 93 | break out 94 | } 95 | } 96 | } 97 | } 98 | return 99 | } 100 | 101 | //获得牌型和大小 102 | func pokersValue(pokers string) (cardType string, score int) { 103 | if combination, ok := Pokers[SortStr(pokers)]; ok { 104 | cardType = combination.Type 105 | score = combination.Score 106 | } 107 | return 108 | } 109 | 110 | //比较牌大小,并返回是否翻倍 111 | func ComparePoker(baseNum, comparedNum []int) (int, bool) { 112 | logs.Debug("comparedNum %v %v", baseNum, comparedNum) 113 | if len(baseNum) == 0 || len(comparedNum) == 0 { 114 | if len(baseNum) == 0 && len(comparedNum) == 0 { 115 | return 0, false 116 | } else { 117 | if len(baseNum) != 0 { 118 | return -1, false 119 | } else { 120 | comparedType, _ := pokersValue(ToPokers(comparedNum)) 121 | if comparedType == "rocket" || comparedType == "bomb" { 122 | return 1, true 123 | } 124 | return 1, false 125 | } 126 | } 127 | } 128 | baseType, baseScore := pokersValue(ToPokers(baseNum)) 129 | comparedType, comparedScore := pokersValue(ToPokers(comparedNum)) 130 | logs.Debug("compare poker %v, %v, %v, %v", baseType, baseScore, comparedType, comparedScore) 131 | if baseType == comparedType { 132 | return comparedScore - baseScore, false 133 | } 134 | if comparedType == "rocket" { 135 | return 1, true 136 | } 137 | if baseType == "rocket" { 138 | return -1, false 139 | } 140 | if comparedType == "bomb" { 141 | return 1, true 142 | } 143 | return 0, false 144 | } 145 | 146 | //查找手牌中是否有比被比较牌型大的牌 147 | func CardsAbove(handsNum, lastShotNum []int) (aboveNum []int) { 148 | handCards := ToPokers(handsNum) 149 | turnCards := ToPokers(lastShotNum) 150 | cardType, cardScore := pokersValue(turnCards) 151 | logs.Debug("CardsAbove handsNum %v ,lastShotNum %v, handCards %v,cardType %v,turnCards %v", 152 | handsNum, lastShotNum, handCards, cardType, turnCards) 153 | if len(cardType) == 0 { 154 | return 155 | } 156 | for _, combination := range TypeToPokers[cardType] { 157 | if combination.Score > cardScore && IsContains(handCards, combination.Poker) { 158 | aboveNum = pokersInHand(handsNum, combination.Poker) 159 | return 160 | } 161 | } 162 | if cardType != "boom" && cardType != "rocket" { 163 | for _, combination := range TypeToPokers["boom"] { 164 | if IsContains(handCards, combination.Poker) { 165 | aboveNum = pokersInHand(handsNum, combination.Poker) 166 | return 167 | } 168 | } 169 | } else if IsContains(handCards, "Ww") { 170 | aboveNum = pokersInHand(handsNum, "Ww") 171 | return 172 | } 173 | return 174 | } 175 | -------------------------------------------------------------------------------- /conf/app.conf: -------------------------------------------------------------------------------- 1 | [dev] 2 | 3 | http_port=80 4 | 5 | ;日志相关配置 6 | log_path=./logs/game.log 7 | log_level=debug 8 | 9 | ;sqlite配置 10 | db_path=./db/landlord.db 11 | 12 | 13 | [testing] 14 | 15 | http_port=80 16 | 17 | ;日志相关配置 18 | log_path=./logs/game.log 19 | log_level=warn 20 | 21 | ;sqlite配置 22 | db_path=./db/landlord.db 23 | 24 | [product] 25 | 26 | http_port=80 27 | 28 | ;日志相关配置 29 | log_path=./logs/game.log 30 | log_level=error 31 | 32 | ;sqlite配置 33 | db_path=./db/landlord.db -------------------------------------------------------------------------------- /controllers/Index.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/astaxie/beego/logs" 6 | "html/template" 7 | "landlord/common" 8 | "net/http" 9 | "strconv" 10 | ) 11 | 12 | func Index(w http.ResponseWriter, r *http.Request) { 13 | ret := make(map[string]interface{}) 14 | t, err := template.ParseFiles("templates/poker.html") 15 | if err != nil { 16 | logs.Error("user request Index - can't find template file %s", "templates/poker.html") 17 | return 18 | } 19 | userId := "" 20 | cookie, err := r.Cookie("userid") 21 | if err != nil { 22 | logs.Error("user request Index - get cookie err: %v", err) 23 | } else { 24 | userId = cookie.Value 25 | } 26 | username := "" 27 | cookie, err = r.Cookie("username") 28 | if err != nil { 29 | logs.Error("user request Index - get cookie err: %v", err) 30 | } else { 31 | username = cookie.Value 32 | } 33 | logs.Debug("user request Index - userId [%v]", userId) 34 | logs.Debug("user request Index - username [%v]", username) 35 | user := make(map[string]interface{}) 36 | user["uid"] = userId 37 | user["username"] = username 38 | res, err := json.Marshal(user) 39 | if err != nil { 40 | logs.Error("user request Index - json marsha1 user %v ,err:%v", user, err) 41 | return 42 | } 43 | ret["user"] = string(res) 44 | ret["port"] = strconv.Itoa(common.GameConfInfo.HttpPort) 45 | err = t.Execute(w, ret) 46 | if err != nil { 47 | logs.Error("user request Index - template execute err : %v", err) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /controllers/Login.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "net/http" 5 | "github.com/astaxie/beego/logs" 6 | "fmt" 7 | "crypto/md5" 8 | "landlord/common" 9 | ) 10 | 11 | func Login(w http.ResponseWriter, r *http.Request) { 12 | defer func() { 13 | if r:= recover(); r != nil { 14 | logs.Error("user request Login - Login panic:%v ",r) 15 | } 16 | }() 17 | var ret = []byte{'1'} 18 | defer func() { 19 | w.Header().Set("Access-Control-Allow-Origin", "*") 20 | _,err := w.Write(ret) 21 | if err != nil { 22 | logs.Error("") 23 | } 24 | }() 25 | email := r.FormValue("email") 26 | if len(email) == 0 { 27 | email = r.PostFormValue("email") 28 | if email == "" { 29 | logs.Error("user request Login - err: email is empty") 30 | return 31 | } 32 | } 33 | password := r.FormValue("password") 34 | if len(password) == 0 { 35 | password = r.PostFormValue("password") 36 | if password == "" { 37 | logs.Error("user request Login - err: password is empty") 38 | return 39 | } 40 | } 41 | md5Password := fmt.Sprintf("%x", md5.Sum([]byte(password))) 42 | var account = common.Account{} 43 | //err := common.GameConfInfo.MysqlConf.Pool.Get(&account, "select * from account where username=? and password", email,md5Password) 44 | row := common.GameConfInfo.Db.QueryRow("SELECT * FROM `account` WHERE username=? AND password=?",email,md5Password) 45 | if row != nil { 46 | err := row.Scan(&account.Id,&account.Email,&account.Username,&account.Password,&account.Coin,&account.CreatedDate,&account.UpdateDate) 47 | if err != nil { 48 | cookie := http.Cookie{Name: "userid", Value: string(account.Id), Path: "/", MaxAge: 86400} 49 | http.SetCookie(w, &cookie) 50 | cookie = http.Cookie{Name: "username", Value: account.Username, Path: "/", MaxAge: 86400} 51 | http.SetCookie(w, &cookie) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /controllers/Logout.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "net/http" 5 | "github.com/astaxie/beego/logs" 6 | ) 7 | 8 | func LoginOut(w http.ResponseWriter, r *http.Request) { 9 | defer func() { 10 | if r := recover(); r != nil { 11 | logs.Error("LoginOut panic:%v ", r) 12 | } 13 | }() 14 | cookie := http.Cookie{Name: "user", Path: "/", MaxAge: -1} 15 | http.SetCookie(w, &cookie) 16 | 17 | _,err := w.Write([]byte{'1'}) 18 | if err != nil { 19 | logs.Error("LoginOut err: %v",err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /controllers/Register.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/astaxie/beego/logs" 8 | "landlord/common" 9 | "net/http" 10 | "strconv" 11 | "time" 12 | ) 13 | 14 | func Register(w http.ResponseWriter, r *http.Request) { 15 | defer func() { 16 | if r := recover(); r != nil { 17 | logs.Error("Register panic:%v ", r) 18 | } 19 | }() 20 | var ret = []byte{'1'} 21 | defer func() { 22 | w.Header().Set("Access-Control-Allow-Origin", "*") 23 | _, err := w.Write(ret) 24 | if err != nil { 25 | logs.Error("user request Register - err : %v", err) 26 | } 27 | }() 28 | username := r.FormValue("username") 29 | if len(username) == 0 { 30 | username = r.PostFormValue("username") 31 | if username == "" { 32 | logs.Error("register err: username is empty") 33 | return 34 | } 35 | } 36 | password := r.FormValue("password") 37 | if len(password) == 0 { 38 | password = r.PostFormValue("password") 39 | if password == "" { 40 | logs.Error("register err: password is empty") 41 | return 42 | } 43 | } 44 | logs.Debug("user request register : username=%v, password=%v ", username, password) 45 | 46 | var account = common.Account{} 47 | row := common.GameConfInfo.Db.QueryRow("select * from account where username=?", username) 48 | err := row.Scan(&account.Id, &account.Email, &account.Username, &account.Password, &account.Coin, &account.CreatedDate, &account.UpdateDate) 49 | if err != nil { 50 | now := time.Now().Format("2006-01-02 15:04:05") 51 | md5Password := fmt.Sprintf("%x", md5.Sum([]byte(password))) 52 | stmt, err := common.GameConfInfo.Db.Prepare(`insert into account(email,username,password,coin,created_date,updated_date) values(?,?,?,?,?,?) `) 53 | if err != nil { 54 | logs.Error("insert new account [%v] err : %v", username, err) 55 | return 56 | } 57 | result, err := stmt.Exec(username, username, md5Password, 10000, now, now) 58 | if err != nil { 59 | logs.Error("insert new account [%v] err : %v", username, err) 60 | return 61 | } 62 | lastInsertId, err := result.LastInsertId() 63 | if err != nil { 64 | logs.Error("insert new account [%v] get last insert id err : %v", username, err) 65 | return 66 | } 67 | ret, err = json.Marshal(map[string]interface{}{"uid": lastInsertId, "username": username}) 68 | if err != nil { 69 | logs.Error("json marsha1 failed err:%v", err) 70 | return 71 | } 72 | cookie := http.Cookie{Name: "userid", Value: strconv.Itoa(int(lastInsertId)), Path: "/", MaxAge: 86400} 73 | http.SetCookie(w, &cookie) 74 | cookie = http.Cookie{Name: "username", Value: username, Path: "/", MaxAge: 86400} 75 | http.SetCookie(w, &cookie) 76 | } else { 77 | logs.Debug("user [%v] request register err: user already exists", username) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /db/landlord.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/db/landlord.db -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module landlord 2 | 3 | go 1.12 4 | 5 | replace ( 6 | golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 => github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85 7 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a => github.com/golang/net v0.0.0-20181114220301-adae6a3d119a 8 | golang.org/x/sys v0.3.0 => github.com/golang/sys v0.3.0 9 | golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0 10 | ) 11 | 12 | require ( 13 | github.com/astaxie/beego v1.11.1 14 | github.com/go-sql-driver/mysql v1.4.1 15 | github.com/gorilla/websocket v1.4.0 16 | github.com/jmoiron/sqlx v1.2.0 17 | github.com/mattn/go-sqlite3 v1.10.0 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= 2 | github.com/astaxie/beego v1.11.1 h1:6DESefxW5oMcRLFRKi53/6exzup/IR6N4EzzS1n6CnQ= 3 | github.com/astaxie/beego v1.11.1/go.mod h1:i69hVzgauOPSw5qeyF4GVZhn7Od0yG5bbCGzmhbWxgQ= 4 | github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= 5 | github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= 6 | github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff/go.mod h1:PhH1ZhyCzHKt4uAasyx+ljRCgoezetRNf59CUtwUkqY= 7 | github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= 8 | github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= 9 | github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= 10 | github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U= 11 | github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= 12 | github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= 13 | github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= 14 | github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 15 | github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= 16 | github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 17 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 18 | github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 19 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 20 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 21 | github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:B7ZbAFz7NOmvpUE5RGtu3u0WIizy5GdvbNpEf4RPnWs= 22 | github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:uZvAcrsnNaCxlh1HorK5dUQHGmEKPh2H/Rl1kehswPo= 23 | github.com/golang/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:98y8FxUyMjTdJ5eOj/8vzuiVO14/dkJ98NYhEPG8QGY= 24 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 25 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 26 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 27 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 28 | github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= 29 | github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= 30 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 31 | github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 32 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 33 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 34 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 35 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 36 | github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= 37 | github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= 38 | github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= 39 | github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE= 40 | github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= 41 | github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc= 42 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 44 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 45 | -------------------------------------------------------------------------------- /main.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/main.exe -------------------------------------------------------------------------------- /main/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/astaxie/beego/config" 5 | "github.com/astaxie/beego/logs" 6 | "landlord/common" 7 | "os" 8 | ) 9 | 10 | var ( 11 | gameConf = &common.GameConfInfo 12 | ) 13 | 14 | func initConf() (err error) { 15 | environment := os.Getenv("ENV") 16 | if environment != "dev" && environment != "testing" && environment != "product" { 17 | environment = "product" 18 | } 19 | logs.Info("the running environment is : %s", environment) 20 | conf, err := config.NewConfig("ini", "conf/app.conf") 21 | if err != nil { 22 | logs.Error("new conf failed ,err : %v", err) 23 | return 24 | } 25 | 26 | environment += "::" 27 | gameConf.HttpPort, err = conf.Int(environment + "http_port") 28 | if err != nil { 29 | logs.Error("init http_port failed,err: %v", err) 30 | return 31 | } 32 | 33 | logs.Debug("read conf succ , http port : %v", gameConf.HttpPort) 34 | 35 | //todo 日志配置 36 | gameConf.LogPath = conf.String(environment + "log_path") 37 | if len(gameConf.LogPath) == 0 { 38 | gameConf.LogPath = "./logs/game.log" 39 | } 40 | 41 | logs.Debug("read conf succ , LogPath : %v", gameConf.LogPath) 42 | gameConf.LogLevel = conf.String(environment + "log_level") 43 | if len(gameConf.LogLevel) == 0 { 44 | gameConf.LogLevel = "debug" 45 | } 46 | logs.Debug("read conf succ , LogLevel : %v", gameConf.LogLevel) 47 | 48 | //todo sqlite配置 49 | gameConf.DbPath = conf.String(environment + "db_path") 50 | if len(gameConf.DbPath) == 0 { 51 | gameConf.DbPath = "./db/landlord.db" 52 | } 53 | logs.Debug("read conf succ , DbPath : %v", gameConf.DbPath) 54 | return 55 | } 56 | -------------------------------------------------------------------------------- /main/init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/astaxie/beego/logs" 8 | _ "github.com/mattn/go-sqlite3" 9 | ) 10 | 11 | func conversionLogLevel(logLevel string) int { 12 | switch logLevel { 13 | case "debug": 14 | return logs.LevelDebug 15 | case "warn": 16 | return logs.LevelWarn 17 | case "info": 18 | return logs.LevelInfo 19 | case "trace": 20 | return logs.LevelTrace 21 | } 22 | return logs.LevelDebug 23 | } 24 | 25 | func initLogger() (err error) { 26 | config := make(map[string]interface{}) 27 | config["filename"] = gameConf.LogPath 28 | config["level"] = conversionLogLevel(gameConf.LogLevel) 29 | 30 | configStr, err := json.Marshal(config) 31 | if err != nil { 32 | fmt.Println("marsha1 faild,err", err) 33 | return 34 | } 35 | err = logs.SetLogger(logs.AdapterFile, string(configStr)) 36 | if err != nil { 37 | logs.Error("init logger :%v",err) 38 | } 39 | return 40 | } 41 | 42 | func initSqlite() (err error) { 43 | gameConf.Db, err = sql.Open("sqlite3", gameConf.DbPath) 44 | if err != nil { 45 | logs.Error("initSqlite err : %v", err) 46 | return 47 | } 48 | var stmt *sql.Stmt 49 | stmt, err = gameConf.Db.Prepare(`CREATE TABLE IF NOT EXISTS "account" ("id" INTEGER NOT NULL,"email" text(32),"username" TEXT(16),"password" TEXT(32),"coin" integer,"created_date" TEXT(32),"updated_date" TEXT(32),PRIMARY KEY ("id"))`) 50 | if err != nil { 51 | logs.Error("initSqlite err : %v", err) 52 | return 53 | } 54 | _, err = stmt.Exec() 55 | if err != nil { 56 | logs.Error("create table err:", err) 57 | return 58 | } 59 | return 60 | } 61 | 62 | func initSec() (err error) { 63 | err = initLogger() 64 | if err != nil { 65 | logs.Error("init logger failed,err:%v", err) 66 | return 67 | } 68 | err = initSqlite() 69 | if err != nil { 70 | logs.Error("init sqlite failed,err:%v", err) 71 | return 72 | } 73 | logs.Info("init sec succ") 74 | return 75 | } 76 | -------------------------------------------------------------------------------- /main/main.go: -------------------------------------------------------------------------------- 1 | package main // import "landlord" 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/astaxie/beego/logs" 7 | "io" 8 | _ "landlord/router" 9 | "net/http" 10 | "os" 11 | "strconv" 12 | ) 13 | 14 | func main() { 15 | err := initConf() 16 | if err != nil { 17 | logs.Error("init conf err:%v", err) 18 | return 19 | } 20 | defer func() { 21 | if gameConf.Db != nil { 22 | err = gameConf.Db.Close() 23 | if err != nil { 24 | logs.Error("main close sqllite db err :%v", err) 25 | } 26 | } 27 | }() 28 | err = initSec() 29 | if err != nil { 30 | logs.Error("init sec err:%v", err) 31 | return 32 | } 33 | 34 | var addr = flag.String("addr", fmt.Sprintf(":%d", gameConf.HttpPort), "http service address") 35 | err = http.ListenAndServe(*addr, nil) 36 | if err != nil { 37 | logs.Error("ListenAndServe: err:%v", err) 38 | } 39 | } 40 | 41 | func init() { //生成pid文件,保存pid 42 | pidFileName := "pid" 43 | fileInfo, err := os.Stat(pidFileName) 44 | if err != nil { 45 | if os.IsNotExist(err) { 46 | err = os.Mkdir(pidFileName, os.ModePerm) 47 | fileInfo, _ = os.Stat(pidFileName) 48 | } 49 | } 50 | if fileInfo.IsDir() { 51 | pid := os.Getpid() 52 | pidFile, err := os.OpenFile(pidFileName+"/landlord.pid", os.O_RDWR|os.O_CREATE, 0766) 53 | if err != nil { 54 | logs.Error("open pidFile [%s] error :%v", pidFileName, err) 55 | return 56 | } 57 | err = pidFile.Truncate(0) //清空数据 58 | 59 | _, err = io.WriteString(pidFile, strconv.Itoa(pid)) 60 | if err != nil { 61 | logs.Error("write pid error :%v", err) 62 | } 63 | 64 | err = pidFile.Close() 65 | if err != nil { 66 | logs.Error("close pid file err: %v", err) 67 | } 68 | } else { 69 | logs.Error("pidFile [%s] is exists and not dir", pidFileName) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "net/http" 5 | "landlord/service" 6 | "landlord/controllers" 7 | ) 8 | 9 | func init() { 10 | http.HandleFunc("/", controllers.Index) 11 | http.HandleFunc("/login", controllers.Login) 12 | http.HandleFunc("/loginOut", controllers.LoginOut) 13 | http.HandleFunc("/reg", controllers.Register) 14 | 15 | http.HandleFunc("/ws", service.ServeWs) 16 | 17 | // 设置静态目录 18 | static := http.FileServer(http.Dir("./static")) 19 | http.Handle("/static/", http.StripPrefix("/static/", static)) 20 | } 21 | -------------------------------------------------------------------------------- /service/client.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "time" 7 | "github.com/gorilla/websocket" 8 | "github.com/astaxie/beego/logs" 9 | "encoding/json" 10 | "landlord/common" 11 | "strconv" 12 | ) 13 | 14 | const ( 15 | writeWait = 1 * time.Second 16 | pongWait = 60 * time.Second 17 | pingPeriod = (pongWait * 9) / 10 18 | maxMessageSize = 512 19 | 20 | RoleFarmer = 0 21 | RoleLandlord = 1 22 | ) 23 | 24 | var ( 25 | newline = []byte{'\n'} 26 | space = []byte{' '} 27 | upGrader = websocket.Upgrader{ 28 | ReadBufferSize: 1024, 29 | WriteBufferSize: 1024, 30 | CheckOrigin: func(r *http.Request) bool { return true }, 31 | }//不验证origin 32 | ) 33 | 34 | type UserId int 35 | 36 | type UserInfo struct { 37 | UserId UserId `json:"user_id"` 38 | Username string `json:"username"` 39 | Coin int `json:"coin"` 40 | Role int 41 | } 42 | 43 | type Client struct { 44 | conn *websocket.Conn 45 | UserInfo *UserInfo 46 | Room *Room 47 | Table *Table 48 | HandPokers []int 49 | Ready bool 50 | IsCalled bool //是否叫完分 51 | Next *Client //链表 52 | IsRobot bool 53 | toRobot chan []interface{} //发送给robot的消息 54 | toServer chan []interface{} //robot发送给服务器 55 | } 56 | 57 | //重置状态 58 | func (c *Client) reset() { 59 | c.UserInfo.Role = 1 60 | c.HandPokers = make([]int, 0, 21) 61 | c.Ready = false 62 | c.IsCalled = false 63 | } 64 | 65 | //发送房间内已有的牌桌信息 66 | func (c *Client) sendRoomTables() { 67 | res := make([][2]int, 0) 68 | for _, table := range c.Room.Tables { 69 | if len(table.TableClients) < 3 { 70 | res = append(res, [2]int{int(table.TableId), len(table.TableClients)}) 71 | } 72 | } 73 | c.sendMsg([]interface{}{common.ResTableList, res}) 74 | } 75 | 76 | func (c *Client) sendMsg(msg []interface{}) { 77 | if c.IsRobot { 78 | c.toRobot <- msg 79 | return 80 | } 81 | msgByte, err := json.Marshal(msg) 82 | if err != nil { 83 | logs.Error("send msg [%v] marsha1 err:%v", string(msgByte), err) 84 | return 85 | } 86 | err = c.conn.SetWriteDeadline(time.Now().Add(writeWait)) 87 | if err != nil { 88 | logs.Error("send msg SetWriteDeadline [%v] err:%v", string(msgByte), err) 89 | return 90 | } 91 | w, err := c.conn.NextWriter(websocket.TextMessage) 92 | if err != nil { 93 | err = c.conn.Close() 94 | if err != nil { 95 | logs.Error("close client err: %v",err) 96 | } 97 | } 98 | _,err = w.Write(msgByte) 99 | if err != nil { 100 | logs.Error("Write msg [%v] err: %v",string(msgByte),err) 101 | } 102 | if err := w.Close(); err != nil { 103 | err = c.conn.Close() 104 | if err != nil { 105 | logs.Error("close err: %v",err) 106 | } 107 | } 108 | } 109 | 110 | //光比客户端 111 | func (c *Client) close() { 112 | if c.Table != nil { 113 | for _, client := range c.Table.TableClients { 114 | if c.Table.Creator == c && c != client { 115 | c.Table.Creator = client 116 | } 117 | if c == client.Next { 118 | client.Next = nil 119 | } 120 | } 121 | if len(c.Table.TableClients) != 1 { 122 | for _, client := range c.Table.TableClients { 123 | if client != client.Table.Creator { 124 | client.Table.Creator.Next = client 125 | } 126 | } 127 | } 128 | if len(c.Table.TableClients) == 1 { 129 | c.Table.Creator = nil 130 | delete(c.Room.Tables, c.Table.TableId) 131 | return 132 | } 133 | delete(c.Table.TableClients, c.UserInfo.UserId) 134 | if c.Table.State == GamePlaying { 135 | c.Table.syncUser() 136 | //c.Table.reset() 137 | } 138 | if c.IsRobot { 139 | close(c.toRobot) 140 | close(c.toServer) 141 | } 142 | } 143 | } 144 | 145 | //可能是因为版本问题,导致有些未处理的error 146 | func (c *Client) readPump() { 147 | defer func() { 148 | //logs.Debug("readPump exit") 149 | c.conn.Close() 150 | c.close() 151 | if c.Room.AllowRobot { 152 | if c.Table != nil { 153 | for _, client := range c.Table.TableClients { 154 | client.close() 155 | } 156 | } 157 | } 158 | }() 159 | c.conn.SetReadLimit(maxMessageSize) 160 | c.conn.SetReadDeadline(time.Now().Add(pongWait)) 161 | c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) 162 | for { 163 | _, message, err := c.conn.ReadMessage() 164 | if err != nil { 165 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { 166 | logs.Error("websocket user_id[%d] unexpected close error: %v", c.UserInfo.UserId, err) 167 | } 168 | return 169 | } 170 | message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1)) 171 | var data []interface{} 172 | err = json.Unmarshal(message, &data) 173 | if err != nil { 174 | logs.Error("message unmarsha1 err, user_id[%d] err:%v", c.UserInfo.UserId, err) 175 | } else { 176 | wsRequest(data, c) 177 | } 178 | } 179 | } 180 | 181 | //心跳 182 | func (c *Client) Ping() { 183 | ticker := time.NewTicker(pingPeriod) 184 | defer func() { 185 | ticker.Stop() 186 | }() 187 | for { 188 | select { 189 | case <-ticker.C: 190 | c.conn.SetWriteDeadline(time.Now().Add(writeWait)) 191 | if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { 192 | return 193 | } 194 | } 195 | } 196 | } 197 | 198 | func ServeWs(w http.ResponseWriter, r *http.Request) { 199 | conn, err := upGrader.Upgrade(w, r, nil) 200 | if err != nil { 201 | logs.Error("upgrader err:%v", err) 202 | return 203 | } 204 | client := &Client{conn: conn, HandPokers: make([]int, 0, 21), UserInfo: &UserInfo{}} 205 | var userId int 206 | var username string 207 | cookie, err := r.Cookie("userid") 208 | 209 | if err != nil { 210 | logs.Error("get cookie err: %v", err) 211 | } else { 212 | userIdStr := cookie.Value 213 | userId, err = strconv.Atoi(userIdStr) 214 | } 215 | cookie, err = r.Cookie("username") 216 | 217 | if err != nil { 218 | logs.Error("get cookie err: %v", err) 219 | } else { 220 | username = cookie.Value 221 | } 222 | 223 | if userId != 0 && username != "" { 224 | client.UserInfo.UserId = UserId(userId) 225 | client.UserInfo.Username = username 226 | go client.readPump() 227 | go client.Ping() 228 | return 229 | } 230 | logs.Error("user need login first") 231 | client.conn.Close() 232 | } 233 | -------------------------------------------------------------------------------- /service/request.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/astaxie/beego/logs" 5 | "landlord/common" 6 | ) 7 | 8 | //处理websocket请求 9 | func wsRequest(data []interface{}, client *Client) { 10 | defer func() { 11 | if r := recover(); r != nil { 12 | logs.Error("wsRequest panic:%v ", r) 13 | } 14 | }() 15 | if len(data) < 1 { 16 | return 17 | } 18 | var req int 19 | if r, ok := data[0].(float64); ok { 20 | req = int(r) 21 | } 22 | switch req { 23 | case common.ReqCheat: 24 | if len(data) < 2 { 25 | logs.Error("user [%d] request ReqCheat ,but missing user id", client.UserInfo.UserId) 26 | return 27 | } 28 | 29 | case common.ReqLogin: 30 | client.sendMsg([]interface{}{common.ResLogin, client.UserInfo.UserId, client.UserInfo.Username}) 31 | 32 | case common.ReqRoomList: 33 | client.sendMsg([]interface{}{common.ResRoomList}) 34 | 35 | case common.ReqTableList: 36 | client.sendRoomTables() 37 | 38 | case common.ReqJoinRoom: 39 | if len(data) < 2 { 40 | logs.Error("user [%d] request join room ,but missing room id", client.UserInfo.UserId) 41 | return 42 | } 43 | var roomId int 44 | if id, ok := data[1].(float64); ok { 45 | roomId = int(id) 46 | } 47 | roomManager.Lock.RLock() 48 | defer roomManager.Lock.RUnlock() 49 | if room, ok := roomManager.Rooms[roomId]; ok { 50 | client.Room = room 51 | res := make([][2]int, 0) 52 | for _, table := range client.Room.Tables { 53 | if len(table.TableClients) < 3 { 54 | res = append(res, [2]int{int(table.TableId), len(table.TableClients)}) 55 | } 56 | } 57 | client.sendMsg([]interface{}{common.ResJoinRoom, res}) 58 | } 59 | 60 | case common.ReqNewTable: 61 | table := client.Room.newTable(client) 62 | table.joinTable(client) 63 | 64 | case common.ReqJoinTable: 65 | if len(data) < 2 { 66 | return 67 | } 68 | var tableId TableId 69 | if id, ok := data[1].(float64); ok { 70 | tableId = TableId(id) 71 | } 72 | if client.Room == nil { 73 | return 74 | } 75 | client.Room.Lock.RLock() 76 | defer client.Room.Lock.RUnlock() 77 | 78 | if table, ok := client.Room.Tables[tableId]; ok { 79 | table.joinTable(client) 80 | } 81 | client.sendRoomTables() 82 | case common.ReqDealPoker: 83 | if client.Table.State == GameEnd { 84 | client.Ready = true 85 | } 86 | case common.ReqCallScore: 87 | logs.Debug("[%v] ReqCallScore %v", client.UserInfo.Username, data) 88 | client.Table.Lock.Lock() 89 | defer client.Table.Lock.Unlock() 90 | 91 | if client.Table.State != GameCallScore { 92 | logs.Debug("game call score at run time ,%v", client.Table.State) 93 | return 94 | } 95 | if client.Table.GameManage.Turn == client || client.Table.GameManage.FirstCallScore == client { 96 | client.Table.GameManage.Turn = client.Next 97 | } else { 98 | logs.Debug("user [%v] call score turn err ") 99 | } 100 | if len(data) < 2 { 101 | return 102 | } 103 | var score int 104 | if s, ok := data[1].(float64); ok { 105 | score = int(s) 106 | } 107 | 108 | if score > 0 && score < client.Table.GameManage.MaxCallScore || score > 3 { 109 | logs.Error("player[%d] call score[%d] cheat", client.UserInfo.UserId, score) 110 | return 111 | } 112 | if score > client.Table.GameManage.MaxCallScore { 113 | client.Table.GameManage.MaxCallScore = score 114 | client.Table.GameManage.MaxCallScoreTurn = client 115 | } 116 | client.IsCalled = true 117 | callEnd := score == 3 || client.Table.allCalled() 118 | userCall := []interface{}{common.ResCallScore, client.UserInfo.UserId, score, callEnd} 119 | for _, c := range client.Table.TableClients { 120 | c.sendMsg(userCall) 121 | } 122 | if callEnd { 123 | logs.Debug("call score end") 124 | client.Table.callEnd() 125 | } 126 | case common.ReqShotPoker: 127 | logs.Debug("user [%v] ReqShotPoker %v", client.UserInfo.Username, data) 128 | client.Table.Lock.Lock() 129 | defer func() { 130 | client.Table.GameManage.Turn = client.Next 131 | client.Table.Lock.Unlock() 132 | }() 133 | 134 | if client.Table.GameManage.Turn != client { 135 | logs.Error("shot poker err,not your [%d] turn .[%d]", client.UserInfo.UserId, client.Table.GameManage.Turn.UserInfo.UserId) 136 | return 137 | } 138 | if len(data) > 1 { 139 | if pokers, ok := data[1].([]interface{}); ok { 140 | shotPokers := make([]int, 0, len(pokers)) 141 | for _, item := range pokers { 142 | if i, ok := item.(float64); ok { 143 | poker := int(i) 144 | inHand := false 145 | for _, handPoker := range client.HandPokers { 146 | if handPoker == poker { 147 | inHand = true 148 | break 149 | } 150 | } 151 | if inHand { 152 | shotPokers = append(shotPokers, poker) 153 | } else { 154 | logs.Warn("player[%d] play non-exist poker", client.UserInfo.UserId) 155 | res := []interface{}{common.ResShotPoker, client.UserInfo.UserId, []int{}} 156 | for _, c := range client.Table.TableClients { 157 | c.sendMsg(res) 158 | } 159 | return 160 | } 161 | } 162 | } 163 | if len(shotPokers) > 0 { 164 | compareRes, isMulti := common.ComparePoker(client.Table.GameManage.LastShotPoker, shotPokers) 165 | if client.Table.GameManage.LastShotClient != client && compareRes < 1 { 166 | logs.Warn("player[%d] shot poker %v small than last shot poker %v ", client.UserInfo.UserId, shotPokers, client.Table.GameManage.LastShotPoker) 167 | res := []interface{}{common.ResShotPoker, client.UserInfo.UserId, []int{}} 168 | for _, c := range client.Table.TableClients { 169 | c.sendMsg(res) 170 | } 171 | return 172 | } 173 | if isMulti { 174 | client.Table.GameManage.Multiple *= 2 175 | } 176 | client.Table.GameManage.LastShotClient = client 177 | client.Table.GameManage.LastShotPoker = shotPokers 178 | for _, shotPoker := range shotPokers { 179 | for i, poker := range client.HandPokers { 180 | if shotPoker == poker { 181 | copy(client.HandPokers[i:], client.HandPokers[i+1:]) 182 | client.HandPokers = client.HandPokers[:len(client.HandPokers)-1] 183 | break 184 | } 185 | } 186 | } 187 | } 188 | res := []interface{}{common.ResShotPoker, client.UserInfo.UserId, shotPokers} 189 | for _, c := range client.Table.TableClients { 190 | c.sendMsg(res) 191 | } 192 | if len(client.HandPokers) == 0 { 193 | client.Table.gameOver(client) 194 | } 195 | } 196 | } 197 | 198 | //case common.ReqGameOver: 199 | case common.ReqChat: 200 | if len(data) > 1 { 201 | switch data[1].(type) { 202 | case string: 203 | client.Table.chat(client, data[1].(string)) 204 | } 205 | } 206 | case common.ReqRestart: 207 | client.Table.Lock.Lock() 208 | defer client.Table.Lock.Unlock() 209 | 210 | if client.Table.State == GameEnd { 211 | client.Ready = true 212 | for _, c := range client.Table.TableClients { 213 | if c.Ready == false { 214 | return 215 | } 216 | } 217 | logs.Debug("restart") 218 | client.Table.reset() 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /service/robot.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/astaxie/beego/logs" 5 | "landlord/common" 6 | "time" 7 | ) 8 | 9 | func (c *Client) runRobot() { 10 | for { 11 | select { 12 | case msg, ok := <-c.toServer: 13 | if !ok { 14 | return 15 | } 16 | wsRequest(msg, c) 17 | case msg, ok := <-c.toRobot: 18 | if !ok { 19 | return 20 | } 21 | logs.Debug("robot [%v] receive message %v ", c.UserInfo.Username, msg) 22 | if len(msg) < 1 { 23 | logs.Error("send to robot [%v],message err ,%v", c.UserInfo.Username, msg) 24 | return 25 | } 26 | if act, ok := msg[0].(int); ok { 27 | protocolCode := int(act) 28 | switch protocolCode { 29 | case common.ResDealPoker: 30 | time.Sleep(time.Second) 31 | c.Table.Lock.RLock() 32 | if c.Table.GameManage.FirstCallScore == c { 33 | c.autoCallScore() 34 | } 35 | c.Table.Lock.RUnlock() 36 | 37 | case common.ResCallScore: 38 | if len(msg) < 4 { 39 | logs.Error("ResCallScore msg err:%v",msg) 40 | return 41 | } 42 | time.Sleep(time.Second) 43 | c.Table.Lock.RLock() 44 | if c.Table.GameManage.Turn == c && !c.IsCalled { 45 | var callEnd bool 46 | logs.Debug("ResCallScore %t",msg[3]) 47 | if res, ok := msg[3].(bool); ok { 48 | callEnd = bool(res) 49 | } 50 | if !callEnd { 51 | c.autoCallScore() 52 | } 53 | } 54 | c.Table.Lock.RUnlock() 55 | 56 | case common.ResShotPoker: 57 | time.Sleep(time.Second) 58 | c.Table.Lock.RLock() 59 | if c.Table.GameManage.Turn == c { 60 | c.autoShotPoker() 61 | } 62 | c.Table.Lock.RUnlock() 63 | 64 | case common.ResShowPoker: 65 | time.Sleep(time.Second) 66 | //logs.Debug("robot [%v] role [%v] receive message ResShowPoker turn :%v", c.UserInfo.Username, c.UserInfo.Role, c.Table.GameManage.Turn.UserInfo.Username) 67 | c.Table.Lock.RLock() 68 | if c.Table.GameManage.Turn == c || (c.Table.GameManage.Turn == nil && c.UserInfo.Role == RoleLandlord) { 69 | c.autoShotPoker() 70 | } 71 | c.Table.Lock.RUnlock() 72 | case common.ResGameOver: 73 | c.Ready = true 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | //自动出牌 81 | func (c *Client) autoShotPoker() { 82 | //因为机器人休眠一秒后才出牌,有可能因用户退出而关闭chan 83 | defer func() { 84 | err := recover() 85 | if err != nil { 86 | logs.Warn("autoShotPoker err : %v",err) 87 | } 88 | }() 89 | logs.Debug("robot [%v] auto-shot poker", c.UserInfo.Username) 90 | shotPokers := make([]int, 0) 91 | if len(c.Table.GameManage.LastShotPoker) == 0 || c.Table.GameManage.LastShotClient == c { 92 | shotPokers = append(shotPokers, c.HandPokers[0]) 93 | } else { 94 | shotPokers = common.CardsAbove(c.HandPokers, c.Table.GameManage.LastShotPoker) 95 | } 96 | float64Pokers := make([]interface{}, 0) 97 | for _, poker := range shotPokers { 98 | float64Pokers = append(float64Pokers, float64(poker)) 99 | } 100 | req := []interface{}{float64(common.ReqShotPoker)} 101 | req = append(req, float64Pokers) 102 | logs.Debug("robot [%v] autoShotPoker %v", c.UserInfo.Username, float64Pokers) 103 | c.toServer <- req 104 | } 105 | 106 | //自动叫分 107 | func (c *Client) autoCallScore() { 108 | defer func() { 109 | err := recover() 110 | if err != nil { 111 | logs.Warn("autoCallScore err : %v",err) 112 | } 113 | }() 114 | logs.Debug("robot [%v] autoCallScore", c.UserInfo.Username) 115 | c.toServer <- []interface{}{float64(common.ReqCallScore), float64(3)} 116 | } 117 | -------------------------------------------------------------------------------- /service/room.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "sync" 5 | "github.com/astaxie/beego/logs" 6 | ) 7 | 8 | var ( 9 | roomManager = RoomManager{ 10 | Rooms: map[int]*Room{ 11 | 1: { 12 | RoomId: 1, 13 | AllowRobot: true, 14 | EntranceFee: 200, 15 | Tables: make(map[TableId]*Table), 16 | }, 17 | 2: { 18 | RoomId: 2, 19 | AllowRobot: false, 20 | EntranceFee: 200, 21 | Tables: make(map[TableId]*Table), 22 | }, 23 | }, 24 | } 25 | ) 26 | 27 | type RoomId int 28 | 29 | type RoomManager struct { 30 | Lock sync.RWMutex 31 | Rooms map[int]*Room 32 | TableIdInc TableId 33 | } 34 | 35 | type Room struct { 36 | RoomId RoomId 37 | Lock sync.RWMutex 38 | AllowRobot bool 39 | Tables map[TableId]*Table 40 | EntranceFee int 41 | } 42 | 43 | //新建牌桌 44 | func (r *Room) newTable(client *Client) (table *Table) { 45 | roomManager.Lock.Lock() 46 | defer roomManager.Lock.Unlock() 47 | 48 | r.Lock.Lock() 49 | defer r.Lock.Unlock() 50 | roomManager.TableIdInc = roomManager.TableIdInc + 1 51 | table = &Table{ 52 | TableId: roomManager.TableIdInc, 53 | Creator: client, 54 | TableClients: make(map[UserId]*Client, 3), 55 | GameManage: &GameManage{ 56 | FirstCallScore: client, 57 | Multiple: 1, 58 | LastShotPoker: make([]int, 0), 59 | Pokers: make([]int, 0, 54), 60 | }, 61 | } 62 | r.Tables[table.TableId] = table 63 | logs.Debug("create new table ok! allow robot :%v", r.AllowRobot) 64 | return 65 | } 66 | 67 | //func init() { 68 | // go func() { //压测 69 | // time.Sleep(time.Second * 3) 70 | // for i:=0;i<1;i++{ 71 | // client := &Client{ 72 | // Room: roomManager.Rooms[1], 73 | // HandPokers: make([]int, 0, 21), 74 | // UserInfo: &UserInfo{ 75 | // UserId: UserId(rand.Intn(10000)), 76 | // Username: "ROBOT-0", 77 | // Coin: 10000, 78 | // }, 79 | // IsRobot: true, 80 | // toRobot: make(chan []interface{}, 3), 81 | // toServer: make(chan []interface{}, 3), 82 | // } 83 | // go client.runRobot() 84 | // table := client.Room.newTable(client) 85 | // table.joinTable(client) 86 | // } 87 | // }() 88 | //} -------------------------------------------------------------------------------- /service/table.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "github.com/astaxie/beego/logs" 6 | "landlord/common" 7 | "math/rand" 8 | "sort" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type TableId int 14 | 15 | const ( 16 | GameWaitting = iota 17 | GameCallScore 18 | GamePlaying 19 | GameEnd 20 | ) 21 | 22 | type Table struct { 23 | Lock sync.RWMutex 24 | TableId TableId 25 | State int 26 | Creator *Client 27 | TableClients map[UserId]*Client 28 | GameManage *GameManage 29 | } 30 | 31 | type GameManage struct { 32 | Turn *Client 33 | FirstCallScore *Client //每局轮转 34 | MaxCallScore int //最大叫分 35 | MaxCallScoreTurn *Client 36 | LastShotClient *Client 37 | Pokers []int 38 | LastShotPoker []int 39 | Multiple int //加倍 40 | } 41 | 42 | func (table *Table) allCalled() bool { 43 | for _, client := range table.TableClients { 44 | if !client.IsCalled { 45 | return false 46 | } 47 | } 48 | return true 49 | } 50 | 51 | //一局结束 52 | func (table *Table) gameOver(client *Client) { 53 | coin := table.Creator.Room.EntranceFee * table.GameManage.MaxCallScore * table.GameManage.Multiple 54 | table.State = GameEnd 55 | for _, c := range table.TableClients { 56 | res := []interface{}{common.ResGameOver, client.UserInfo.UserId} 57 | if client == c { 58 | res = append(res, coin*2-100) 59 | } else { 60 | res = append(res, coin) 61 | } 62 | for _, cc := range table.TableClients { 63 | if cc != c { 64 | userPokers := make([]int, 0, len(cc.HandPokers)+1) 65 | userPokers = append(append(userPokers, int(cc.UserInfo.UserId)), cc.HandPokers...) 66 | res = append(res, userPokers) 67 | } 68 | } 69 | c.sendMsg(res) 70 | } 71 | logs.Debug("table[%d] game over", table.TableId) 72 | } 73 | 74 | //叫分阶段结束 75 | func (table *Table) callEnd() { 76 | table.State = GamePlaying 77 | table.GameManage.FirstCallScore = table.GameManage.FirstCallScore.Next 78 | if table.GameManage.MaxCallScoreTurn == nil || table.GameManage.MaxCallScore == 0 { 79 | table.GameManage.MaxCallScoreTurn = table.Creator 80 | table.GameManage.MaxCallScore = 1 81 | //return 82 | } 83 | landLord := table.GameManage.MaxCallScoreTurn 84 | landLord.UserInfo.Role = RoleLandlord 85 | table.GameManage.Turn = landLord 86 | for _, poker := range table.GameManage.Pokers { 87 | landLord.HandPokers = append(landLord.HandPokers, poker) 88 | } 89 | res := []interface{}{common.ResShowPoker, landLord.UserInfo.UserId, table.GameManage.Pokers} 90 | for _, c := range table.TableClients { 91 | c.sendMsg(res) 92 | } 93 | } 94 | 95 | //客户端加入牌桌 96 | func (table *Table) joinTable(c *Client) { 97 | table.Lock.Lock() 98 | defer table.Lock.Unlock() 99 | if len(table.TableClients) > 2 { 100 | logs.Error("Player[%d] JOIN Table[%d] FULL", c.UserInfo.UserId, table.TableId) 101 | return 102 | } 103 | logs.Debug("[%v] user [%v] request join table", c.UserInfo.UserId, c.UserInfo.Username) 104 | if _, ok := table.TableClients[c.UserInfo.UserId]; ok { 105 | logs.Error("[%v] user [%v] already in this table", c.UserInfo.UserId, c.UserInfo.Username) 106 | return 107 | } 108 | 109 | c.Table = table 110 | c.Ready = true 111 | for _, client := range table.TableClients { 112 | if client.Next == nil { 113 | client.Next = c 114 | break 115 | } 116 | } 117 | table.TableClients[c.UserInfo.UserId] = c 118 | table.syncUser() 119 | if len(table.TableClients) == 3 { 120 | c.Next = table.Creator 121 | table.State = GameCallScore 122 | table.dealPoker() 123 | } else if c.Room.AllowRobot { 124 | go table.addRobot(c.Room) 125 | logs.Debug("robot join ok") 126 | } 127 | } 128 | 129 | //加入机器人 130 | func (table *Table) addRobot(room *Room) { 131 | logs.Debug("robot [%v] join table", fmt.Sprintf("ROBOT-%d", len(table.TableClients))) 132 | if len(table.TableClients) < 3 { 133 | client := &Client{ 134 | Room: room, 135 | HandPokers: make([]int, 0, 21), 136 | UserInfo: &UserInfo{ 137 | UserId: table.getRobotID() , 138 | Username: fmt.Sprintf("ROBOT-%d", len(table.TableClients)), 139 | Coin: 10000, 140 | }, 141 | IsRobot: true, 142 | toRobot: make(chan []interface{}, 3), 143 | toServer: make(chan []interface{}, 3), 144 | } 145 | go client.runRobot() 146 | table.joinTable(client) 147 | } 148 | } 149 | 150 | //生成随机robotID 151 | func (table *Table)getRobotID() (robot UserId) { 152 | time.Sleep(time.Microsecond * 10) 153 | rand.Seed(time.Now().UnixNano()) 154 | robot = UserId(rand.Intn(10000)) 155 | table.Lock.RLock() 156 | defer table.Lock.RUnlock() 157 | if _,ok := table.TableClients[robot];ok { 158 | return table.getRobotID() 159 | } 160 | return 161 | } 162 | 163 | //发牌 164 | func (table *Table) dealPoker() { 165 | logs.Debug("deal poker") 166 | table.GameManage.Pokers = make([]int, 0) 167 | for i := 0; i < 54; i++ { 168 | table.GameManage.Pokers = append(table.GameManage.Pokers, i) 169 | } 170 | table.ShufflePokers() 171 | for i := 0; i < 17; i++ { 172 | for _, client := range table.TableClients { 173 | client.HandPokers = append(client.HandPokers, table.GameManage.Pokers[len(table.GameManage.Pokers)-1]) 174 | table.GameManage.Pokers = table.GameManage.Pokers[:len(table.GameManage.Pokers)-1] 175 | } 176 | } 177 | response := make([]interface{}, 0, 3) 178 | response = append(append(append(response, common.ResDealPoker), table.GameManage.FirstCallScore.UserInfo.UserId), nil) 179 | for _, client := range table.TableClients { 180 | sort.Ints(client.HandPokers) 181 | response[len(response)-1] = client.HandPokers 182 | client.sendMsg(response) 183 | } 184 | } 185 | 186 | func (table *Table) chat(client *Client, msg string) { 187 | res := []interface{}{common.ResChat, client.UserInfo.UserId, msg} 188 | for _, c := range table.TableClients { 189 | c.sendMsg(res) 190 | } 191 | } 192 | 193 | func (table *Table) reset() { 194 | table.GameManage = &GameManage{ 195 | FirstCallScore: table.GameManage.FirstCallScore, 196 | Turn: nil, 197 | MaxCallScore: 0, 198 | MaxCallScoreTurn: nil, 199 | LastShotClient: nil, 200 | Pokers: table.GameManage.Pokers[:0], 201 | LastShotPoker: table.GameManage.LastShotPoker[:0], 202 | Multiple: 1, 203 | } 204 | table.State = GameCallScore 205 | if table.Creator != nil { 206 | table.Creator.sendMsg([]interface{}{common.ResRestart}) 207 | } 208 | for _, c := range table.TableClients { 209 | c.reset() 210 | } 211 | if len(table.TableClients) == 3 { 212 | table.dealPoker() 213 | } 214 | } 215 | 216 | //洗牌 217 | func (table *Table) ShufflePokers() { 218 | logs.Debug("ShufflePokers") 219 | r := rand.New(rand.NewSource(time.Now().Unix())) 220 | i := len(table.GameManage.Pokers) 221 | for i > 0 { 222 | randIndex := r.Intn(i) 223 | table.GameManage.Pokers[i-1], table.GameManage.Pokers[randIndex] = table.GameManage.Pokers[randIndex], table.GameManage.Pokers[i-1] 224 | i-- 225 | } 226 | } 227 | 228 | //同步用户信息 229 | func (table *Table) syncUser() () { 230 | logs.Debug("sync user") 231 | response := make([]interface{}, 0, 3) 232 | response = append(append(response, common.ResJoinTable), table.TableId) 233 | tableUsers := make([][2]interface{}, 0, 2) 234 | current := table.Creator 235 | for i := 0; i < len(table.TableClients); i++ { 236 | tableUsers = append(tableUsers, [2]interface{}{current.UserInfo.UserId, current.UserInfo.Username}) 237 | current = current.Next 238 | } 239 | response = append(response, tableUsers) 240 | for _, client := range table.TableClients { 241 | client.sendMsg(response) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /static/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/1.png -------------------------------------------------------------------------------- /static/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/2.png -------------------------------------------------------------------------------- /static/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/3.png -------------------------------------------------------------------------------- /static/audio/bg_game.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/audio/bg_game.ogg -------------------------------------------------------------------------------- /static/audio/bg_room.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/audio/bg_room.mp3 -------------------------------------------------------------------------------- /static/audio/deal.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/audio/deal.mp3 -------------------------------------------------------------------------------- /static/audio/end_lose.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/audio/end_lose.mp3 -------------------------------------------------------------------------------- /static/audio/end_win.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/audio/end_win.mp3 -------------------------------------------------------------------------------- /static/audio/f_score_0.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/audio/f_score_0.mp3 -------------------------------------------------------------------------------- /static/audio/f_score_1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/audio/f_score_1.mp3 -------------------------------------------------------------------------------- /static/audio/f_score_2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/audio/f_score_2.mp3 -------------------------------------------------------------------------------- /static/audio/f_score_3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/audio/f_score_3.mp3 -------------------------------------------------------------------------------- /static/audio/m_score_0.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/audio/m_score_0.mp3 -------------------------------------------------------------------------------- /static/audio/m_score_1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/audio/m_score_1.mp3 -------------------------------------------------------------------------------- /static/audio/m_score_2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/audio/m_score_2.mp3 -------------------------------------------------------------------------------- /static/audio/m_score_3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/audio/m_score_3.mp3 -------------------------------------------------------------------------------- /static/generator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Poker 6 | 7 |
8 | Code: Click card to get code. 9 |
10 | 11 |
12 |   13 |
14 | 15 | 16 | 71 | 72 | -------------------------------------------------------------------------------- /static/i/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/bg.png -------------------------------------------------------------------------------- /static/i/btn.json: -------------------------------------------------------------------------------- 1 | {"frames": [ 2 | 3 | { 4 | "filename": "alarm.png", 5 | "frame": {"x":251,"y":0,"w":84,"h":84}, 6 | "rotated": false, 7 | "trimmed": false, 8 | "spriteSourceSize": {"x":0,"y":0,"w":84,"h":84}, 9 | "sourceSize": {"w":84,"h":84} 10 | }, 11 | { 12 | "filename": "exit.png", 13 | "frame": {"x":128,"y":180,"w":160,"h":60}, 14 | "rotated": false, 15 | "trimmed": false, 16 | "spriteSourceSize": {"x":0,"y":0,"w":160,"h":60}, 17 | "sourceSize": {"w":160,"h":60} 18 | }, 19 | { 20 | "filename": "hint.png", 21 | "frame": {"x":0,"y":132,"w":128,"h":48}, 22 | "rotated": false, 23 | "trimmed": false, 24 | "spriteSourceSize": {"x":0,"y":0,"w":128,"h":48}, 25 | "sourceSize": {"w":128,"h":48} 26 | }, 27 | { 28 | "filename": "icon_default.png", 29 | "frame": {"x":335,"y":0,"w":84,"h":84}, 30 | "rotated": false, 31 | "trimmed": false, 32 | "spriteSourceSize": {"x":0,"y":0,"w":84,"h":84}, 33 | "sourceSize": {"w":84,"h":84} 34 | }, 35 | { 36 | "filename": "icon_farmer.png", 37 | "frame": {"x":167,"y":0,"w":84,"h":84}, 38 | "rotated": false, 39 | "trimmed": false, 40 | "spriteSourceSize": {"x":0,"y":0,"w":84,"h":84}, 41 | "sourceSize": {"w":84,"h":84} 42 | }, 43 | { 44 | "filename": "icon_landlord.png", 45 | "frame": {"x":83,"y":0,"w":84,"h":84}, 46 | "rotated": false, 47 | "trimmed": false, 48 | "spriteSourceSize": {"x":0,"y":0,"w":84,"h":84}, 49 | "sourceSize": {"w":84,"h":84} 50 | }, 51 | { 52 | "filename": "pass.png", 53 | "frame": {"x":256,"y":132,"w":128,"h":48}, 54 | "rotated": false, 55 | "trimmed": false, 56 | "spriteSourceSize": {"x":0,"y":0,"w":128,"h":48}, 57 | "sourceSize": {"w":128,"h":48} 58 | }, 59 | { 60 | "filename": "quick.png", 61 | "frame": {"x":160,"y":240,"w":160,"h":60}, 62 | "rotated": false, 63 | "trimmed": false, 64 | "spriteSourceSize": {"x":0,"y":0,"w":160,"h":60}, 65 | "sourceSize": {"w":160,"h":60} 66 | }, 67 | { 68 | "filename": "register.png", 69 | "frame": {"x":320,"y":240,"w":160,"h":60}, 70 | "rotated": false, 71 | "trimmed": false, 72 | "spriteSourceSize": {"x":0,"y":0,"w":160,"h":60}, 73 | "sourceSize": {"w":160,"h":60} 74 | }, 75 | { 76 | "filename": "score_0.png", 77 | "frame": {"x":0,"y":180,"w":128,"h":48}, 78 | "rotated": false, 79 | "trimmed": false, 80 | "spriteSourceSize": {"x":0,"y":0,"w":128,"h":48}, 81 | "sourceSize": {"w":128,"h":48} 82 | }, 83 | { 84 | "filename": "score_1.png", 85 | "frame": {"x":128,"y":132,"w":128,"h":48}, 86 | "rotated": false, 87 | "trimmed": false, 88 | "spriteSourceSize": {"x":0,"y":0,"w":128,"h":48}, 89 | "sourceSize": {"w":128,"h":48} 90 | }, 91 | { 92 | "filename": "score_2.png", 93 | "frame": {"x":0,"y":84,"w":128,"h":48}, 94 | "rotated": false, 95 | "trimmed": false, 96 | "spriteSourceSize": {"x":0,"y":0,"w":128,"h":48}, 97 | "sourceSize": {"w":128,"h":48} 98 | }, 99 | { 100 | "filename": "score_3.png", 101 | "frame": {"x":128,"y":84,"w":128,"h":48}, 102 | "rotated": false, 103 | "trimmed": false, 104 | "spriteSourceSize": {"x":0,"y":0,"w":128,"h":48}, 105 | "sourceSize": {"w":128,"h":48} 106 | }, 107 | { 108 | "filename": "setting.png", 109 | "frame": {"x":0,"y":240,"w":160,"h":60}, 110 | "rotated": false, 111 | "trimmed": false, 112 | "spriteSourceSize": {"x":0,"y":0,"w":160,"h":60}, 113 | "sourceSize": {"w":160,"h":60} 114 | }, 115 | { 116 | "filename": "shot.png", 117 | "frame": {"x":256,"y":84,"w":128,"h":48}, 118 | "rotated": false, 119 | "trimmed": false, 120 | "spriteSourceSize": {"x":0,"y":0,"w":128,"h":48}, 121 | "sourceSize": {"w":128,"h":48} 122 | }, 123 | { 124 | "filename": "start.png", 125 | "frame": {"x":288,"y":180,"w":160,"h":60}, 126 | "rotated": false, 127 | "trimmed": false, 128 | "spriteSourceSize": {"x":0,"y":0,"w":160,"h":60}, 129 | "sourceSize": {"w":160,"h":60} 130 | }, 131 | { 132 | "filename": "table.png", 133 | "frame": {"x":0,"y":0,"w":83,"h":64}, 134 | "rotated": false, 135 | "trimmed": false, 136 | "spriteSourceSize": {"x":0,"y":0,"w":83,"h":64}, 137 | "sourceSize": {"w":83,"h":64} 138 | }], 139 | "meta": { 140 | "app": "http://www.codeandweb.com/texturepacker", 141 | "version": "1.0", 142 | "image": "btn.png", 143 | "format": "RGBA8888", 144 | "size": {"w":480,"h":300}, 145 | "scale": "1", 146 | "smartupdate": "$TexturePacker:SmartUpdate:69ff5bba555b3f26130bf3e3671006cb:e8e25170cdaffd298411d399512029d0:382bbd42eff5a2b763aa915dc00d7597$" 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /static/i/btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn.png -------------------------------------------------------------------------------- /static/i/btn/alarm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/alarm.png -------------------------------------------------------------------------------- /static/i/btn/btn.tps: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | fileFormatVersion 5 | 4 6 | texturePackerVersion 7 | 4.4.0 8 | fileName 9 | /Users/nv/workspace/pycharm/doudizhu/src/static/i/btn/btn.tps 10 | autoSDSettings 11 | 12 | 13 | scale 14 | 1 15 | extension 16 | 17 | spriteFilter 18 | 19 | acceptFractionalValues 20 | 21 | maxTextureSize 22 | 23 | width 24 | -1 25 | height 26 | -1 27 | 28 | 29 | 30 | allowRotation 31 | 32 | shapeDebug 33 | 34 | dpi 35 | 72 36 | dataFormat 37 | phaser-json-array 38 | textureFileName 39 | 40 | flipPVR 41 | 42 | pvrCompressionQuality 43 | PVR_QUALITY_NORMAL 44 | atfCompressData 45 | 46 | mipMapMinSize 47 | 32768 48 | etc1CompressionQuality 49 | ETC1_QUALITY_LOW_PERCEPTUAL 50 | etc2CompressionQuality 51 | ETC2_QUALITY_LOW_PERCEPTUAL 52 | dxtCompressionMode 53 | DXT_PERCEPTUAL 54 | jxrColorFormat 55 | JXR_YUV444 56 | jxrTrimFlexBits 57 | 0 58 | jxrCompressionLevel 59 | 0 60 | ditherType 61 | NearestNeighbour 62 | backgroundColor 63 | 0 64 | libGdx 65 | 66 | filtering 67 | 68 | x 69 | Linear 70 | y 71 | Linear 72 | 73 | 74 | shapePadding 75 | 0 76 | jpgQuality 77 | 80 78 | pngOptimizationLevel 79 | 0 80 | webpQualityLevel 81 | 101 82 | textureSubPath 83 | 84 | atfFormats 85 | 86 | textureFormat 87 | png 88 | borderPadding 89 | 0 90 | maxTextureSize 91 | 92 | width 93 | 2048 94 | height 95 | 2048 96 | 97 | fixedTextureSize 98 | 99 | width 100 | -1 101 | height 102 | -1 103 | 104 | algorithmSettings 105 | 106 | algorithm 107 | Basic 108 | freeSizeMode 109 | Best 110 | sizeConstraints 111 | AnySize 112 | forceSquared 113 | 114 | maxRects 115 | 116 | heuristic 117 | Best 118 | 119 | basic 120 | 121 | sortBy 122 | Best 123 | order 124 | Ascending 125 | 126 | polygon 127 | 128 | alignToGrid 129 | 1 130 | 131 | 132 | andEngine 133 | 134 | minFilter 135 | Linear 136 | packageName 137 | Texture 138 | wrap 139 | 140 | s 141 | Clamp 142 | t 143 | Clamp 144 | 145 | magFilter 146 | MagLinear 147 | 148 | dataFileNames 149 | 150 | data 151 | 152 | name 153 | ../btn.json 154 | 155 | 156 | multiPack 157 | 158 | forceIdenticalLayout 159 | 160 | outputFormat 161 | RGBA8888 162 | alphaHandling 163 | ClearTransparentPixels 164 | contentProtection 165 | 166 | key 167 | 168 | 169 | autoAliasEnabled 170 | 171 | trimSpriteNames 172 | 173 | prependSmartFolderName 174 | 175 | autodetectAnimations 176 | 177 | globalSpriteSettings 178 | 179 | scale 180 | 1 181 | scaleMode 182 | Smooth 183 | extrude 184 | 0 185 | trimThreshold 186 | 1 187 | trimMargin 188 | 1 189 | trimMode 190 | None 191 | tracerTolerance 192 | 200 193 | heuristicMask 194 | 195 | defaultPivotPoint 196 | 0.5,0.5 197 | writePivotPoints 198 | 199 | 200 | individualSpriteSettings 201 | 202 | alarm.png 203 | icon_default.png 204 | icon_farmer.png 205 | icon_landlord.png 206 | 207 | pivotPoint 208 | 0.5,0.5 209 | scale9Enabled 210 | 211 | scale9Borders 212 | 21,21,42,42 213 | scale9Paddings 214 | 21,21,42,42 215 | scale9FromFile 216 | 217 | 218 | exit.png 219 | hint.png 220 | pass.png 221 | quick.png 222 | score_0.png 223 | score_1.png 224 | score_2.png 225 | score_3.png 226 | setting.png 227 | shot.png 228 | start.png 229 | 230 | pivotPoint 231 | 0.5,0.5 232 | scale9Enabled 233 | 234 | scale9Borders 235 | 50,19,100,38 236 | scale9Paddings 237 | 50,19,100,38 238 | scale9FromFile 239 | 240 | 241 | register.png 242 | 243 | pivotPoint 244 | 0.5,0.5 245 | scale9Enabled 246 | 247 | scale9Borders 248 | 40,15,80,30 249 | scale9Paddings 250 | 40,15,80,30 251 | scale9FromFile 252 | 253 | 254 | table.png 255 | 256 | pivotPoint 257 | 0.5,0.5 258 | scale9Enabled 259 | 260 | scale9Borders 261 | 21,16,42,32 262 | scale9Paddings 263 | 21,16,42,32 264 | scale9FromFile 265 | 266 | 267 | 268 | fileList 269 | 270 | . 271 | 272 | ignoreFileList 273 | 274 | replaceList 275 | 276 | ignoredWarnings 277 | 278 | commonDivisorX 279 | 1 280 | commonDivisorY 281 | 1 282 | packNormalMaps 283 | 284 | autodetectNormalMaps 285 | 286 | normalMapFilter 287 | 288 | normalMapSuffix 289 | 290 | normalMapSheetFileName 291 | 292 | exporterProperties 293 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /static/i/btn/exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/exit.png -------------------------------------------------------------------------------- /static/i/btn/hint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/hint.png -------------------------------------------------------------------------------- /static/i/btn/icon_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/icon_default.png -------------------------------------------------------------------------------- /static/i/btn/icon_farmer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/icon_farmer.png -------------------------------------------------------------------------------- /static/i/btn/icon_landlord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/icon_landlord.png -------------------------------------------------------------------------------- /static/i/btn/pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/pass.png -------------------------------------------------------------------------------- /static/i/btn/quick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/quick.png -------------------------------------------------------------------------------- /static/i/btn/register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/register.png -------------------------------------------------------------------------------- /static/i/btn/score_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/score_0.png -------------------------------------------------------------------------------- /static/i/btn/score_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/score_1.png -------------------------------------------------------------------------------- /static/i/btn/score_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/score_2.png -------------------------------------------------------------------------------- /static/i/btn/score_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/score_3.png -------------------------------------------------------------------------------- /static/i/btn/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/setting.png -------------------------------------------------------------------------------- /static/i/btn/shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/shot.png -------------------------------------------------------------------------------- /static/i/btn/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/start.png -------------------------------------------------------------------------------- /static/i/btn/table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/btn/table.png -------------------------------------------------------------------------------- /static/i/poker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/poker.png -------------------------------------------------------------------------------- /static/i/preload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/i/preload.png -------------------------------------------------------------------------------- /static/js/boot.js: -------------------------------------------------------------------------------- 1 | PG = { 2 | music: null, 3 | playerInfo: {}, 4 | orientated: false 5 | }; 6 | 7 | PG.getCookie = function (name) { 8 | var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); 9 | return r ? r[1] : undefined; 10 | }; 11 | 12 | PG.PW = 90; 13 | PG.PH = 120; 14 | 15 | PG.Boot = { 16 | preload: function () { 17 | this.load.image('preloaderBar', 'static/i/preload.png'); 18 | }, 19 | create: function () { 20 | this.input.maxPointers = 1; 21 | this.stage.disableVisibilityChange = true; 22 | this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL; 23 | this.scale.enterIncorrectOrientation.add(this.enterIncorrectOrientation, this); 24 | this.scale.leaveIncorrectOrientation.add(this.leaveIncorrectOrientation, this); 25 | this.onSizeChange(); 26 | this.state.start('Preloader'); 27 | }, 28 | onSizeChange: function () { 29 | this.scale.minWidth = 480; 30 | this.scale.minHeight = 270; 31 | var device = this.game.device; 32 | if (device.android || device.iOS) { 33 | this.scale.maxWidth = window.innerWidth; 34 | this.scale.maxHeight = window.innerHeight; 35 | } else { 36 | this.scale.maxWidth = 960; 37 | this.scale.maxHeight = 540; 38 | } 39 | this.scale.pageAlignHorizontally = true; 40 | this.scale.pageAlignVertically = true; 41 | this.scale.forceOrientation(true); 42 | }, 43 | enterIncorrectOrientation: function () { 44 | PG.orientated = false; 45 | document.getElementById('orientation').style.display = 'block'; 46 | }, 47 | leaveIncorrectOrientation: function () { 48 | PG.orientated = true; 49 | document.getElementById('orientation').style.display = 'none'; 50 | } 51 | }; 52 | 53 | PG.Preloader = { 54 | 55 | preload: function () { 56 | this.preloadBar = this.game.add.sprite(120, 200, 'preloaderBar'); 57 | this.load.setPreloadSprite(this.preloadBar); 58 | 59 | this.load.audio('music_room', 'static/audio/bg_room.mp3'); 60 | this.load.audio('music_game', 'static/audio/bg_game.ogg'); 61 | this.load.audio('music_deal', 'static/audio/deal.mp3'); 62 | this.load.audio('music_win', 'static/audio/end_win.mp3'); 63 | this.load.audio('music_lose', 'static/audio/end_lose.mp3'); 64 | this.load.audio('f_score_0', 'static/audio/f_score_0.mp3'); 65 | this.load.audio('f_score_1', 'static/audio/f_score_1.mp3'); 66 | this.load.audio('f_score_2', 'static/audio/f_score_2.mp3'); 67 | this.load.audio('f_score_3', 'static/audio/f_score_3.mp3'); 68 | this.load.atlas('btn', 'static/i/btn.png', 'static/i/btn.json'); 69 | this.load.image('bg', 'static/i/bg.png'); 70 | this.load.spritesheet('poker', 'static/i/poker.png', 90, 120); 71 | this.load.json('rule', 'static/rule.json'); 72 | }, 73 | 74 | create: function () { 75 | PG.RuleList = this.cache.getJSON('rule'); 76 | var jsonVal = document.getElementById("user").value; 77 | if (jsonVal) { 78 | PG.playerInfo = JSON.parse(jsonVal); 79 | if (PG.playerInfo['uid']) { 80 | this.state.start('MainMenu'); 81 | } else { 82 | this.state.start('Login'); 83 | } 84 | } else { 85 | this.state.start('Login'); 86 | } 87 | PG.music = this.game.add.audio('music_room'); 88 | PG.music.loop = true; 89 | PG.music.loopFull(); 90 | PG.music.play(); 91 | } 92 | }; 93 | 94 | PG.MainMenu = { 95 | create: function () { 96 | this.stage.backgroundColor = '#182d3b'; 97 | var bg = this.game.add.sprite(this.game.width / 2, 0, 'bg'); 98 | bg.anchor.set(0.5, 0); 99 | 100 | var aiRoom = this.game.add.button(this.game.world.width / 2, this.game.world.height / 4, 'btn', this.gotoAiRoom, this, 'quick.png', 'quick.png', 'quick.png'); 101 | aiRoom.anchor.set(0.5); 102 | this.game.world.add(aiRoom); 103 | 104 | var humanRoom = this.game.add.button(this.game.world.width / 2, this.game.world.height / 2, 'btn', this.gotoRoom, this, 'start.png', 'start.png', 'start.png'); 105 | humanRoom.anchor.set(0.5); 106 | this.game.world.add(humanRoom); 107 | 108 | var setting = this.game.add.button(this.game.world.width / 2, this.game.world.height * 3 / 4, 'btn', this.gotoSetting, this, 'setting.png', 'setting.png', 'setting.png'); 109 | setting.anchor.set(0.5); 110 | this.game.world.add(setting); 111 | 112 | var style = {font: "28px Arial", fill: "#fff", align: "right"}; 113 | var text = this.game.add.text(this.game.world.width - 4, 4, "欢迎回来 " + PG.playerInfo.username, style); 114 | text.addColor('#cc00cc', 4); 115 | text.anchor.set(1, 0); 116 | }, 117 | 118 | gotoAiRoom: function () { 119 | // start(key, clearWorld, clearCache, parameter) 120 | this.state.start('Game', true, false, 1); 121 | // this.music.stop(); 122 | }, 123 | 124 | gotoRoom: function () { 125 | this.state.start('Game', true, false, 2); 126 | }, 127 | 128 | gotoSetting: function () { 129 | var style = {font: "22px Arial", fill: "#fff", align: "center"}; 130 | var text = this.game.add.text(0, 0, "hei hei hei hei", style); 131 | var tween = this.game.add.tween(text).to({x: 600, y: 450}, 2000, "Linear", true); 132 | tween.onComplete.add(Phaser.Text.prototype.destroy, text); 133 | } 134 | }; 135 | 136 | PG.Login = { 137 | create: function () { 138 | this.stage.backgroundColor = '#182d3b'; 139 | var bg = this.game.add.sprite(this.game.width / 2, 0, 'bg'); 140 | bg.anchor.set(0.5, 0); 141 | 142 | var style = { 143 | font: '24px Arial', fill: '#000', width: 300, padding: 12, 144 | borderWidth: 1, borderColor: '#c8c8c8', borderRadius: 2, 145 | textAlign: 'center', placeHolder: '姓名' 146 | // type: PhaserInput.InputType.password 147 | }; 148 | this.game.add.plugin(PhaserInput.Plugin); 149 | 150 | this.username = this.game.add.inputField((this.game.world.width - 300) / 2, this.game.world.centerY - 160, style); 151 | 152 | style.placeHolder = '密码'; 153 | this.password = this.game.add.inputField((this.game.world.width - 300) / 2, this.game.world.centerY - 90, style); 154 | 155 | style.placeHolder = '再次输入密码'; 156 | this.passwordAgain = this.game.add.inputField((this.game.world.width - 300) / 2, this.game.world.centerY - 15, style); 157 | 158 | var style = {font: "22px Arial", fill: "#f00", align: "center"}; 159 | this.errorText = this.game.add.text(this.game.world.centerX, this.game.world.centerY + 45, '', style); 160 | this.errorText.anchor.set(0.5, 0); 161 | 162 | var login = this.game.add.button(this.game.world.centerX, this.game.world.centerY + 100, 'btn', this.onLogin, this, 'register.png', 'register.png', 'register.png'); 163 | login.anchor.set(0.5); 164 | }, 165 | 166 | onLogin: function () { 167 | if (!this.username.value) { 168 | this.username.startFocus(); 169 | this.errorText.text = '请输入用户名'; 170 | return; 171 | } 172 | if (!this.password.value) { 173 | this.password.startFocus(); 174 | this.errorText.text = '请输入密码'; 175 | return; 176 | } 177 | if (!this.passwordAgain.value) { 178 | this.passwordAgain.startFocus(); 179 | this.errorText.text = '请再次输入密码'; 180 | return; 181 | } 182 | if (this.password.value != this.passwordAgain.value) { 183 | this.errorText.text = "两次输入的密码不一致"; 184 | return; 185 | } 186 | 187 | var httpRequest = new XMLHttpRequest(); 188 | var that = this; 189 | httpRequest.onreadystatechange = function () { 190 | if (httpRequest.readyState === XMLHttpRequest.DONE) { 191 | if (httpRequest.status === 200) { 192 | if (httpRequest.responseText == '1') { 193 | that.errorText.text = '该用户名已经被占用'; 194 | } else { 195 | PG.playerInfo = JSON.parse(httpRequest.responseText); 196 | that.state.start('MainMenu'); 197 | } 198 | } else { 199 | console.log('Error:' + httpRequest.status); 200 | that.errorText.text = httpRequest.responseText; 201 | } 202 | } 203 | }; 204 | httpRequest.open('POST', '/reg', true); 205 | httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 206 | httpRequest.setRequestHeader('X-Csrftoken', PG.getCookie("_xsrf")); 207 | 208 | var req = 'username=' + encodeURIComponent(this.username.value) + '&password=' + encodeURIComponent(this.password.value); 209 | httpRequest.send(req); 210 | } 211 | }; 212 | -------------------------------------------------------------------------------- /static/js/game.js: -------------------------------------------------------------------------------- 1 | 2 | PG.Game = function(game) { 3 | 4 | this.roomId = 1; 5 | this.players = []; 6 | 7 | this.titleBar = null; 8 | this.tableId = 0; 9 | this.shotLayer = null; 10 | 11 | this.tablePoker = []; 12 | this.tablePokerPic = {}; 13 | 14 | this.lastShotPlayer = null; 15 | 16 | this.whoseTurn = 0; 17 | 18 | }; 19 | 20 | PG.Game.prototype = { 21 | 22 | init: function(roomId) { 23 | this.roomId = roomId; 24 | }, 25 | 26 | debug_log(obj) { 27 | console.log('*******'); 28 | console.log(obj); 29 | console.log('********'); 30 | }, 31 | 32 | create: function () { 33 | this.stage.backgroundColor = '#182d3b'; 34 | 35 | this.players.push(PG.createPlay(0, this)); 36 | this.players.push(PG.createPlay(1, this)); 37 | this.players.push(PG.createPlay(2, this)); 38 | this.players[0].updateInfo(PG.playerInfo.uid, PG.playerInfo.username); 39 | PG.Socket.connect(this.onopen.bind(this), this.onmessage.bind(this), this.onerror.bind(this)); 40 | 41 | this.createTitleBar(); 42 | }, 43 | 44 | onopen: function() { 45 | console.log('socket onopen'); 46 | PG.Socket.send([PG.Protocol.REQ_JOIN_ROOM, this.roomId]); 47 | }, 48 | 49 | onerror: function() { 50 | console.log('socket connect onerror'); 51 | }, 52 | 53 | send_message: function(request) { 54 | PG.Socket.send(request); 55 | }, 56 | 57 | onmessage: function(packet) { 58 | var opcode = packet[0]; 59 | switch(opcode) { 60 | case PG.Protocol.RSP_JOIN_ROOM: 61 | if (this.roomId == 1) { 62 | PG.Socket.send([PG.Protocol.REQ_JOIN_TABLE, -1]); 63 | } else { 64 | this.createTableLayer(packet[1]); 65 | } 66 | break; 67 | case PG.Protocol.RSP_TABLE_LIST: 68 | this.createTableLayer(packet[1]); 69 | break; 70 | case PG.Protocol.RSP_NEW_TABLE: 71 | this.tableId = packet[1]; 72 | this.titleBar.text = '房间:' + this.tableId; 73 | break; 74 | case PG.Protocol.RSP_JOIN_TABLE: 75 | this.tableId = packet[1]; 76 | this.titleBar.text = '房间:' + this.tableId; 77 | var playerIds = packet[2]; 78 | for (var i = 0; i < playerIds.length; i++) { 79 | if (playerIds[i][0] == this.players[0].uid) { 80 | var info_1 = playerIds[(i+1)%3]; 81 | var info_2 = playerIds[(i+2)%3]; 82 | this.players[1].updateInfo(info_1[0], info_1[1]); 83 | this.players[2].updateInfo(info_2[0], info_2[1]); 84 | break; 85 | } 86 | } 87 | break; 88 | case PG.Protocol.RSP_DEAL_POKER: 89 | var playerId = packet[1]; 90 | var pokers = packet[2]; 91 | console.log(pokers); 92 | this.dealPoker(pokers); 93 | this.whoseTurn = this.uidToSeat(playerId); 94 | this.startCallScore(0); 95 | break; 96 | case PG.Protocol.RSP_CALL_SCORE: 97 | var playerId = packet[1]; 98 | var score = packet[2]; 99 | var callend = packet[3]; 100 | this.debug_log(callend); 101 | this.whoseTurn = this.uidToSeat(playerId); 102 | //this.debug_log(playerId); 103 | 104 | var hanzi = ['不叫', "一分", "两分", "三分"]; 105 | this.players[this.whoseTurn].say(hanzi[score]); 106 | if (!callend) { 107 | this.whoseTurn = (this.whoseTurn + 1) % 3; 108 | this.startCallScore(score); 109 | } 110 | break; 111 | case PG.Protocol.RSP_SHOW_POKER: 112 | this.whoseTurn = this.uidToSeat(packet[1]); 113 | this.tablePoker[0] = packet[2][0]; 114 | this.tablePoker[1] = packet[2][1]; 115 | this.tablePoker[2] = packet[2][2]; 116 | this.players[this.whoseTurn].setLandlord(); 117 | this.showLastThreePoker(); 118 | break; 119 | case PG.Protocol.RSP_SHOT_POKER: 120 | this.handleShotPoker(packet); 121 | break; 122 | case PG.Protocol.RSP_GAME_OVER: 123 | var winner = packet[1]; 124 | var coin = packet[2]; 125 | 126 | var loserASeat = this.uidToSeat(packet[3][0]); 127 | this.players[loserASeat].replacePoker(packet[3], 1); 128 | this.players[loserASeat].reDealPoker(); 129 | 130 | var loserBSeat = this.uidToSeat(packet[4][0]); 131 | this.players[loserBSeat].replacePoker(packet[4], 1); 132 | this.players[loserBSeat].reDealPoker(); 133 | // this.players[loserBSeat].removeAllPoker(); 134 | // this.players[loserASeat].pokerInHand = []; 135 | 136 | this.whoseTurn = this.uidToSeat(winner); 137 | 138 | function gameOver() { 139 | alert(this.players[this.whoseTurn].isLandlord ? "地主赢" : "农民赢"); 140 | PG.Socket.send([PG.Protocol.REQ_RESTART]); 141 | this.cleanWorld(); 142 | } 143 | this.game.time.events.add(3000, gameOver, this); 144 | break; 145 | case PG.Protocol.RSP_CHEAT: 146 | var seat = this.uidToSeat(packet[1]); 147 | this.players[seat].replacePoker(packet[2], 0); 148 | this.players[seat].reDealPoker(); 149 | break; 150 | case PG.Protocol.RSP_RESTART: 151 | this.restart(); 152 | default: 153 | console.log("UNKNOWN PACKET:", packet) 154 | } 155 | }, 156 | 157 | cleanWorld: function () { 158 | for (i =0; i < 3; i ++) { 159 | this.players[i].cleanPokers(); 160 | try { 161 | this.players[i].uiLeftPoker.kill(); 162 | } 163 | catch (err) { 164 | } 165 | this.players[i].uiHead.frameName = 'icon_farmer.png'; 166 | } 167 | 168 | for (var i = 0; i < this.tablePoker.length; i++) { 169 | var p = this.tablePokerPic[this.tablePoker[i]]; 170 | // p.kill(); 171 | p.destroy(); 172 | } 173 | }, 174 | 175 | restart: function () { 176 | this.players = []; 177 | this.shotLayer = null; 178 | 179 | this.tablePoker = []; 180 | this.tablePokerPic = {}; 181 | 182 | this.lastShotPlayer = null; 183 | 184 | this.whoseTurn = 0; 185 | 186 | this.stage.backgroundColor = '#182d3b'; 187 | this.players.push(PG.createPlay(0, this)); 188 | this.players.push(PG.createPlay(1, this)); 189 | this.players.push(PG.createPlay(2, this)); 190 | player_id = [1, 11, 12]; 191 | for (var i = 0; i < 3; i++) { 192 | //this.players[i].uiHead.kill(); 193 | this.players[i].updateInfo(player_id[i], ' '); 194 | } 195 | 196 | // this.send_message([PG.Protocol.REQ_DEAL_POKEER, -1]); 197 | // PG.Socket.send([PG.Protocol.REQ_JOIN_TABLE, this.tableId]); 198 | }, 199 | 200 | update: function () { 201 | }, 202 | 203 | uidToSeat: function (uid) { 204 | for (var i = 0; i < 3; i++) { 205 | // this.debug_log(this.players[i].uid); 206 | if (uid == this.players[i].uid) 207 | return i; 208 | } 209 | console.log('ERROR uidToSeat:' + uid); 210 | return -1; 211 | }, 212 | 213 | dealPoker: function(pokers) { 214 | 215 | for (var i = 0; i < 3; i++) { 216 | var p = new PG.Poker(this, 54, 54); 217 | this.game.world.add(p); 218 | this.tablePoker[i] = p.id; 219 | this.tablePoker[i + 3] = p; 220 | } 221 | 222 | for (var i = 0; i < 17; i++) { 223 | this.players[2].pokerInHand.push(54); 224 | this.players[1].pokerInHand.push(54); 225 | this.players[0].pokerInHand.push(pokers.pop()); 226 | } 227 | 228 | this.players[0].dealPoker(); 229 | this.players[1].dealPoker(); 230 | this.players[2].dealPoker(); 231 | //this.game.time.events.add(1000, function() { 232 | // this.send_message([PG.Protocol.REQ_CHEAT, this.players[1].uid]); 233 | // this.send_message([PG.Protocol.REQ_CHEAT, this.players[2].uid]); 234 | //}, this); 235 | }, 236 | 237 | showLastThreePoker: function() { 238 | for (var i = 0; i < 3; i++) { 239 | var pokerId = this.tablePoker[i]; 240 | var p = this.tablePoker[i + 3]; 241 | p.id = pokerId; 242 | p.frame = pokerId; 243 | this.game.add.tween(p).to({ x: this.game.world.width/2 + (i - 1) * 60}, 600, Phaser.Easing.Default, true); 244 | } 245 | this.game.time.events.add(1500, this.dealLastThreePoker, this); 246 | }, 247 | 248 | dealLastThreePoker: function() { 249 | var turnPlayer = this.players[this.whoseTurn]; 250 | 251 | for (var i = 0; i < 3; i++) { 252 | var pid = this.tablePoker[i]; 253 | var poker = this.tablePoker[i + 3]; 254 | turnPlayer.pokerInHand.push(pid); 255 | turnPlayer.pushAPoker(poker); 256 | } 257 | turnPlayer.sortPoker(); 258 | if (this.whoseTurn == 0) { 259 | turnPlayer.arrangePoker(); 260 | for (var i = 0; i < 3; i++) { 261 | var p = this.tablePoker[i + 3]; 262 | var tween = this.game.add.tween(p).to({y: this.game.world.height - PG.PH * 0.8 }, 400, Phaser.Easing.Default, true); 263 | function adjust(p) { 264 | this.game.add.tween(p).to({y: this.game.world.height - PG.PH /2}, 400, Phaser.Easing.Default, true, 400); 265 | }; 266 | tween.onComplete.add(adjust, this, p); 267 | } 268 | } else { 269 | var first = turnPlayer.findAPoker(54); 270 | for (var i = 0; i < 3; i++) { 271 | var p = this.tablePoker[i + 3]; 272 | p.frame = 54; 273 | p.frame = 54; 274 | this.game.add.tween(p).to({ x: first.x, y: first.y}, 200, Phaser.Easing.Default, true); 275 | } 276 | } 277 | 278 | this.tablePoker = []; 279 | this.lastShotPlayer = turnPlayer; 280 | if (this.whoseTurn == 0) { 281 | this.startPlay(); 282 | } 283 | }, 284 | 285 | handleShotPoker: function(packet) { 286 | this.whoseTurn = this.uidToSeat(packet[1]); 287 | var turnPlayer = this.players[this.whoseTurn]; 288 | var pokers = packet[2]; 289 | if (pokers.length == 0) { 290 | this.players[this.whoseTurn].say("不出"); 291 | } else { 292 | var pokersPic = {}; 293 | pokers.sort(PG.Poker.comparePoker); 294 | var count= pokers.length; 295 | var gap = Math.min((this.game.world.width - PG.PW * 2) / count, PG.PW * 0.36); 296 | for (var i = 0; i < count; i++) { 297 | var p = turnPlayer.findAPoker(pokers[i]); 298 | p.id = pokers[i]; 299 | p.frame = pokers[i]; 300 | p.bringToTop(); 301 | this.game.add.tween(p).to({ x: this.game.world.width/2 + (i - count/2) * gap, y: this.game.world.height * 0.4}, 500, Phaser.Easing.Default, true); 302 | 303 | turnPlayer.removeAPoker(pokers[i]); 304 | pokersPic[p.id] = p; 305 | } 306 | 307 | for (var i = 0; i < this.tablePoker.length; i++) { 308 | var p = this.tablePokerPic[this.tablePoker[i]]; 309 | // p.kill(); 310 | p.destroy(); 311 | } 312 | this.tablePoker = pokers; 313 | this.tablePokerPic = pokersPic; 314 | this.lastShotPlayer = turnPlayer; 315 | turnPlayer.arrangePoker(); 316 | } 317 | if (turnPlayer.pokerInHand.length > 0) { 318 | this.whoseTurn = (this.whoseTurn + 1) % 3; 319 | if (this.whoseTurn == 0) { 320 | this.game.time.events.add(1000, this.startPlay, this); 321 | } 322 | } 323 | }, 324 | 325 | startCallScore: function(minscore) { 326 | function btnTouch(btn) { 327 | this.send_message([PG.Protocol.REQ_CALL_SCORE, btn.score]); 328 | btn.parent.destroy(); 329 | var audio = this.game.add.audio('f_score_' + btn.score); 330 | audio.play(); 331 | }; 332 | 333 | if (this.whoseTurn == 0) { 334 | var step = this.game.world.width/6; 335 | var ss = [1.5, 1, 0.5, 0]; 336 | var sx = this.game.world.width/2 - step * ss[minscore]; 337 | var sy = this.game.world.height * 0.6; 338 | var group = this.game.add.group(); 339 | var pass = this.game.make.button(sx, sy, "btn", btnTouch, this, 'score_0.png', 'score_0.png', 'score_0.png'); 340 | pass.anchor.set(0.5, 0); 341 | pass.score = 0; 342 | group.add(pass); 343 | sx += step; 344 | 345 | for (var i = minscore + 1; i <= 3; i++) { 346 | var tn = 'score_' + i + '.png'; 347 | var call = this.game.make.button(sx, sy, "btn", btnTouch, this, tn, tn, tn); 348 | call.anchor.set(0.5, 0); 349 | call.score = i; 350 | group.add(call); 351 | sx += step; 352 | } 353 | } else { 354 | // TODO show clock on player 355 | } 356 | 357 | }, 358 | 359 | startPlay: function() { 360 | if (this.isLastShotPlayer()) { 361 | this.players[0].playPoker([]); 362 | } else { 363 | this.players[0].playPoker(this.tablePoker); 364 | } 365 | }, 366 | 367 | finishPlay: function(pokers) { 368 | this.send_message([PG.Protocol.REQ_SHOT_POKER, pokers]); 369 | }, 370 | 371 | isLastShotPlayer: function() { 372 | return this.players[this.whoseTurn] == this.lastShotPlayer; 373 | }, 374 | 375 | createTableLayer: function (tables) { 376 | tables.push([-1, 0]); 377 | 378 | var group = this.game.add.group(); 379 | this.game.world.bringToTop(group); 380 | var gc = this.game.make.graphics(0, 0); 381 | gc.beginFill(0x00000080); 382 | gc.endFill(); 383 | group.add(gc); 384 | var style = {font: "22px Arial", fill: "#fff", align: "center"}; 385 | 386 | for (var i = 0; i < tables.length; i++) { 387 | var sx = this.game.world.width * (i%6 + 1)/(6 + 1); 388 | var sy = this.game.world.height * (Math.floor(i/6) + 1)/(4 + 1); 389 | 390 | var table = this.game.make.button(sx, sy, 'btn', this.onJoin, this, 'table.png', 'table.png', 'table.png'); 391 | table.anchor.set(0.5, 1); 392 | table.tableId = tables[i][0]; 393 | group.add(table); 394 | 395 | var text = this.game.make.text(sx, sy, '房间:' + tables[i][0] + '人数:' + tables[i][1], style); 396 | text.anchor.set(0.5, 0); 397 | group.add(text); 398 | 399 | if (i == tables.length - 1) { 400 | text.text = '新建房间'; 401 | } 402 | } 403 | }, 404 | 405 | quitGame: function () { 406 | this.state.start('MainMenu'); 407 | }, 408 | 409 | createTitleBar: function() { 410 | var style = {font: "22px Arial", fill: "#fff", align: "center"}; 411 | this.titleBar = this.game.add.text(this.game.world.centerX, 0, '房间:', style); 412 | }, 413 | 414 | onJoin: function (btn) { 415 | if (btn.tableId == -1) { 416 | this.send_message([PG.Protocol.REQ_NEW_TABLE]); 417 | } else { 418 | this.send_message([PG.Protocol.REQ_JOIN_TABLE, btn.tableId]); 419 | } 420 | btn.parent.destroy(); 421 | } 422 | }; 423 | 424 | 425 | 426 | 427 | 428 | 429 | -------------------------------------------------------------------------------- /static/js/generator.js: -------------------------------------------------------------------------------- 1 | var symbolPath = { 2 | 'a': 'M6,200V183H23L58,0H78L117,183H131V200H85V183H97L92,156H46L42,183H54V200H6zM88,135L68,37L49,135H88z', 3 | '2': 'M10,200L11,187C15,149,23,136,70,97C93,78,100,68,101,57C104,31,81,23,65,23C46,22,23,34,35,62L12,68C8,43,12,18,33,8C61,-6,96,-1,115,21C127,36,129,56,123,72C104,113,39,131,35,179H105V152H127V200L10,200z', 4 | '3': 'M2,156L18,145C31,167,47,181,70,178C104,176,119,140,112,113C105,89,76,77,53,90C47,93,43,96,41,96C39,96,33,85,34,82C50,59,87,21,87,21H28V47H6V0H120V16C120,16,90,48,80,64C104,65,125,81,132,105C136,118,135,148,129,160C119,182,94,199,71,200C33,202,12,176,2,156L2,156z', 5 | '4': 'M70,200L70,183L86,183L86,153L5,153L5,133L93,0L107,0L107,133L132,133L132,153L107,153L107,183L120,183L120,200zM86,49L30,133L86,133z', 6 | '5': 'M4,148L24,148C28,160,37,173,48,176C80,183,101,166,108,144C116,120,107,84,85,71C67,61,40,70,27,92L13,83L20,0H112V20H37L37,55C52,44,77,44,93,52C123,66,137,98,131,137C123,175,105,197,64,200C20,201,4,170,4,148L4,148z', 7 | '6': 'M8,139C6,122,6,78,8,65C15,26,30,7,55,2C81,-4,116,3,124,35L103,36C91,14,60,15,46,29C34,37,28,68,30,70C30,70,50,55,73,55C120,55,132,94,130,127C129,167,116,198,73,200C31,198,12,177,8,139zM110,128C111,101,98,80,73,77C50,76,26,99,27,127C29,155,40,179,69,179C101,179,110,147,110,128z', 8 | '7': 'M37,200C50,131,65,79,102,22H26V46H6V0H117L131,22C91,64,54,202,61,200H37z', 9 | '8': 'M2,142C3,115,13,105,32,90C17,79,10,63,12,50C15,17,41,0,69,0C98,1,123,24,125,48C127,69,120,79,105,90C123,105,135,115,135,141C134,168,111,199,71,200C31,201,1,168,2,142L2,142zM113,142C115,117,93,101,69,101C45,101,23,121,23,143C23,166,51,178,69,178C91,178,112,163,113,142L113,142zM105,55C106,34,87,20,67,21C50,21,31,34,31,51C31,72,52,83,70,83C86,84,105,71,105,55L105,55z', 10 | '9': 'MM11,161L30,156C37,174,52,180,67,178C94,176,102,146,104,120C94,131,78,137,64,136C21,134,10,100,10,65C9,35,21,13,43,3C55,-1,81,-1,92,4C118,18,128,42,126,98C126,144,117,198,66,200C36,204,14,181,11,161L11,161zM85,111C94,105,98,100,102,92C106,86,106,83,106,69C103,36,86,17,60,21C44,23,36,31,33,46C24,73,35,105,55,112C63,116,78,115,85,111L85,111z', 11 | //10 12 | '0': 'M6,200V0H26V200H6M85,0C66,0,50,17,50,39V162C50,183,66,200,85,200H96C115,200,130,183,130,162V39C130,17,115,0,96,0H85M90,19C102,19,110,28,110,38V163C110,174,102,183,90,183C79,183,70,174,70,163V38C70,28,79,19,90,19L90,19z', 13 | 'j': 'M68,0V21H88C88,21,89,41,89,84C89,126,90,146,88,158C81,185,40,185,32,166C27,155,28,146,28,134H6C6,134,6,140,6,147C6,178,17,193,41,198C65,204,95,194,105,174C111,162,111,161,111,89C111,41,111,21,111,21H130V0H68z', 14 | 'q': 'M24,134L6,134L6,112L24,112C24,112,24,60,24,40C24,18,40,0,66,0C92,0,110,18,110,40C110,62,111,148,110,155C110,168,108,170,108,171C110,176,130,178,130,177L130,199C115,201,109,199,96,190C88,198,65,205,46,196C32,190,24,174,24,134zM81,174C73,162,58,145,44,140C44,156,46,165,51,171C59,181,71,183,81,174zM66,22C50,22,44,30,44,70C44,94,44,116,44,116C67,123,90,150,90,150L90,70C90,30,82,22,66,22z', 15 | 'k': 'M76,180L96,180L64,106L40,142L40,180L56,180L56,200L0,200L0,180L20,180L20,20L0,20L0,0L56,0L56,20L40,20L40,100L92.0636,19.841L76,20L76,0L136,0L136,20L120,20L76,88L116,180L136,180L136,200L76,200z', 16 | //jOker,jo 17 | 'o': 'M141,0L181,0C168,55,161,150,129,183C91,219,15,198,21,141L60,137C58,157,62,166,81,166C102,165,110,143,115,118M6,378C6,306,53,256,119,256C197,256,213,346,187,398C164,438,130,458,88,459C39,458,7,422,6,378M47,377C49,406,67,425,93,425C168,423,182,292,115,290C69,294,47,338,47,377M0,714L42,518L84,518L66,601L159,518L215,518L124,595L191,714L144,714L94,621L55,654L42,714M8,973L50,777L200,777L193,809L85,809L75,854L180,854L173,887L68,887L56,940L173,940L166,973M43,1231L1,1231L44,1035L133,1035C170,1037,197,1051,198,1087C195,1127,169,1143,136,1148C158,1171,171,1206,182,1231L137,1231C116,1182,112,1150,60,1150M67,1121C96,1121,155,1126,156,1087C151,1061,100,1068,78,1068z', 18 | //Hearts 19 | 'h': 'M100,30C60,7,0,7,0,76C0,131,100,190,100,190C100,190,200,131,200,76C200,7,140,7,100,30z', 20 | //Diamonds 21 | 'd': 'M184,100C152,120,120,160,100,200C80,160,48,120,16,100C48,80,80,40,100,0C120,40,152,80,184,100z', 22 | //Spades 23 | 's': 'M200,120C200,168,144,176,116,156C116,180,116,188,128,200C112,196,88,196,72,200C84,188,84,180,84,156C56,176,0,168,0,120C0,72,60,36,100,0C140,36,200,72,200,120z', 24 | //Clubs 25 | 'c': 'M80,200C92,184,92,160,92,136C76,180,0,176,0,124C0,80,40,76,68,88C80,92,80,88,72,84C44,64,40,0,100,0C160,0,156,64,128,84C120,88,120,92,132,88C160,76,200,80,200,124C200,176,124,180,108,136C108,160,108,184,120,200C100,196,100,196,80,200z', 26 | //cRown,cr 27 | 'r': 'M44,60,C45,56,-3,33,0,70,C2,107,39,146,48,150,C57,154,12,107,12,77,C12,45,43,65,44,60,M37,65,C31,64,20,60,19,81,C19,100,63,158,65,149,C65,139,33,102,37,65,M86,56,C87,52,38,28,40,66,C43,103,69,141,78,148,C86,155,54,102,54,71,C54,39,86,60,86,56,M82,65,C77,64,59,54,59,74,C60,95,82,146,84,138,C86,132,78,102,82,65,M154,60,C153,56,203,33,200,70,C197,107,159,146,151,150,C142,154,187,107,187,77,C187,45,155,65,154,60,M161,65,C167,64,179,60,180,81,C181,100,137,158,135,149,C134,139,165,102,161,65,M113,56,C112,52,161,28,158,66,C155,103,130,141,122,148,C114,155,145,102,145,71,C145,39,114,60,113,56,M117,65,C123,64,141,54,141,74,C140,95,118,146,116,138,C114,132,121,102,117,65z' 28 | //Nine - for 99, remove remark for below line and add a comma to upper line 29 | //'n': 'M157,89C159,188,80,211,16,196L23,160C62,172,100,167,107,119C93,127,83,133,62,132C28,133,0,113,0,70C0,25,37,0,78,0C137,0,157,41,157,89M105,56C100,42,92,34,77,33C59,33,49,49,49,69C52,101,83,104,107,95C107,82,108,66,105,56z' 30 | }, fixSuit = function(suit) { 31 | //hearts, diamonds, spades, clubs 32 | return (suit || 'h').substr(0, 1).toLowerCase(); 33 | }, fixSymbol = function(symbol) { 34 | //A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, JOKER, NINE, CROWN 35 | symbol = (symbol || 'o').toString().toLowerCase(); 36 | return symbol.substr((symbol.match(/jo|10|cr/)) ? 1 : 0, 1); 37 | }; 38 | 39 | /** 40 | * Draw card number side 41 | * @summary canvas.drawPokerCard (x, y, size, suit, point) 42 | * @param {number} [x=0] - The x coordinate of top left corner of card in canvas. 43 | * @param {number} [y=0] - The y coordinate of top left corner of card in canvas. 44 | * @param {number} [size=200] - Height pixel of card. The ratio of card width and height is fixed to 3:4. 45 | * @param {string} [suit='h'] - Poker suit. The value is case insensitive and it should be one of these value in []: 46 | * ['h', 'hearts', 'd', 'diamonds', 's', 'spades', 'c', 'clubs'] 47 | * 'h', 'd', 's', 'c' are abbreviation 48 | * When card point is 'O', 'h' or 'd' means big joker, 's' or 'c' means little joker. 49 | * @param {string} [point='O'] - Card point. The value is case insensitive and it should be one of these value in []: 50 | * ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'O', 'JOKER'] 51 | * 'O'(letter O) is abbreviation of 'JOKER' 52 | * @example 53 | * canvas.drawPokerCard (0, 400, 100, 'hearts', 'O'); 54 | * canvas.drawPokerCard (0, 200, 100, 'd', 'Q'); 55 | */ 56 | CanvasRenderingContext2D.prototype.drawPokerCard = function(x, y, size, suit, point) { 57 | var ax = function(n) { 58 | return x + n * size / 200; 59 | }, ay = function(n) { 60 | return y + n * size / 200; 61 | }, as = function(n) { 62 | return n * size / 200; 63 | }; 64 | 65 | suit = fixSuit(suit); 66 | point = fixSymbol(point); 67 | 68 | this.drawEmptyCard(ax(0), ay(0), as(200)); 69 | this.fillStyle = (suit === 'h' || suit === 'd') ? '#a22' : '#000'; 70 | if (size >= 100) { 71 | if (point !== 'o') { 72 | this.fillPokerSymbol(ax(40), ay(65), as(70), suit); 73 | this.fillPokerSymbol(ax(10), ay(10), as(40), point); 74 | this.fillPokerSymbol(ax(11), ay(55), as(25), suit); 75 | this.fillPokerSymbol(ax(140), ay(190), as(-40), point); 76 | this.fillPokerSymbol(ax(139), ay(145), as(-25), suit); 77 | } else { 78 | this.fillPokerSymbol(ax(11), ay(10), as(18), 'o'); 79 | this.fillPokerSymbol(ax(139), ay(190), as(-18), 'o'); 80 | if (suit === 'h' || suit === 'd') { 81 | this.drawPokerCrown(ax(38), ay(63), as(74), '#b55', '#a22'); 82 | this.drawPokerCrown(ax(40), ay(65), as(70), '#fdf98b', '#e7bd4f', '#a22'); 83 | } else { 84 | this.drawPokerCrown(ax(38), ay(63), as(74), '#000', '#000'); 85 | this.drawPokerCrown(ax(40), ay(65), as(70), '#eee', '#888', '#333'); 86 | } 87 | } 88 | } else { 89 | if (point !== 'o') { 90 | this.fillPokerSymbol(ax(30), ay(75), as(100), suit); 91 | this.fillPokerSymbol(ax(15), ay(15), as(50), point); 92 | } else { 93 | this.fillPokerSymbol(ax(11), ay(10), as(22), 'o'); 94 | if (suit === 'h' || suit === 'd') { 95 | this.drawPokerCrown(ax(45), ay(73), as(89), '#b55', '#a22'); 96 | this.drawPokerCrown(ax(47), ay(75), as(85), '#fdf98b', '#e7bd4f', '#a22'); 97 | } else { 98 | this.drawPokerCrown(ax(45), ay(73), as(89), '#000', '#000'); 99 | this.drawPokerCrown(ax(47), ay(75), as(85), '#eee', '#888', '#333'); 100 | } 101 | } 102 | } 103 | }; 104 | 105 | /** 106 | * Draw card back side 107 | * @summary canvas.drawPokerBack (x, y, size[, foregroundColor, backgroundColor]) 108 | * @param {number} [x=0] - The x coordinate of top left corner of card in canvas. 109 | * @param {number} [y=0] - The y coordinate of top left corner of card in canvas. 110 | * @param {number} [size=200] - Height pixel of card. The ratio of card width and height is fixed to 3:4. 111 | * @param {string} [foregroundColor='#BB5555'] - Foreground color. 112 | * @param {string} [backgroundColor='#AA2222'] - Background color. 113 | * @example 114 | * canvas.drawPokerBack (10, 10, 300, '#a22', '#b55') 115 | * canvas.drawPokerBack (375, 400, 100, '#2E319C', '#7A7BB8'); 116 | */ 117 | CanvasRenderingContext2D.prototype.drawPokerBack = function(x, y, size, foregroundColor, backgroundColor) { 118 | var ax = function(n) { 119 | return x + n * size / 200; 120 | }, ay = function(n) { 121 | return y + n * size / 200; 122 | }, as = function(n) { 123 | return n * size / 200; 124 | }; 125 | 126 | foregroundColor = foregroundColor || '#b55'; 127 | backgroundColor = backgroundColor || '#a22'; 128 | 129 | this.drawEmptyCard(x, y, size); 130 | 131 | this.fillStyle = backgroundColor; 132 | this.fillRoundRect(ax(10), ay(10), as(130), as(180), as(8)); 133 | this.strokeStyle = foregroundColor; 134 | this.strokeRoundRect(ax(18), ay(18), as(114), as(164), as(4)); 135 | this.fillStyle = foregroundColor; 136 | this.fillRoundRect(ax(26), ay(26), as(96), as(148), as(24), true); 137 | 138 | this.fillPokerSymbol(ax(24), ay(24), as(20), 's'); 139 | this.fillPokerSymbol(ax(106), ay(24), as(20), 's'); 140 | this.fillPokerSymbol(ax(44), ay(176), as(-20), 's'); 141 | this.fillPokerSymbol(ax(126), ay(176), as(-20), 's'); 142 | this.fillStyle = backgroundColor; 143 | this.fillRoundRect(ax(50), ay(40), as(50), as(120), as(24)); 144 | this.fillPokerSymbol(ax(32), ay(54), as(86), 's'); 145 | this.fillPokerSymbol(ax(30), ay(60), as(16), 's'); 146 | this.fillPokerSymbol(ax(104), ay(60), as(16), 's'); 147 | this.fillPokerSymbol(ax(30), ay(128), as(16), 's'); 148 | this.fillPokerSymbol(ax(104), ay(128), as(16), 's'); 149 | this.strokePokerSymbol(ax(31), ay(53), as(88), 's'); 150 | this.fillStyle = foregroundColor; 151 | 152 | this.fillPokerSymbol(ax(50), ay(75), as(50), 'c'); 153 | //for 99, replace the upper line to below 2 lines. 154 | //this.fillPokerSymbol(ax(47), ay(80), as(35), 'n'); 155 | //this.fillPokerSymbol(ax(77), ay(80), as(35), 'n'); 156 | }; 157 | 158 | /** 159 | * Draw round corner rectangle 160 | * @summary canvas.roundRect (x, y[, width, height[, radius[, direction]]]) 161 | * @param {number} [x=0] - The x coordinate of top left corner of rectangle in canvas. 162 | * @param {number} [y=0] - The y coordinate of top left corner of rectangle in canvas. 163 | * @param {number} [width=200] - Width of the rectangle. 164 | * @param {number} [height=200] - Height of the rectangle. 165 | * @param {number} [radius=20] - Radius of corner round. 166 | * @param {boolean} [direction=false] - Direction of corner round. The true or false means inward or outward. 167 | * @example 168 | * canvas.roundRect (0, 0, 200, 200, 30); 169 | * canvas.roundRect (50, 50, 100, 100, 30, true); 170 | */ 171 | CanvasRenderingContext2D.prototype.roundRect = function(x, y, width, height, radius, direction) { 172 | width = width || 200; 173 | height = height || 200; 174 | radius = radius || 20; 175 | 176 | this.beginPath(); 177 | if (!direction) { 178 | this.moveTo(x + radius, y); 179 | this.lineTo(x + width - radius, y); 180 | this.arc(x + width - radius, y + radius, radius, (Math.PI / 180) * 270, 0); 181 | this.lineTo(x + width, y + height - radius); 182 | this.arc(x + width - radius, y + height - radius, radius, 0, (Math.PI / 2)); 183 | this.lineTo(x + radius, y + height); 184 | this.arc(x + radius, y + height - radius, radius, (Math.PI / 2), Math.PI); 185 | this.lineTo(x, y + radius); 186 | this.arc(x + radius, y + radius, radius, Math.PI, (Math.PI / 180) * 270); 187 | } else { 188 | this.moveTo(x, y + radius); 189 | this.lineTo(x, y + height - radius); 190 | this.arc(x, y + height, radius, (Math.PI / 180) * 270, 0); 191 | this.lineTo(x + width - radius, y + height); 192 | this.arc(x + width, y + height, radius, Math.PI, (Math.PI / 180) * 270); 193 | this.lineTo(x + width, y + radius); 194 | this.arc(x + width, y, radius, Math.PI / 2, Math.PI); 195 | this.lineTo(x + radius, y); 196 | this.arc(x, y, radius, 0, Math.PI / 2); 197 | } 198 | this.closePath(); 199 | }; 200 | 201 | /** 202 | * Stroke round corner rectangle 203 | * @summary canvas.strokeRoundRect (x, y[, width, height[, radius[, direction]]]) 204 | * @param {number} [x=0] - The x coordinate of top left corner of rectangle in canvas. 205 | * @param {number} [y=0] - The y coordinate of top left corner of rectangle in canvas. 206 | * @param {number} [width=200] - Width of the rectangle. 207 | * @param {number} [height=200] - Height of the rectangle. 208 | * @param {number} [radius=20] - Radius of corner round. 209 | * @param {boolean} [direction=false] - Direction of corner round. The true or false means inward or outward. 210 | * @example 211 | * canvas.strokeRoundRect (0, 0, 200, 200, 30); 212 | * canvas.strokeRoundRect (50, 50, 100, 100, 30, true); 213 | */ 214 | CanvasRenderingContext2D.prototype.strokeRoundRect = function(x, y, width, height, radius, direction) { 215 | this.roundRect(x + 0.5, y + 0.5, width - 1, height - 1, radius, direction); 216 | this.stroke(); 217 | }; 218 | 219 | /** 220 | * Fill round corner rectangle 221 | * @summary canvas.fillRoundRect (x, y[, width, height[, radius[, direction]]]) 222 | * @param {number} [x=0] - The x coordinate of top left corner of rectangle in canvas. 223 | * @param {number} [y=0] - The y coordinate of top left corner of rectangle in canvas. 224 | * @param {number} [width=200] - Width of the rectangle. 225 | * @param {number} [height=200] - Height of the rectangle. 226 | * @param {number} [radius=20] - Radius of corner round. 227 | * @param {boolean} [direction=false] - Direction of corner round. The true or false means inward or outward. 228 | * @example 229 | * canvas.fillRoundRect (0, 0, 200, 200, 30); 230 | * canvas.fillRoundRect (50, 50, 100, 100, 30, true); 231 | */ 232 | CanvasRenderingContext2D.prototype.fillRoundRect = function(x, y, width, height, radius, direction) { 233 | this.roundRect(x, y, width, height, radius, direction); 234 | this.fill(); 235 | }; 236 | 237 | /** 238 | * Draw SVG curve 239 | * @summary canvas.svgCurve (x, y, size, svgPath) 240 | * @param {number} [x=0] - The x coordinate of top left corner of card in canvas. 241 | * @param {number} [y=0] - The y coordinate of top left corner of card in canvas. 242 | * @param {number} size - The pixel size of the curve. 243 | * @param {string} svgPath - Value of property 'd' of SVG 'path' method. 244 | * When create the curve by svg software, please move the origin of coordinate be 0,0. 245 | * And keep the bigger size of height and width to 200px. 246 | * Don't use AQ or T method in svg software, browser canvas have not relative method to render it. 247 | * @example 248 | * draw a heart symbol: 249 | * canvas.svgCurve ('M100,30C60,7 0,7 0,76C0,131 100,190 100,190C100,190 200,131 200,76C200,7 140,7 100,30z', 0, 0, 200)); 250 | */ 251 | CanvasRenderingContext2D.prototype.svgCurve = function(x, y, size, svgPath) { 252 | var relativeX, relativeY, pathNumber, pathArray, svgPathArray, ax = function(n) { 253 | return ( relativeX = x + n * size / 200); 254 | }, ay = function(n) { 255 | return ( relativeY = y + n * size / 200); 256 | }; 257 | svgPathArray = svgPath.replace(/ *([MZLHVCSQTA]) */gi, '|$1,').replace(/^\||\|[Z],/gi, '').split(/\|/); 258 | 259 | this.beginPath(); 260 | for (pathNumber in svgPathArray) { 261 | pathArray = svgPathArray[pathNumber].split(/[, ]/); 262 | if (pathArray[0] === 'M') { 263 | this.moveTo(ax(pathArray[1]), ay(pathArray[2])); 264 | } else if (pathArray[0] === 'L') { 265 | this.lineTo(ax(pathArray[1]), ay(pathArray[2])); 266 | } else if (pathArray[0] === 'H') { 267 | this.lineTo(ax(pathArray[1]), relativeY); 268 | } else if (pathArray[0] === 'V') { 269 | this.lineTo(relativeX, ay(pathArray[1])); 270 | } else if (pathArray[0] === 'C') { 271 | this.bezierCurveTo(ax(pathArray[1]), ay(pathArray[2]), ax(pathArray[3]), ay(pathArray[4]), ax(pathArray[5]), ay(pathArray[6])); 272 | } else if (pathArray[0] === 'Q') { 273 | this.quadraticCurveTo(ax(pathArray[1]), ay(pathArray[2]), ax(pathArray[3]), ay(pathArray[4])); 274 | } 275 | } 276 | this.closePath(); 277 | }; 278 | 279 | /** 280 | * Draw poker symbol 281 | * @summary canvas.drawPokerSymbol (x, y, size[, symbol]) 282 | * @param {number} [x=0] - The x coordinate of top left corner of card in canvas. 283 | * @param {number} [y=0] - The y coordinate of top left corner of card in canvas. 284 | * @param {number} [size=200] - Height pixel of card. The ratio of card width and height is fixed to 3:4. 285 | * @param {string} [symbol='O'] - The name of symbol. Value is case insensitive and should be one of below: 286 | * ['h', 'hearts', 'd', 'diamonds', 's', 'spades', 'c', 'clubs'] 287 | * 'h', 'd', 's', 'c' are abbreviation 288 | * ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'O', 'JOKER'] 289 | * 'O'(letter O) is abbreviation of 'JOKER' 290 | * ['R', 'CROWN'] // crown, a part of crown, to jointing a crown of JOKER card 291 | * ['N', 'NINE'] // Nine, bold '9' for jointing '99' pattern 292 | * @example 293 | * canvas.drawPokerSymbol (0, 0, 200, 'hearts'); 294 | */ 295 | CanvasRenderingContext2D.prototype.drawPokerSymbol = function(x, y, size, symbol) { 296 | symbol = fixSymbol(symbol); 297 | if (symbolPath[symbol]) { 298 | this.svgCurve(x, y, size, symbolPath[symbol]); 299 | } 300 | }; 301 | 302 | /** 303 | * Stroke poker symbol 304 | * @summary canvas.strokePokerSymbol (x, y, size[, symbol]) 305 | * @param {number} [x=0] - The x coordinate of top left corner of card in canvas. 306 | * @param {number} [y=0] - The y coordinate of top left corner of card in canvas. 307 | * @param {number} [size=200] - Height pixel of card. The ratio of card width and height is fixed to 3:4. 308 | * @param {string} [symbol='O'] - The name of symbol. Value is case insensitive and should be one of below: 309 | * ['h', 'hearts', 'd', 'diamonds', 's', 'spades', 'c', 'clubs'] 310 | * 'h', 'd', 's', 'c' are abbreviation 311 | * ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'O', 'JOKER'] 312 | * 'O'(letter O) is abbreviation of 'JOKER' 313 | * ['R', 'CROWN'] // crown, a part of crown, to jointing a crown of JOKER card 314 | * ['N', 'NINE'] // Nine, bold '9' for jointing '99' pattern 315 | * @example 316 | * canvas.strokePokerSymbol (0, 0, 200, 'hearts'); 317 | */ 318 | CanvasRenderingContext2D.prototype.strokePokerSymbol = function(x, y, size, symbol) { 319 | this.drawPokerSymbol(x + 0.5, y + 0.5, size - 1, symbol); 320 | this.stroke(); 321 | }; 322 | 323 | /** 324 | * Fill poker symbol 325 | * @summary canvas.fillPokerSymbol (x, y, size[, symbol]) 326 | * @param {number} [x=0] - The x coordinate of top left corner of card in canvas. 327 | * @param {number} [y=0] - The y coordinate of top left corner of card in canvas. 328 | * @param {number} [size=200] - Height pixel of card. The ratio of card width and height is fixed to 3:4. 329 | * @param {string} [symbol='O'] - The name of symbol. Value is case insensitive and should be one of below: 330 | * ['h', 'hearts', 'd', 'diamonds', 's', 'spades', 'c', 'clubs'] 331 | * 'h', 'd', 's', 'c' are abbreviation 332 | * ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'O', 'JOKER'] 333 | * 'O'(letter O) is abbreviation of 'JOKER' 334 | * ['R', 'CROWN'] // crown, a part of crown, to jointing a crown of JOKER card 335 | * ['N', 'NINE'] // Nine, bold '9' for jointing '99' pattern 336 | * @example 337 | * canvas.fillPokerSymbol (0, 0, 200, 'hearts'); 338 | */ 339 | CanvasRenderingContext2D.prototype.fillPokerSymbol = function(x, y, size, symbol) { 340 | this.drawPokerSymbol(x, y, size, symbol); 341 | this.fill(); 342 | }; 343 | 344 | /** 345 | * Draw crown 346 | * @summary canvas.drawPokerCrown (x, y, size[, startColor, endColor[, fillColor]]) 347 | * @param {number} [x=0] - The x coordinate of top left corner of card in canvas. 348 | * @param {number} [y=0] - The y coordinate of top left corner of card in canvas. 349 | * @param {number} [size=200] - Height pixel of card. The ratio of card width and height is fixed to 3:4. 350 | * @param {string} [startColor='#FDF98B'] - Start color of gradient background color. 351 | * @param {string} [endColor ='#E7BD4F'] - End color of gradient background color. 352 | * @param {string} [fillColor ='#FFFFFF'] - Fill color of jewel of crown. 353 | * @example 354 | * canvas.drawPokerCrown(0, 0, 200); 355 | */ 356 | CanvasRenderingContext2D.prototype.drawPokerCrown = function(x, y, size, startColor, endColor, fillColor) { 357 | var fillLinGrad, ax = function(n) { 358 | return x + n * size / 200; 359 | }, ay = function(n) { 360 | return y + n * size / 200; 361 | }, as = function(n) { 362 | return n * size / 200; 363 | }; 364 | 365 | startColor = startColor || '#fdf98b'; 366 | endColor = endColor || '#e7bd4f'; 367 | fillColor = fillColor || '#fff'; 368 | 369 | fillLinGrad = this.createLinearGradient(ax(5), ay(5), ax(100), ay(200)); 370 | fillLinGrad.addColorStop(0, startColor); 371 | fillLinGrad.addColorStop(1, endColor); 372 | 373 | this.fillStyle = fillLinGrad; 374 | this.fillPokerSymbol(ax(0), ay(0), as(200), 'r'); 375 | this.fillRoundRect(ax(88), ay(42), as(23), as(110), as(12)); 376 | this.fillPokerSymbol(ax(86), ay(18), as(27), 's'); 377 | this.fillRoundRect(ax(40), ay(150), as(120), as(24), as(10)); 378 | this.fillStyle = fillColor; 379 | this.fillPokerSymbol(ax(92), ay(26), as(15), 'd'); 380 | this.fillPokerSymbol(ax(93), ay(60), as(13), 'h'); 381 | this.fillPokerSymbol(ax(93), ay(80), as(13), 'h'); 382 | this.fillPokerSymbol(ax(93), ay(100), as(13), 'h'); 383 | this.fillPokerSymbol(ax(93), ay(120), as(13), 'h'); 384 | this.fillPokerSymbol(ax(93), ay(155), as(13), 'h'); 385 | this.fillPokerSymbol(ax(73), ay(155), as(13), 'h'); 386 | this.fillPokerSymbol(ax(53), ay(155), as(13), 'h'); 387 | this.fillPokerSymbol(ax(113), ay(155), as(13), 'h'); 388 | this.fillPokerSymbol(ax(133), ay(155), as(13), 'h'); 389 | }; 390 | 391 | /** 392 | * Draw blank card 393 | * @summary canvas.drawEmptyCard (x, y, size[, startColor, endColor]) 394 | * @param {number} [x=0] - The x coordinate of top left corner of card in canvas. 395 | * @param {number} [y=0] - The y coordinate of top left corner of card in canvas. 396 | * @param {number} [size=200] - Height pixel of card. The ratio of card width and height is fixed to 3:4. 397 | * @example 398 | * canvas.drawEmptyCard(0, 0, 200); 399 | */ 400 | CanvasRenderingContext2D.prototype.drawEmptyCard = function(x, y, size) { 401 | var fillLinGrad, ax = function(n) { 402 | return x + n * size / 200; 403 | }, ay = function(n) { 404 | return y + n * size / 200; 405 | }, as = function(n) { 406 | return n * size / 200; 407 | }; 408 | 409 | fillLinGrad = this.createLinearGradient(ax(5), ay(5), ax(55), ay(200)); 410 | fillLinGrad.addColorStop(0, '#fff'); 411 | fillLinGrad.addColorStop(1, '#e0e0e0'); 412 | 413 | this.fillStyle = fillLinGrad; 414 | this.fillRoundRect(ax(0), ay(0), as(150), as(200), as(16)); 415 | this.strokeStyle = '#666'; 416 | this.strokeRoundRect(ax(0), ay(0), as(150), as(200), as(16)); 417 | }; 418 | 419 | window.Poker = { 420 | /** 421 | * Draw card number side as a image 422 | * @summary Poker.getCardImage (size, suit, point) 423 | * @param {number} [size=200] - Height pixel of card. The ratio of card width and height is fixed to 3:4. 424 | * @param {string} [suit='h'] - Poker suit. The value is case insensitive and it should be one of these value in []: 425 | * ['h', 'hearts', 'd', 'diamonds', 's', 'spades', 'c', 'clubs'] 426 | * 'h', 'd', 's', 'c' are abbreviation 427 | * When card point is 'O', 'h' or 'd' means big joker, 's' or 'c' means little joker. 428 | * @param {string} [point='O'] - Card point. The value is case insensitive and it should be one of these value in []: 429 | * ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'O', 'JOKER'] 430 | * 'O'(letter O) is abbreviation of 'JOKER' 431 | * @return {HTMLElement} image 432 | * @example 433 | * document.body.appendChild(Poker.getCardImage(100, 'h', 'Q')); 434 | */ 435 | getCardImage: function(size, suit, point) { 436 | var image = document.createElement('img'); 437 | image.src = this.getCardData(size, suit, point); 438 | return image; 439 | }, 440 | 441 | /** 442 | * Get card number side image data 443 | * @summary Poker.getCardData (size, suit, point) 444 | * @param {number} [size=200] - Height pixel of card. The ratio of card width and height is fixed to 3:4. 445 | * @param {string} [suit='h'] - Poker suit. The value is case insensitive and it should be one of these value in []: 446 | * ['h', 'hearts', 'd', 'diamonds', 's', 'spades', 'c', 'clubs'] 447 | * 'h', 'd', 's', 'c' are abbreviation 448 | * When card point is 'O', 'h' or 'd' means big joker, 's' or 'c' means little joker. 449 | * @param {string} [point='O'] - Card point. The value is case insensitive and it should be one of these value in []: 450 | * ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'O', 'JOKER'] 451 | * 'O'(letter O) is abbreviation of 'JOKER' 452 | * @return {string} imageData 453 | * @example 454 | * var imgData = Poker.getCardData(100, 'h', 'Q'); 455 | */ 456 | getCardData: function(size, suit, point) { 457 | return this.getCardCanvas(size, suit, point).toDataURL(); 458 | }, 459 | 460 | /** 461 | * Draw card back side as a image 462 | * @summary Poker.getBackImage (size[, foregroundColor, backgroundColor]) 463 | * @param {number} [size=200] - Height pixel of card. The ratio of card width and height is fixed to 3:4. 464 | * @param {string} [foregroundColor='#BB5555'] - Foreground color. 465 | * @param {string} [backgroundColor='#AA2222'] - Background color. 466 | * @return {HTMLElement} image 467 | * @example 468 | * document.body.appendChild(Poker.getBackImage(300, '#2E319C', '#7A7BB8')); 469 | */ 470 | getBackImage: function(size, foregroundColor, backgroundColor) { 471 | var image = document.createElement('img'); 472 | image.src = this.getBackData(size, foregroundColor, backgroundColor); 473 | return image; 474 | }, 475 | 476 | /** 477 | * Get card back side image data 478 | * @summary Poker.getBackData (size[, foregroundColor, backgroundColor]) 479 | * @param {number} [size=200] - Height pixel of card. The ratio of card width and height is fixed to 3:4. 480 | * @param {string} [foregroundColor='#BB5555'] - Foreground color. 481 | * @param {string} [backgroundColor='#AA2222'] - Background color. 482 | * @return {string} imageData 483 | * @example 484 | * var imageData = Poker.getBackCanvas(300, '#2E319C', '#7A7BB8'); 485 | */ 486 | getBackData: function(size, foregroundColor, backgroundColor) { 487 | return this.getBackCanvas(size, foregroundColor, backgroundColor).toDataURL(); 488 | } 489 | }; 490 | -------------------------------------------------------------------------------- /static/js/net.js: -------------------------------------------------------------------------------- 1 | 2 | PG.Protocol = { 3 | REQ_CHEAT : 1, 4 | RSP_CHEAT : 2, 5 | 6 | REQ_LOGIN : 11, 7 | RSP_LOGIN : 12, 8 | 9 | REQ_ROOM_LIST : 13, 10 | RSP_ROOM_LIST : 14, 11 | 12 | REQ_TABLE_LIST : 15, 13 | RSP_TABLE_LIST : 16, 14 | 15 | REQ_JOIN_ROOM : 17, 16 | RSP_JOIN_ROOM : 18, 17 | 18 | REQ_JOIN_TABLE : 19, 19 | RSP_JOIN_TABLE : 20, 20 | 21 | REQ_NEW_TABLE : 21, 22 | RSP_NEW_TABLE : 22, 23 | 24 | REQ_DEAL_POKER : 31, 25 | RSP_DEAL_POKER : 32, 26 | 27 | REQ_CALL_SCORE : 33, 28 | RSP_CALL_SCORE : 34, 29 | 30 | REQ_SHOW_POKER : 35, 31 | RSP_SHOW_POKER : 36, 32 | 33 | REQ_SHOT_POKER : 37, 34 | RSP_SHOT_POKER : 38, 35 | 36 | REQ_GAME_OVER : 41, 37 | RSP_GAME_OVER : 42, 38 | 39 | REQ_CHAT : 43, 40 | RSP_CHAT : 44, 41 | 42 | REQ_RESTART : 45, 43 | RSP_RESTART : 46 44 | }; 45 | 46 | PG.Socket = { 47 | websocket: null, 48 | onmessage: null 49 | }; 50 | 51 | PG.Socket.connect = function(onopen, onmessage, onerror) { 52 | 53 | if (this.websocket != null) { 54 | return; 55 | } 56 | 57 | // if (localStorage.hasOwnProperty("port")){ 58 | // this.websocket = new WebSocket("ws://" + window.location.host +":" + localStorage.getItem("port") + "/ws"); 59 | // } else { 60 | this.websocket = new WebSocket("ws://" + window.location.host + "/ws"); 61 | // } 62 | 63 | this.websocket.binaryType = 'arraybuffer'; 64 | this.websocket.onopen = function(evt) { 65 | console.log("CONNECTED"); 66 | onopen(); 67 | }; 68 | 69 | this.websocket.onerror = function(evt) { 70 | console.log('CONNECT ERROR: ' + evt.data); 71 | onerror(); 72 | }; 73 | 74 | this.websocket.onclose = function(evt) { 75 | console.log("DISCONNECTED"); 76 | this.websocket = null; 77 | }; 78 | 79 | this.websocket.onmessage = function(evt) { 80 | console.log('RSP: ' + evt.data); 81 | onmessage(JSON.parse(evt.data)); 82 | }; 83 | }; 84 | 85 | PG.Socket.send = function(msg) { 86 | console.log('REQ: ' + msg); 87 | this.websocket.send(JSON.stringify(msg)); 88 | }; 89 | -------------------------------------------------------------------------------- /static/js/phaser-input.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * phaser-input - version 2.0.5 3 | * Adds input boxes to Phaser like CanvasInput, but also works for WebGL and Mobile, made for Phaser only. 4 | * 5 | * OrangeGames 6 | * Build at 02-06-2017 7 | * Released under MIT License 8 | */ 9 | 10 | var __extends = (this && this.__extends) || (function () { 11 | var extendStatics = Object.setPrototypeOf || 12 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 13 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 14 | return function (d, b) { 15 | extendStatics(d, b); 16 | function __() { this.constructor = d; } 17 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 18 | }; 19 | })(); 20 | var PhaserInput; 21 | (function (PhaserInput) { 22 | var InputType; 23 | (function (InputType) { 24 | InputType[InputType["text"] = 0] = "text"; 25 | InputType[InputType["password"] = 1] = "password"; 26 | InputType[InputType["number"] = 2] = "number"; 27 | })(InputType = PhaserInput.InputType || (PhaserInput.InputType = {})); 28 | var InputElement = (function () { 29 | function InputElement(game, id, type, value, focusIn, focusOut) { 30 | if (type === void 0) { type = InputType.text; } 31 | if (value === void 0) { value = ''; } 32 | var _this = this; 33 | this.id = id; 34 | this.type = type; 35 | this.game = game; 36 | this.focusIn = focusIn; 37 | this.focusOut = focusOut; 38 | var canvasTopX = this.game.canvas.getBoundingClientRect().top + document.body.scrollTop; 39 | this.element = document.createElement('input'); 40 | this.element.id = id; 41 | this.element.style.position = 'absolute'; 42 | this.element.style.top = canvasTopX + 'px'; 43 | this.element.style.left = (-40).toString() + 'px'; 44 | this.element.style.width = (10).toString() + 'px'; 45 | this.element.style.height = (10).toString() + 'px'; 46 | this.element.style.border = '0px'; 47 | this.element.value = this.value; 48 | this.element.type = InputType[type]; 49 | this.element.addEventListener('focusin', function () { 50 | if (_this.focusIn instanceof Phaser.Signal) { 51 | _this.focusIn.dispatch(); 52 | } 53 | }); 54 | this.element.addEventListener('focusout', function () { 55 | if (_this.focusOut instanceof Phaser.Signal) { 56 | _this.focusOut.dispatch(); 57 | } 58 | }); 59 | document.body.appendChild(this.element); 60 | } 61 | InputElement.prototype.addKeyUpListener = function (callback) { 62 | this.keyUpCallback = callback; 63 | document.addEventListener('keyup', this.keyUpCallback); 64 | this.element.addEventListener('input', this.keyUpCallback); 65 | }; 66 | InputElement.prototype.blockKeyDownEvents = function () { 67 | document.addEventListener('keydown', this.preventKeyPropagation); 68 | }; 69 | InputElement.prototype.preventKeyPropagation = function (evt) { 70 | if (evt.stopPropagation) { 71 | evt.stopPropagation(); 72 | } 73 | else { 74 | event.cancelBubble = true; 75 | } 76 | }; 77 | InputElement.prototype.unblockKeyDownEvents = function () { 78 | document.removeEventListener('keydown', this.preventKeyPropagation); 79 | }; 80 | InputElement.prototype.removeEventListener = function () { 81 | document.removeEventListener('keyup', this.keyUpCallback); 82 | this.element.removeEventListener('input', this.keyUpCallback); 83 | }; 84 | InputElement.prototype.destroy = function () { 85 | document.body.removeChild(this.element); 86 | }; 87 | InputElement.prototype.setMax = function (max, min) { 88 | if (max === undefined) { 89 | return; 90 | } 91 | if (this.type === InputType.text || this.type === InputType.password) { 92 | this.element.maxLength = parseInt(max, 10); 93 | } 94 | else if (this.type === InputType.number) { 95 | this.element.max = max; 96 | if (min === undefined) { 97 | return; 98 | } 99 | this.element.min = min; 100 | } 101 | }; 102 | Object.defineProperty(InputElement.prototype, "value", { 103 | get: function () { 104 | return this.element.value; 105 | }, 106 | set: function (value) { 107 | this.element.value = value; 108 | }, 109 | enumerable: true, 110 | configurable: true 111 | }); 112 | InputElement.prototype.focus = function () { 113 | var _this = this; 114 | this.element.focus(); 115 | if (!this.game.device.desktop && this.game.device.chrome) { 116 | var originalWidth_1 = window.innerWidth, originalHeight_1 = window.innerHeight; 117 | var kbAppeared_1 = false; 118 | var interval_1 = setInterval(function () { 119 | if (originalWidth_1 > window.innerWidth || originalHeight_1 > window.innerHeight) { 120 | kbAppeared_1 = true; 121 | } 122 | if (kbAppeared_1 && originalWidth_1 === window.innerWidth && originalHeight_1 === window.innerHeight) { 123 | if (_this.focusOut instanceof Phaser.Signal) { 124 | _this.focusOut.dispatch(); 125 | } 126 | clearInterval(interval_1); 127 | } 128 | }, 50); 129 | } 130 | }; 131 | InputElement.prototype.blur = function () { 132 | this.element.blur(); 133 | }; 134 | Object.defineProperty(InputElement.prototype, "hasSelection", { 135 | get: function () { 136 | if (this.type === InputType.number) { 137 | return false; 138 | } 139 | return this.element.selectionStart !== this.element.selectionEnd; 140 | }, 141 | enumerable: true, 142 | configurable: true 143 | }); 144 | Object.defineProperty(InputElement.prototype, "caretStart", { 145 | get: function () { 146 | return this.element.selectionEnd; 147 | }, 148 | enumerable: true, 149 | configurable: true 150 | }); 151 | Object.defineProperty(InputElement.prototype, "caretEnd", { 152 | get: function () { 153 | return this.element.selectionStart; 154 | }, 155 | enumerable: true, 156 | configurable: true 157 | }); 158 | InputElement.prototype.getCaretPosition = function () { 159 | if (this.type === InputType.number) { 160 | return -1; 161 | } 162 | return this.element.selectionStart; 163 | }; 164 | InputElement.prototype.setCaretPosition = function (pos) { 165 | if (this.type === InputType.number) { 166 | return; 167 | } 168 | this.element.setSelectionRange(pos, pos); 169 | }; 170 | return InputElement; 171 | }()); 172 | PhaserInput.InputElement = InputElement; 173 | })(PhaserInput || (PhaserInput = {})); 174 | var PhaserInput; 175 | (function (PhaserInput) { 176 | var ForceCase; 177 | (function (ForceCase) { 178 | ForceCase[ForceCase["none"] = 0] = "none"; 179 | ForceCase[ForceCase["lower"] = 1] = "lower"; 180 | ForceCase[ForceCase["upper"] = 2] = "upper"; 181 | })(ForceCase = PhaserInput.ForceCase || (PhaserInput.ForceCase = {})); 182 | var InputField = (function (_super) { 183 | __extends(InputField, _super); 184 | function InputField(game, x, y, inputOptions) { 185 | if (inputOptions === void 0) { inputOptions = {}; } 186 | var _this = _super.call(this, game, x, y) || this; 187 | _this.focusOutOnEnter = true; 188 | _this.placeHolder = null; 189 | _this.box = null; 190 | _this.focus = false; 191 | _this.value = ''; 192 | _this.windowScale = 1; 193 | _this.blockInput = true; 194 | _this.focusIn = new Phaser.Signal(); 195 | _this.focusOut = new Phaser.Signal(); 196 | _this.blink = true; 197 | _this.cnt = 0; 198 | _this.inputOptions = inputOptions; 199 | _this.inputOptions.width = (typeof inputOptions.width === 'number') ? inputOptions.width : 150; 200 | _this.inputOptions.padding = (typeof inputOptions.padding === 'number') ? inputOptions.padding : 0; 201 | _this.inputOptions.textAlign = inputOptions.textAlign || 'left'; 202 | _this.inputOptions.type = inputOptions.type || PhaserInput.InputType.text; 203 | _this.inputOptions.forceCase = (inputOptions.forceCase) ? inputOptions.forceCase : ForceCase.none; 204 | _this.inputOptions.borderRadius = (typeof inputOptions.borderRadius === 'number') ? inputOptions.borderRadius : 0; 205 | _this.inputOptions.height = (typeof inputOptions.height === 'number') ? inputOptions.height : 14; 206 | _this.inputOptions.fillAlpha = (inputOptions.fillAlpha === undefined) ? 1 : inputOptions.fillAlpha; 207 | _this.inputOptions.selectionColor = inputOptions.selectionColor || 'rgba(179, 212, 253, 0.8)'; 208 | _this.inputOptions.zoom = (!game.device.desktop) ? inputOptions.zoom || false : false; 209 | _this.box = new PhaserInput.InputBox(_this.game, inputOptions); 210 | _this.setTexture(_this.box.generateTexture()); 211 | _this.textMask = new PhaserInput.TextMask(_this.game, inputOptions); 212 | _this.addChild(_this.textMask); 213 | _this.domElement = new PhaserInput.InputElement(_this.game, 'phaser-input-' + (Math.random() * 10000 | 0).toString(), _this.inputOptions.type, _this.value, _this.focusIn, _this.focusOut); 214 | _this.domElement.setMax(_this.inputOptions.max, _this.inputOptions.min); 215 | _this.selection = new PhaserInput.SelectionHighlight(_this.game, _this.inputOptions); 216 | _this.selection.mask = _this.textMask; 217 | _this.addChild(_this.selection); 218 | if (inputOptions.placeHolder && inputOptions.placeHolder.length > 0) { 219 | _this.placeHolder = new Phaser.Text(game, _this.inputOptions.padding, _this.inputOptions.padding, inputOptions.placeHolder, { 220 | font: inputOptions.font || '14px Arial', 221 | fontWeight: inputOptions.fontWeight || 'normal', 222 | fill: inputOptions.placeHolderColor || '#bfbebd' 223 | }); 224 | _this.placeHolder.mask = _this.textMask; 225 | _this.addChild(_this.placeHolder); 226 | } 227 | _this.cursor = new Phaser.Text(game, _this.inputOptions.padding, _this.inputOptions.padding - 2, '|', { 228 | font: inputOptions.font || '14px Arial', 229 | fontWeight: inputOptions.fontWeight || 'normal', 230 | fill: inputOptions.cursorColor || '#000000' 231 | }); 232 | _this.cursor.visible = false; 233 | _this.addChild(_this.cursor); 234 | _this.text = new Phaser.Text(game, _this.inputOptions.padding, _this.inputOptions.padding, '', { 235 | font: inputOptions.font || '14px Arial', 236 | fontWeight: inputOptions.fontWeight || 'normal', 237 | fill: inputOptions.fill || '#000000' 238 | }); 239 | _this.text.mask = _this.textMask; 240 | _this.addChild(_this.text); 241 | _this.offscreenText = new Phaser.Text(game, _this.inputOptions.padding, _this.inputOptions.padding, '', { 242 | font: inputOptions.font || '14px Arial', 243 | fontWeight: inputOptions.fontWeight || 'normal', 244 | fill: inputOptions.fill || '#000000' 245 | }); 246 | _this.updateTextAlignment(); 247 | _this.inputEnabled = true; 248 | _this.input.useHandCursor = true; 249 | _this.game.input.onDown.add(_this.checkDown, _this); 250 | _this.focusOut.add(function () { 251 | if (PhaserInput.KeyboardOpen) { 252 | _this.endFocus(); 253 | if (_this.inputOptions.zoom) { 254 | _this.zoomOut(); 255 | } 256 | } 257 | }); 258 | return _this; 259 | } 260 | Object.defineProperty(InputField.prototype, "width", { 261 | get: function () { 262 | return this.inputOptions.width; 263 | }, 264 | set: function (width) { 265 | this.inputOptions.width = width; 266 | this.box.resize(width); 267 | this.textMask.resize(width); 268 | this.updateTextAlignment(); 269 | }, 270 | enumerable: true, 271 | configurable: true 272 | }); 273 | InputField.prototype.updateTextAlignment = function () { 274 | switch (this.inputOptions.textAlign) { 275 | case 'left': 276 | this.text.anchor.set(0, 0); 277 | this.text.x = this.inputOptions.padding; 278 | if (null !== this.placeHolder) { 279 | this.placeHolder.anchor.set(0, 0); 280 | } 281 | this.cursor.x = this.inputOptions.padding + this.getCaretPosition(); 282 | break; 283 | case 'center': 284 | this.text.anchor.set(0.5, 0); 285 | this.text.x = this.inputOptions.padding + this.inputOptions.width / 2; 286 | if (null !== this.placeHolder) { 287 | this.placeHolder.anchor.set(0.5, 0); 288 | this.placeHolder.x = this.inputOptions.padding + this.inputOptions.width / 2; 289 | } 290 | this.cursor.x = this.inputOptions.padding + this.inputOptions.width / 2 - this.text.width / 2 + this.getCaretPosition(); 291 | break; 292 | case 'right': 293 | this.text.anchor.set(1, 0); 294 | this.text.x = this.inputOptions.padding + this.inputOptions.width; 295 | if (null !== this.placeHolder) { 296 | this.placeHolder.anchor.set(1, 0); 297 | this.placeHolder.x = this.inputOptions.padding + this.inputOptions.width; 298 | } 299 | this.cursor.x = this.inputOptions.padding + this.inputOptions.width; 300 | break; 301 | } 302 | }; 303 | InputField.prototype.checkDown = function (e) { 304 | if (!this.value) { 305 | this.resetText(); 306 | } 307 | if (this.input.checkPointerOver(e)) { 308 | if (this.focus) { 309 | this.setCaretOnclick(e); 310 | return; 311 | } 312 | if (this.inputOptions.zoom && !PhaserInput.Zoomed) { 313 | this.zoomIn(); 314 | } 315 | this.startFocus(); 316 | } 317 | else { 318 | if (this.focus === true) { 319 | this.endFocus(); 320 | if (this.inputOptions.zoom) { 321 | this.zoomOut(); 322 | } 323 | } 324 | } 325 | }; 326 | InputField.prototype.update = function () { 327 | this.text.update(); 328 | if (this.placeHolder) { 329 | this.placeHolder.update(); 330 | } 331 | if (!this.focus) { 332 | return; 333 | } 334 | if (this.cnt !== 30) { 335 | return this.cnt++; 336 | } 337 | this.cursor.visible = this.blink; 338 | this.blink = !this.blink; 339 | this.cnt = 0; 340 | }; 341 | InputField.prototype.endFocus = function () { 342 | var _this = this; 343 | if (!this.focus) { 344 | return; 345 | } 346 | this.domElement.removeEventListener(); 347 | if (this.blockInput === true) { 348 | this.domElement.unblockKeyDownEvents(); 349 | } 350 | this.focus = false; 351 | if (this.value.length === 0 && null !== this.placeHolder) { 352 | this.placeHolder.visible = true; 353 | } 354 | this.cursor.visible = false; 355 | if (this.game.device.desktop) { 356 | setTimeout(function () { 357 | _this.domElement.blur(); 358 | }, 0); 359 | } 360 | else { 361 | this.domElement.blur(); 362 | } 363 | if (!this.game.device.desktop) { 364 | PhaserInput.KeyboardOpen = false; 365 | PhaserInput.onKeyboardClose.dispatch(); 366 | } 367 | }; 368 | InputField.prototype.startFocus = function () { 369 | var _this = this; 370 | this.focus = true; 371 | if (null !== this.placeHolder) { 372 | this.placeHolder.visible = false; 373 | } 374 | if (this.game.device.desktop) { 375 | setTimeout(function () { 376 | _this.keyUpProcessor(); 377 | }, 0); 378 | } 379 | else { 380 | this.keyUpProcessor(); 381 | } 382 | if (!this.game.device.desktop) { 383 | PhaserInput.KeyboardOpen = true; 384 | PhaserInput.onKeyboardOpen.dispatch(); 385 | } 386 | }; 387 | InputField.prototype.keyUpProcessor = function () { 388 | this.domElement.addKeyUpListener(this.keyListener.bind(this)); 389 | this.domElement.focus(); 390 | if (this.blockInput === true) { 391 | this.domElement.blockKeyDownEvents(); 392 | } 393 | }; 394 | InputField.prototype.updateText = function () { 395 | var text = ''; 396 | if (this.inputOptions.type === PhaserInput.InputType.password) { 397 | for (var i = 0; i < this.value.length; i++) { 398 | text += '*'; 399 | } 400 | } 401 | else if (this.inputOptions.type === PhaserInput.InputType.number) { 402 | var val = parseInt(this.value); 403 | if (val < parseInt(this.inputOptions.min)) { 404 | text = this.value = this.domElement.value = this.inputOptions.min; 405 | } 406 | else if (val > parseInt(this.inputOptions.max)) { 407 | text = this.value = this.domElement.value = this.inputOptions.max; 408 | } 409 | else { 410 | text = this.value; 411 | } 412 | } 413 | else { 414 | text = this.value; 415 | } 416 | this.text.setText(text); 417 | if (this.text.width > this.inputOptions.width) { 418 | this.text.anchor.x = 1; 419 | this.text.x = this.inputOptions.padding + this.inputOptions.width; 420 | } 421 | else { 422 | switch (this.inputOptions.textAlign) { 423 | case 'left': 424 | this.text.anchor.set(0, 0); 425 | this.text.x = this.inputOptions.padding; 426 | break; 427 | case 'center': 428 | this.text.anchor.set(0.5, 0); 429 | this.text.x = this.inputOptions.padding + this.inputOptions.width / 2; 430 | break; 431 | case 'right': 432 | this.text.anchor.set(1, 0); 433 | this.text.x = this.inputOptions.padding + this.inputOptions.width; 434 | break; 435 | } 436 | } 437 | }; 438 | InputField.prototype.updateCursor = function () { 439 | if (this.text.width > this.inputOptions.width || this.inputOptions.textAlign === 'right') { 440 | this.cursor.x = this.inputOptions.padding + this.inputOptions.width; 441 | } 442 | else { 443 | switch (this.inputOptions.textAlign) { 444 | case 'left': 445 | this.cursor.x = this.inputOptions.padding + this.getCaretPosition(); 446 | break; 447 | case 'center': 448 | this.cursor.x = this.inputOptions.padding + this.inputOptions.width / 2 - this.text.width / 2 + this.getCaretPosition(); 449 | break; 450 | } 451 | } 452 | }; 453 | InputField.prototype.getCaretPosition = function () { 454 | var caretPosition = this.domElement.getCaretPosition(); 455 | if (-1 === caretPosition) { 456 | return this.text.width; 457 | } 458 | var text = this.value; 459 | if (this.inputOptions.type === PhaserInput.InputType.password) { 460 | text = ''; 461 | for (var i = 0; i < this.value.length; i++) { 462 | text += '*'; 463 | } 464 | } 465 | this.offscreenText.setText(text.slice(0, caretPosition)); 466 | return this.offscreenText.width; 467 | }; 468 | InputField.prototype.setCaretOnclick = function (e) { 469 | var localX = (this.text.toLocal(new PIXI.Point(e.x, e.y), this.game.world)).x; 470 | if (this.inputOptions.textAlign && this.inputOptions.textAlign === 'center') { 471 | localX += this.text.width / 2; 472 | } 473 | var characterWidth = this.text.width / this.value.length; 474 | var index = 0; 475 | for (var i = 0; i < this.value.length; i++) { 476 | if (localX >= i * characterWidth && localX <= (i + 1) * characterWidth) { 477 | index = i; 478 | break; 479 | } 480 | } 481 | if (localX > (this.value.length - 1) * characterWidth) { 482 | index = this.value.length; 483 | } 484 | this.startFocus(); 485 | this.domElement.setCaretPosition(index); 486 | this.updateCursor(); 487 | }; 488 | InputField.prototype.updateSelection = function () { 489 | if (this.domElement.hasSelection) { 490 | var text = this.value; 491 | if (this.inputOptions.type === PhaserInput.InputType.password) { 492 | text = ''; 493 | for (var i = 0; i < this.value.length; i++) { 494 | text += '*'; 495 | } 496 | } 497 | text = text.substring(this.domElement.caretStart, this.domElement.caretEnd); 498 | this.offscreenText.setText(text); 499 | this.selection.updateSelection(this.offscreenText.getBounds()); 500 | switch (this.inputOptions.textAlign) { 501 | case 'left': 502 | this.selection.x = this.inputOptions.padding; 503 | break; 504 | case 'center': 505 | this.selection.x = this.inputOptions.padding + this.inputOptions.width / 2 - this.text.width / 2; 506 | break; 507 | } 508 | } 509 | else { 510 | this.selection.clear(); 511 | } 512 | }; 513 | InputField.prototype.zoomIn = function () { 514 | if (PhaserInput.Zoomed) { 515 | return; 516 | } 517 | var bounds = this.getBounds(); 518 | if (window.innerHeight > window.innerWidth) { 519 | this.windowScale = this.game.width / (bounds.width * 1.5); 520 | } 521 | else { 522 | this.windowScale = (this.game.width / 2) / (bounds.width * 1.5); 523 | } 524 | var offsetX = ((this.game.width - bounds.width * 1.5) / 2) / this.windowScale; 525 | this.game.world.scale.set(this.game.world.scale.x * this.windowScale, this.game.world.scale.y * this.windowScale); 526 | this.game.world.pivot.set(bounds.x - offsetX, bounds.y - this.inputOptions.padding * 2); 527 | PhaserInput.Zoomed = true; 528 | }; 529 | InputField.prototype.zoomOut = function () { 530 | if (!PhaserInput.Zoomed) { 531 | return; 532 | } 533 | this.game.world.scale.set(this.game.world.scale.x / this.windowScale, this.game.world.scale.y / this.windowScale); 534 | this.game.world.pivot.set(0, 0); 535 | PhaserInput.Zoomed = false; 536 | }; 537 | InputField.prototype.keyListener = function (evt) { 538 | this.value = this.getFormattedText(this.domElement.value); 539 | if (evt.keyCode === 13) { 540 | if (this.focusOutOnEnter) { 541 | this.endFocus(); 542 | } 543 | return; 544 | } 545 | this.updateText(); 546 | this.updateCursor(); 547 | this.updateSelection(); 548 | evt.preventDefault(); 549 | }; 550 | InputField.prototype.destroy = function (destroyChildren) { 551 | this.game.input.onDown.remove(this.checkDown, this); 552 | this.focusIn.removeAll(); 553 | this.focusOut.removeAll(); 554 | this.domElement.destroy(); 555 | _super.prototype.destroy.call(this, destroyChildren); 556 | }; 557 | InputField.prototype.resetText = function () { 558 | this.setText(); 559 | }; 560 | InputField.prototype.setText = function (text) { 561 | if (text === void 0) { text = ''; } 562 | if (null !== this.placeHolder) { 563 | if (text.length > 0) { 564 | this.placeHolder.visible = false; 565 | } 566 | else { 567 | this.placeHolder.visible = true; 568 | } 569 | } 570 | this.value = this.getFormattedText(text); 571 | this.domElement.value = this.value; 572 | this.updateText(); 573 | this.updateCursor(); 574 | this.endFocus(); 575 | }; 576 | InputField.prototype.getFormattedText = function (text) { 577 | switch (this.inputOptions.forceCase) { 578 | default: 579 | case ForceCase.none: 580 | return text; 581 | case ForceCase.lower: 582 | return text.toLowerCase(); 583 | case ForceCase.upper: 584 | return text.toUpperCase(); 585 | } 586 | }; 587 | return InputField; 588 | }(Phaser.Sprite)); 589 | PhaserInput.InputField = InputField; 590 | })(PhaserInput || (PhaserInput = {})); 591 | var PhaserInput; 592 | (function (PhaserInput) { 593 | var InputBox = (function (_super) { 594 | __extends(InputBox, _super); 595 | function InputBox(game, inputOptions) { 596 | var _this = _super.call(this, game, 0, 0) || this; 597 | _this.bgColor = (inputOptions.backgroundColor) ? parseInt(inputOptions.backgroundColor.slice(1), 16) : 0xffffff; 598 | _this.borderRadius = inputOptions.borderRadius || 0; 599 | _this.borderWidth = inputOptions.borderWidth || 1; 600 | _this.borderColor = (inputOptions.borderColor) ? parseInt(inputOptions.borderColor.slice(1), 16) : 0x959595; 601 | _this.boxAlpha = inputOptions.fillAlpha; 602 | _this.padding = inputOptions.padding; 603 | var height = inputOptions.height; 604 | var width = inputOptions.width; 605 | var height; 606 | if (inputOptions.font) { 607 | height = Math.max(parseInt(inputOptions.font.substr(0, inputOptions.font.indexOf('px')), 10), height); 608 | } 609 | _this.boxHeight = _this.padding * 2 + height; 610 | var width = inputOptions.width; 611 | _this.boxWidth = _this.padding * 2 + width; 612 | _this.drawBox(); 613 | return _this; 614 | } 615 | InputBox.prototype.resize = function (newWidth) { 616 | this.boxWidth = this.padding * 2 + newWidth; 617 | this.drawBox(); 618 | }; 619 | InputBox.prototype.drawBox = function () { 620 | this.clear() 621 | .beginFill(this.bgColor, this.boxAlpha) 622 | .lineStyle(this.borderWidth, this.borderColor, this.boxAlpha); 623 | if (this.borderRadius > 0) { 624 | this.drawRoundedRect(0, 0, this.boxWidth, this.boxHeight, this.borderRadius); 625 | } 626 | else { 627 | this.drawRect(0, 0, this.boxWidth, this.boxHeight); 628 | } 629 | }; 630 | return InputBox; 631 | }(Phaser.Graphics)); 632 | PhaserInput.InputBox = InputBox; 633 | })(PhaserInput || (PhaserInput = {})); 634 | var PhaserInput; 635 | (function (PhaserInput) { 636 | var SelectionHighlight = (function (_super) { 637 | __extends(SelectionHighlight, _super); 638 | function SelectionHighlight(game, inputOptions) { 639 | var _this = _super.call(this, game, inputOptions.padding, inputOptions.padding) || this; 640 | _this.inputOptions = inputOptions; 641 | return _this; 642 | } 643 | SelectionHighlight.prototype.updateSelection = function (rect) { 644 | var color = Phaser.Color.webToColor(this.inputOptions.selectionColor); 645 | this.clear(); 646 | this.beginFill(SelectionHighlight.rgb2hex(color), color.a); 647 | this.drawRect(rect.x, rect.y, rect.width, rect.height - this.inputOptions.padding); 648 | }; 649 | SelectionHighlight.rgb2hex = function (color) { 650 | return parseInt(("0" + color.r.toString(16)).slice(-2) + 651 | ("0" + color.g.toString(16)).slice(-2) + 652 | ("0" + color.b.toString(16)).slice(-2), 16); 653 | }; 654 | return SelectionHighlight; 655 | }(Phaser.Graphics)); 656 | PhaserInput.SelectionHighlight = SelectionHighlight; 657 | })(PhaserInput || (PhaserInput = {})); 658 | var PhaserInput; 659 | (function (PhaserInput) { 660 | var TextMask = (function (_super) { 661 | __extends(TextMask, _super); 662 | function TextMask(game, inputOptions) { 663 | var _this = _super.call(this, game, inputOptions.padding, inputOptions.padding) || this; 664 | var height = inputOptions.height; 665 | if (inputOptions.font) { 666 | height = Math.max(parseInt(inputOptions.font.substr(0, inputOptions.font.indexOf('px')), 10), height); 667 | } 668 | _this.maskWidth = inputOptions.width; 669 | _this.maskHeight = height * 1.3; 670 | _this.drawMask(); 671 | return _this; 672 | } 673 | TextMask.prototype.resize = function (newWidth) { 674 | this.maskWidth = newWidth; 675 | this.drawMask(); 676 | }; 677 | TextMask.prototype.drawMask = function () { 678 | this.clear() 679 | .beginFill(0x000000) 680 | .drawRect(0, 0, this.maskWidth, this.maskHeight) 681 | .endFill(); 682 | }; 683 | return TextMask; 684 | }(Phaser.Graphics)); 685 | PhaserInput.TextMask = TextMask; 686 | })(PhaserInput || (PhaserInput = {})); 687 | var PhaserInput; 688 | (function (PhaserInput) { 689 | PhaserInput.Zoomed = false; 690 | PhaserInput.KeyboardOpen = false; 691 | PhaserInput.onKeyboardOpen = new Phaser.Signal(); 692 | PhaserInput.onKeyboardClose = new Phaser.Signal(); 693 | var Plugin = (function (_super) { 694 | __extends(Plugin, _super); 695 | function Plugin(game, parent) { 696 | var _this = _super.call(this, game, parent) || this; 697 | _this.addInputFieldFactory(); 698 | return _this; 699 | } 700 | Plugin.prototype.addInputFieldFactory = function () { 701 | Phaser.GameObjectFactory.prototype.inputField = function (x, y, inputOptions, group) { 702 | if (group === undefined) { 703 | group = this.world; 704 | } 705 | var nineSliceObject = new PhaserInput.InputField(this.game, x, y, inputOptions); 706 | return group.add(nineSliceObject); 707 | }; 708 | Phaser.GameObjectCreator.prototype.inputField = function (x, y, inputOptions) { 709 | return new PhaserInput.InputField(this.game, x, y, inputOptions); 710 | }; 711 | }; 712 | return Plugin; 713 | }(Phaser.Plugin)); 714 | Plugin.Zoomed = false; 715 | Plugin.KeyboardOpen = false; 716 | Plugin.onKeyboardOpen = new Phaser.Signal(); 717 | Plugin.onKeyboardClose = new Phaser.Signal(); 718 | PhaserInput.Plugin = Plugin; 719 | })(PhaserInput || (PhaserInput = {})); 720 | //# sourceMappingURL=phaser-input.js.map -------------------------------------------------------------------------------- /static/js/phaser-input.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * phaser-input - version 2.0.5 3 | * Adds input boxes to Phaser like CanvasInput, but also works for WebGL and Mobile, made for Phaser only. 4 | * 5 | * OrangeGames 6 | * Build at 02-06-2017 7 | * Released under MIT License 8 | */ 9 | 10 | var __extends=this&&this.__extends||function(){var a=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(a,b){a.__proto__=b}||function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c])};return function(b,c){function d(){this.constructor=b}a(b,c),b.prototype=null===c?Object.create(c):(d.prototype=c.prototype,new d)}}(),PhaserInput;!function(a){var b;!function(a){a[a.text=0]="text",a[a.password=1]="password",a[a.number=2]="number"}(b=a.InputType||(a.InputType={}));var c=function(){function a(a,c,d,e,f,g){void 0===d&&(d=b.text),void 0===e&&(e="");var h=this;this.id=c,this.type=d,this.game=a,this.focusIn=f,this.focusOut=g;var i=this.game.canvas.getBoundingClientRect().top+document.body.scrollTop;this.element=document.createElement("input"),this.element.id=c,this.element.style.position="absolute",this.element.style.top=i+"px",this.element.style.left=(-40).toString()+"px",this.element.style.width=10..toString()+"px",this.element.style.height=10..toString()+"px",this.element.style.border="0px",this.element.value=this.value,this.element.type=b[d],this.element.addEventListener("focusin",function(){h.focusIn instanceof Phaser.Signal&&h.focusIn.dispatch()}),this.element.addEventListener("focusout",function(){h.focusOut instanceof Phaser.Signal&&h.focusOut.dispatch()}),document.body.appendChild(this.element)}return a.prototype.addKeyUpListener=function(a){this.keyUpCallback=a,document.addEventListener("keyup",this.keyUpCallback),this.element.addEventListener("input",this.keyUpCallback)},a.prototype.blockKeyDownEvents=function(){document.addEventListener("keydown",this.preventKeyPropagation)},a.prototype.preventKeyPropagation=function(a){a.stopPropagation?a.stopPropagation():event.cancelBubble=!0},a.prototype.unblockKeyDownEvents=function(){document.removeEventListener("keydown",this.preventKeyPropagation)},a.prototype.removeEventListener=function(){document.removeEventListener("keyup",this.keyUpCallback),this.element.removeEventListener("input",this.keyUpCallback)},a.prototype.destroy=function(){document.body.removeChild(this.element)},a.prototype.setMax=function(a,c){if(void 0!==a)if(this.type===b.text||this.type===b.password)this.element.maxLength=parseInt(a,10);else if(this.type===b.number){if(this.element.max=a,void 0===c)return;this.element.min=c}},Object.defineProperty(a.prototype,"value",{get:function(){return this.element.value},set:function(a){this.element.value=a},enumerable:!0,configurable:!0}),a.prototype.focus=function(){var a=this;if(this.element.focus(),!this.game.device.desktop&&this.game.device.chrome)var b=window.innerWidth,c=window.innerHeight,d=!1,e=setInterval(function(){(b>window.innerWidth||c>window.innerHeight)&&(d=!0),d&&b===window.innerWidth&&c===window.innerHeight&&(a.focusOut instanceof Phaser.Signal&&a.focusOut.dispatch(),clearInterval(e))},50)},a.prototype.blur=function(){this.element.blur()},Object.defineProperty(a.prototype,"hasSelection",{get:function(){return this.type===b.number?!1:this.element.selectionStart!==this.element.selectionEnd},enumerable:!0,configurable:!0}),Object.defineProperty(a.prototype,"caretStart",{get:function(){return this.element.selectionEnd},enumerable:!0,configurable:!0}),Object.defineProperty(a.prototype,"caretEnd",{get:function(){return this.element.selectionStart},enumerable:!0,configurable:!0}),a.prototype.getCaretPosition=function(){return this.type===b.number?-1:this.element.selectionStart},a.prototype.setCaretPosition=function(a){this.type!==b.number&&this.element.setSelectionRange(a,a)},a}();a.InputElement=c}(PhaserInput||(PhaserInput={}));var PhaserInput;!function(a){var b;!function(a){a[a.none=0]="none",a[a.lower=1]="lower",a[a.upper=2]="upper"}(b=a.ForceCase||(a.ForceCase={}));var c=function(c){function d(d,e,f,g){void 0===g&&(g={});var h=c.call(this,d,e,f)||this;return h.focusOutOnEnter=!0,h.placeHolder=null,h.box=null,h.focus=!1,h.value="",h.windowScale=1,h.blockInput=!0,h.focusIn=new Phaser.Signal,h.focusOut=new Phaser.Signal,h.blink=!0,h.cnt=0,h.inputOptions=g,h.inputOptions.width="number"==typeof g.width?g.width:150,h.inputOptions.padding="number"==typeof g.padding?g.padding:0,h.inputOptions.textAlign=g.textAlign||"left",h.inputOptions.type=g.type||a.InputType.text,h.inputOptions.forceCase=g.forceCase?g.forceCase:b.none,h.inputOptions.borderRadius="number"==typeof g.borderRadius?g.borderRadius:0,h.inputOptions.height="number"==typeof g.height?g.height:14,h.inputOptions.fillAlpha=void 0===g.fillAlpha?1:g.fillAlpha,h.inputOptions.selectionColor=g.selectionColor||"rgba(179, 212, 253, 0.8)",h.inputOptions.zoom=d.device.desktop?!1:g.zoom||!1,h.box=new a.InputBox(h.game,g),h.setTexture(h.box.generateTexture()),h.textMask=new a.TextMask(h.game,g),h.addChild(h.textMask),h.domElement=new a.InputElement(h.game,"phaser-input-"+(1e4*Math.random()|0).toString(),h.inputOptions.type,h.value,h.focusIn,h.focusOut),h.domElement.setMax(h.inputOptions.max,h.inputOptions.min),h.selection=new a.SelectionHighlight(h.game,h.inputOptions),h.selection.mask=h.textMask,h.addChild(h.selection),g.placeHolder&&g.placeHolder.length>0&&(h.placeHolder=new Phaser.Text(d,h.inputOptions.padding,h.inputOptions.padding,g.placeHolder,{font:g.font||"14px Arial",fontWeight:g.fontWeight||"normal",fill:g.placeHolderColor||"#bfbebd"}),h.placeHolder.mask=h.textMask,h.addChild(h.placeHolder)),h.cursor=new Phaser.Text(d,h.inputOptions.padding,h.inputOptions.padding-2,"|",{font:g.font||"14px Arial",fontWeight:g.fontWeight||"normal",fill:g.cursorColor||"#000000"}),h.cursor.visible=!1,h.addChild(h.cursor),h.text=new Phaser.Text(d,h.inputOptions.padding,h.inputOptions.padding,"",{font:g.font||"14px Arial",fontWeight:g.fontWeight||"normal",fill:g.fill||"#000000"}),h.text.mask=h.textMask,h.addChild(h.text),h.offscreenText=new Phaser.Text(d,h.inputOptions.padding,h.inputOptions.padding,"",{font:g.font||"14px Arial",fontWeight:g.fontWeight||"normal",fill:g.fill||"#000000"}),h.updateTextAlignment(),h.inputEnabled=!0,h.input.useHandCursor=!0,h.game.input.onDown.add(h.checkDown,h),h.focusOut.add(function(){a.KeyboardOpen&&(h.endFocus(),h.inputOptions.zoom&&h.zoomOut())}),h}return __extends(d,c),Object.defineProperty(d.prototype,"width",{get:function(){return this.inputOptions.width},set:function(a){this.inputOptions.width=a,this.box.resize(a),this.textMask.resize(a),this.updateTextAlignment()},enumerable:!0,configurable:!0}),d.prototype.updateTextAlignment=function(){switch(this.inputOptions.textAlign){case"left":this.text.anchor.set(0,0),this.text.x=this.inputOptions.padding,null!==this.placeHolder&&this.placeHolder.anchor.set(0,0),this.cursor.x=this.inputOptions.padding+this.getCaretPosition();break;case"center":this.text.anchor.set(.5,0),this.text.x=this.inputOptions.padding+this.inputOptions.width/2,null!==this.placeHolder&&(this.placeHolder.anchor.set(.5,0),this.placeHolder.x=this.inputOptions.padding+this.inputOptions.width/2),this.cursor.x=this.inputOptions.padding+this.inputOptions.width/2-this.text.width/2+this.getCaretPosition();break;case"right":this.text.anchor.set(1,0),this.text.x=this.inputOptions.padding+this.inputOptions.width,null!==this.placeHolder&&(this.placeHolder.anchor.set(1,0),this.placeHolder.x=this.inputOptions.padding+this.inputOptions.width),this.cursor.x=this.inputOptions.padding+this.inputOptions.width}},d.prototype.checkDown=function(b){if(this.value||this.resetText(),this.input.checkPointerOver(b)){if(this.focus)return void this.setCaretOnclick(b);this.inputOptions.zoom&&!a.Zoomed&&this.zoomIn(),this.startFocus()}else this.focus===!0&&(this.endFocus(),this.inputOptions.zoom&&this.zoomOut())},d.prototype.update=function(){if(this.text.update(),this.placeHolder&&this.placeHolder.update(),this.focus){if(30!==this.cnt)return this.cnt++;this.cursor.visible=this.blink,this.blink=!this.blink,this.cnt=0}},d.prototype.endFocus=function(){var b=this;this.focus&&(this.domElement.removeEventListener(),this.blockInput===!0&&this.domElement.unblockKeyDownEvents(),this.focus=!1,0===this.value.length&&null!==this.placeHolder&&(this.placeHolder.visible=!0),this.cursor.visible=!1,this.game.device.desktop?setTimeout(function(){b.domElement.blur()},0):this.domElement.blur(),this.game.device.desktop||(a.KeyboardOpen=!1,a.onKeyboardClose.dispatch()))},d.prototype.startFocus=function(){var b=this;this.focus=!0,null!==this.placeHolder&&(this.placeHolder.visible=!1),this.game.device.desktop?setTimeout(function(){b.keyUpProcessor()},0):this.keyUpProcessor(),this.game.device.desktop||(a.KeyboardOpen=!0,a.onKeyboardOpen.dispatch())},d.prototype.keyUpProcessor=function(){this.domElement.addKeyUpListener(this.keyListener.bind(this)),this.domElement.focus(),this.blockInput===!0&&this.domElement.blockKeyDownEvents()},d.prototype.updateText=function(){var b="";if(this.inputOptions.type===a.InputType.password)for(var c=0;cparseInt(this.inputOptions.max)?this.value=this.domElement.value=this.inputOptions.max:this.value}else b=this.value;if(this.text.setText(b),this.text.width>this.inputOptions.width)this.text.anchor.x=1,this.text.x=this.inputOptions.padding+this.inputOptions.width;else switch(this.inputOptions.textAlign){case"left":this.text.anchor.set(0,0),this.text.x=this.inputOptions.padding;break;case"center":this.text.anchor.set(.5,0),this.text.x=this.inputOptions.padding+this.inputOptions.width/2;break;case"right":this.text.anchor.set(1,0),this.text.x=this.inputOptions.padding+this.inputOptions.width}},d.prototype.updateCursor=function(){if(this.text.width>this.inputOptions.width||"right"===this.inputOptions.textAlign)this.cursor.x=this.inputOptions.padding+this.inputOptions.width;else switch(this.inputOptions.textAlign){case"left":this.cursor.x=this.inputOptions.padding+this.getCaretPosition();break;case"center":this.cursor.x=this.inputOptions.padding+this.inputOptions.width/2-this.text.width/2+this.getCaretPosition()}},d.prototype.getCaretPosition=function(){var b=this.domElement.getCaretPosition();if(-1===b)return this.text.width;var c=this.value;if(this.inputOptions.type===a.InputType.password){c="";for(var d=0;d=e*c&&(e+1)*c>=b){d=e;break}b>(this.value.length-1)*c&&(d=this.value.length),this.startFocus(),this.domElement.setCaretPosition(d),this.updateCursor()},d.prototype.updateSelection=function(){if(this.domElement.hasSelection){var b=this.value;if(this.inputOptions.type===a.InputType.password){b="";for(var c=0;cwindow.innerWidth?this.windowScale=this.game.width/(1.5*b.width):this.windowScale=this.game.width/2/(1.5*b.width);var c=(this.game.width-1.5*b.width)/2/this.windowScale;this.game.world.scale.set(this.game.world.scale.x*this.windowScale,this.game.world.scale.y*this.windowScale),this.game.world.pivot.set(b.x-c,b.y-2*this.inputOptions.padding),a.Zoomed=!0}},d.prototype.zoomOut=function(){a.Zoomed&&(this.game.world.scale.set(this.game.world.scale.x/this.windowScale,this.game.world.scale.y/this.windowScale),this.game.world.pivot.set(0,0),a.Zoomed=!1)},d.prototype.keyListener=function(a){return this.value=this.getFormattedText(this.domElement.value),13===a.keyCode?void(this.focusOutOnEnter&&this.endFocus()):(this.updateText(),this.updateCursor(),this.updateSelection(),void a.preventDefault())},d.prototype.destroy=function(a){this.game.input.onDown.remove(this.checkDown,this),this.focusIn.removeAll(),this.focusOut.removeAll(),this.domElement.destroy(),c.prototype.destroy.call(this,a)},d.prototype.resetText=function(){this.setText()},d.prototype.setText=function(a){void 0===a&&(a=""),null!==this.placeHolder&&(a.length>0?this.placeHolder.visible=!1:this.placeHolder.visible=!0),this.value=this.getFormattedText(a),this.domElement.value=this.value,this.updateText(),this.updateCursor(),this.endFocus()},d.prototype.getFormattedText=function(a){switch(this.inputOptions.forceCase){default:case b.none:return a;case b.lower:return a.toLowerCase();case b.upper:return a.toUpperCase()}},d}(Phaser.Sprite);a.InputField=c}(PhaserInput||(PhaserInput={}));var PhaserInput;!function(a){var b=function(a){function b(b,c){var d=a.call(this,b,0,0)||this;d.bgColor=c.backgroundColor?parseInt(c.backgroundColor.slice(1),16):16777215,d.borderRadius=c.borderRadius||0,d.borderWidth=c.borderWidth||1,d.borderColor=c.borderColor?parseInt(c.borderColor.slice(1),16):9803157,d.boxAlpha=c.fillAlpha,d.padding=c.padding;var e,e=c.height,f=c.width;c.font&&(e=Math.max(parseInt(c.font.substr(0,c.font.indexOf("px")),10),e)),d.boxHeight=2*d.padding+e;var f=c.width;return d.boxWidth=2*d.padding+f,d.drawBox(),d}return __extends(b,a),b.prototype.resize=function(a){this.boxWidth=2*this.padding+a,this.drawBox()},b.prototype.drawBox=function(){this.clear().beginFill(this.bgColor,this.boxAlpha).lineStyle(this.borderWidth,this.borderColor,this.boxAlpha),this.borderRadius>0?this.drawRoundedRect(0,0,this.boxWidth,this.boxHeight,this.borderRadius):this.drawRect(0,0,this.boxWidth,this.boxHeight)},b}(Phaser.Graphics);a.InputBox=b}(PhaserInput||(PhaserInput={}));var PhaserInput;!function(a){var b=function(a){function b(b,c){var d=a.call(this,b,c.padding,c.padding)||this;return d.inputOptions=c,d}return __extends(b,a),b.prototype.updateSelection=function(a){var c=Phaser.Color.webToColor(this.inputOptions.selectionColor);this.clear(),this.beginFill(b.rgb2hex(c),c.a),this.drawRect(a.x,a.y,a.width,a.height-this.inputOptions.padding)},b.rgb2hex=function(a){return parseInt(("0"+a.r.toString(16)).slice(-2)+("0"+a.g.toString(16)).slice(-2)+("0"+a.b.toString(16)).slice(-2),16)},b}(Phaser.Graphics);a.SelectionHighlight=b}(PhaserInput||(PhaserInput={}));var PhaserInput;!function(a){var b=function(a){function b(b,c){var d=a.call(this,b,c.padding,c.padding)||this,e=c.height;return c.font&&(e=Math.max(parseInt(c.font.substr(0,c.font.indexOf("px")),10),e)),d.maskWidth=c.width,d.maskHeight=1.3*e,d.drawMask(),d}return __extends(b,a),b.prototype.resize=function(a){this.maskWidth=a,this.drawMask()},b.prototype.drawMask=function(){this.clear().beginFill(0).drawRect(0,0,this.maskWidth,this.maskHeight).endFill()},b}(Phaser.Graphics);a.TextMask=b}(PhaserInput||(PhaserInput={}));var PhaserInput;!function(a){a.Zoomed=!1,a.KeyboardOpen=!1,a.onKeyboardOpen=new Phaser.Signal,a.onKeyboardClose=new Phaser.Signal;var b=function(b){function c(a,c){var d=b.call(this,a,c)||this;return d.addInputFieldFactory(),d}return __extends(c,b),c.prototype.addInputFieldFactory=function(){Phaser.GameObjectFactory.prototype.inputField=function(b,c,d,e){void 0===e&&(e=this.world);var f=new a.InputField(this.game,b,c,d);return e.add(f)},Phaser.GameObjectCreator.prototype.inputField=function(b,c,d){return new a.InputField(this.game,b,c,d)}},c}(Phaser.Plugin);b.Zoomed=!1,b.KeyboardOpen=!1,b.onKeyboardOpen=new Phaser.Signal,b.onKeyboardClose=new Phaser.Signal,a.Plugin=b}(PhaserInput||(PhaserInput={})); -------------------------------------------------------------------------------- /static/js/player.js: -------------------------------------------------------------------------------- 1 | PG.createPlay = function (seat, game) { 2 | var player = seat == 0 ? new PG.Player(seat, game) : new PG.NetPlayer(seat, game); 3 | var xy = [ 4 | PG.PW / 2, game.world.height - PG.PH - 10, 5 | game.world.width - PG.PW / 2, 94, 6 | PG.PW / 2, 94 7 | ]; 8 | player.initUI(xy[seat * 2], xy[seat * 2 + 1]); 9 | if (seat == 0) { 10 | player.initShotLayer(); 11 | } else if (seat == 1) { 12 | player.uiHead.scale.set(-1, 1); 13 | } 14 | return player; 15 | }; 16 | 17 | PG.Player = function (seat, game) { 18 | this.uid = seat; 19 | this.seat = seat; 20 | this.game = game; 21 | 22 | this.pokerInHand = []; 23 | this._pokerPic = {}; 24 | this.isLandlord = false; 25 | 26 | this.hintPoker = []; 27 | this.isDraging = false; 28 | }; 29 | 30 | PG.Player.prototype.initUI = function (sx, sy) { 31 | this.uiHead = this.game.add.sprite(sx, sy, 'btn', 'icon_default.png'); 32 | this.uiHead.anchor.set(0.5, 1); 33 | }; 34 | 35 | PG.Player.prototype.updateInfo = function (uid, name) { 36 | this.uid = uid; 37 | if (uid == -1) { 38 | this.uiHead.frameName = 'icon_default.png'; 39 | } else { 40 | this.uiHead.frameName = 'icon_farmer.png'; 41 | } 42 | }; 43 | 44 | PG.Player.prototype.cleanPokers = function () { 45 | 46 | var length = this.pokerInHand.length; 47 | for (var i = 0; i < length; i++) { 48 | var pid = this.pokerInHand[i]; 49 | var p = this.findAPoker(pid); 50 | p.kill(); 51 | } 52 | this.pokerInHand = []; 53 | } 54 | 55 | PG.Player.prototype.initShotLayer = function () { 56 | this.shotLayer = this.game.add.group(); 57 | var group = this.shotLayer; 58 | 59 | var sy = this.game.world.height * 0.6; 60 | var pass = this.game.make.button(0, sy, "btn", this.onPass, this, 'pass.png', 'pass.png', 'pass.png'); 61 | pass.anchor.set(0.5, 0); 62 | group.add(pass); 63 | var hint = this.game.make.button(0, sy, "btn", this.onHint, this, 'hint.png', 'hint.png', 'hint.png'); 64 | hint.anchor.set(0.5, 0); 65 | group.add(hint); 66 | var shot = this.game.make.button(0, sy, "btn", this.onShot, this, 'shot.png', 'shot.png', 'shot.png'); 67 | shot.anchor.set(0.5, 0); 68 | group.add(shot); 69 | 70 | group.forEach(function (child) { 71 | child.kill(); 72 | }); 73 | }; 74 | 75 | PG.Player.prototype.setLandlord = function () { 76 | this.isLandlord = true; 77 | this.uiHead.frameName = 'icon_landlord.png'; 78 | }; 79 | 80 | PG.Player.prototype.say = function (str) { 81 | 82 | var style = {font: "22px Arial", fill: "#ffffff", align: "center"}; 83 | var sx = this.uiHead.x + this.uiHead.width / 2 + 10; 84 | var sy = this.uiHead.y - this.uiHead.height * 0.5; 85 | var text = this.game.add.text(sx, sy, str, style); 86 | if (this.uiHead.scale.x == -1) { 87 | text.x = text.x - text.width - 10; 88 | } 89 | this.game.time.events.add(2000, text.destroy, text); 90 | }; 91 | 92 | PG.Player.prototype.onInputDown = function (poker, pointer) { 93 | this.isDraging = true; 94 | this.onSelectPoker(poker, pointer); 95 | }; 96 | 97 | PG.Player.prototype.onInputUp = function (poker, pointer) { 98 | this.isDraging = false; 99 | //this.onSelectPoker(poker, pointer); 100 | }; 101 | 102 | PG.Player.prototype.onInputOver = function (poker, pointer) { 103 | if (this.isDraging) { 104 | this.onSelectPoker(poker, pointer); 105 | } 106 | }; 107 | 108 | PG.Player.prototype.onSelectPoker = function (poker, pointer) { 109 | var index = this.hintPoker.indexOf(poker.id); 110 | if (index == -1) { 111 | poker.y = this.game.world.height - PG.PH * 0.8; 112 | this.hintPoker.push(poker.id); 113 | } else { 114 | poker.y = this.game.world.height - PG.PH * 0.5; 115 | this.hintPoker.splice(index, 1); 116 | } 117 | }; 118 | 119 | PG.Player.prototype.onPass = function (btn) { 120 | this.game.finishPlay([]); 121 | this.pokerUnSelected(this.hintPoker); 122 | this.hintPoker = []; 123 | btn.parent.forEach(function (child) { 124 | child.kill(); 125 | }); 126 | }; 127 | 128 | PG.Player.prototype.onHint = function (btn) { 129 | if (this.hintPoker.length == 0) { 130 | this.hintPoker = this.lastTurnPoker; 131 | } else { 132 | this.pokerUnSelected(this.hintPoker); 133 | if (this.lastTurnPoker.length > 0 && !PG.Poker.canCompare(this.hintPoker, this.lastTurnPoker)) { 134 | this.hintPoker = []; 135 | } 136 | } 137 | var bigger = this.hint(this.hintPoker); 138 | if (bigger.length == 0) { 139 | if (this.hintPoker == this.lastTurnPoker) { 140 | this.say("没有能大过的牌"); 141 | } else { 142 | this.pokerUnSelected(this.hintPoker); 143 | } 144 | } else { 145 | this.pokerSelected(bigger); 146 | } 147 | this.hintPoker = bigger; 148 | }; 149 | 150 | PG.Player.prototype.onShot = function (btn) { 151 | if (this.hintPoker.length == 0) { 152 | return; 153 | } 154 | var code = this.canPlay(this.game.isLastShotPlayer() ? [] : this.game.tablePoker, this.hintPoker); 155 | if (code) { 156 | this.say(code); 157 | return; 158 | } 159 | this.game.finishPlay(this.hintPoker); 160 | this.hintPoker = []; 161 | btn.parent.forEach(function (child) { 162 | child.kill(); 163 | }); 164 | }; 165 | 166 | 167 | PG.Player.prototype.hint = function (lastTurnPoker) { 168 | var cards; 169 | var handCards = PG.Poker.toCards(this.pokerInHand); 170 | if (lastTurnPoker.length === 0) { 171 | cards = PG.Rule.bestShot(handCards); 172 | } else { 173 | cards = PG.Rule.cardsAbove(handCards, PG.Poker.toCards(lastTurnPoker)); 174 | } 175 | 176 | return PG.Poker.toPokers(this.pokerInHand, cards); 177 | }; 178 | 179 | PG.Player.prototype.canPlay = function (lastTurnPoker, shotPoker) { 180 | var cardsA = PG.Poker.toCards(shotPoker); 181 | var valueA = PG.Rule.cardsValue(cardsA); 182 | if (!valueA[0]){ 183 | return '出牌不合法'; 184 | } 185 | var cardsB = PG.Poker.toCards(lastTurnPoker); 186 | if (cardsB.length == 0) { 187 | return ''; 188 | } 189 | var valueB = PG.Rule.cardsValue(cardsB); 190 | if (valueA[0] != valueB[0] && valueA[1] < 1000) { 191 | return '出牌类型跟上家不一致'; 192 | } 193 | 194 | if (valueA[1] > valueB[1]) { 195 | return ''; 196 | } 197 | return '出牌需要大于上家'; 198 | }; 199 | 200 | PG.Player.prototype.playPoker = function (lastTurnPoker) { 201 | this.lastTurnPoker = lastTurnPoker; 202 | 203 | var group = this.shotLayer; 204 | var step = this.game.world.width / 6; 205 | var sx = this.game.world.width / 2 - 0.5 * step; 206 | if (!this.game.isLastShotPlayer()) { 207 | sx -= 0.5 * step; 208 | var pass = group.getAt(0); 209 | pass.centerX = sx; 210 | sx += step; 211 | pass.revive(); 212 | } 213 | var hint = group.getAt(1); 214 | hint.centerX = sx; 215 | hint.revive(); 216 | var shot = group.getAt(2); 217 | shot.centerX = sx + step; 218 | shot.revive(); 219 | 220 | this.enableInput(); 221 | }; 222 | 223 | PG.Player.prototype.sortPoker = function () { 224 | this.pokerInHand.sort(PG.Poker.comparePoker); 225 | }; 226 | 227 | PG.Player.prototype.dealPoker = function () { 228 | this.sortPoker(); 229 | var length = this.pokerInHand.length; 230 | for (var i = 0; i < length; i++) { 231 | var pid = this.pokerInHand[i]; 232 | var p = new PG.Poker(this.game, pid, pid); 233 | this.game.world.add(p); 234 | this.pushAPoker(p); 235 | this.dealPokerAnim(p, i); 236 | } 237 | }; 238 | 239 | PG.Player.prototype.dealPokerAnim = function (p, i) { 240 | //to(properties, duration, ease, autoStart, delay, repeat, yoyo) 241 | this.game.add.tween(p).to({ 242 | x: this.game.world.width / 2 + PG.PW * 0.44 * (i - 8.5), 243 | y: this.game.world.height - PG.PH / 2 244 | }, 500, Phaser.Easing.Default, true, 50 * i); 245 | }; 246 | 247 | PG.Player.prototype.arrangePoker = function () { 248 | var count = this.pokerInHand.length; 249 | var gap = Math.min(this.game.world.width / count, PG.PW * 0.44); 250 | for (var i = 0; i < count; i++) { 251 | var pid = this.pokerInHand[i]; 252 | var p = this.findAPoker(pid); 253 | p.bringToTop(); 254 | this.game.add.tween(p).to({x: this.game.world.width / 2 + (i - count / 2) * gap}, 600, Phaser.Easing.Default, true); 255 | } 256 | }; 257 | 258 | PG.Player.prototype.pushAPoker = function (poker) { 259 | this._pokerPic[poker.id] = poker; 260 | 261 | poker.events.onInputDown.add(this.onInputDown, this); 262 | poker.events.onInputUp.add(this.onInputUp, this); 263 | poker.events.onInputOver.add(this.onInputOver, this); 264 | }; 265 | 266 | PG.Player.prototype.removeAPoker = function (pid) { 267 | var length = this.pokerInHand.length; 268 | for (var i = 0; i < length; i++) { 269 | if (this.pokerInHand[i] === pid) { 270 | this.pokerInHand.splice(i, 1); 271 | delete this._pokerPic[pid]; 272 | return; 273 | } 274 | } 275 | console.log('Error: REMOVE POKER ', pid); 276 | }; 277 | 278 | PG.Player.prototype.removeAllPoker = function () { 279 | var length = this.pokerInHand.length; 280 | for (var i = 0; i < length; i++) { 281 | this.pokerInHand.splice(i, 1); 282 | delete this._pokerPic[pid]; 283 | } 284 | console.log('Error: REMOVE POKER ', pid); 285 | }; 286 | 287 | 288 | PG.Player.prototype.findAPoker = function (pid) { 289 | var poker = this._pokerPic[pid]; 290 | if (poker === undefined) { 291 | console.log('Error: FIND POKER ', pid); 292 | } 293 | return poker; 294 | }; 295 | 296 | PG.Player.prototype.enableInput = function () { 297 | var length = this.pokerInHand.length; 298 | for (var i = 0; i < length; i++) { 299 | var p = this.findAPoker(this.pokerInHand[i]); 300 | p.inputEnabled = true; 301 | } 302 | }; 303 | 304 | PG.Player.prototype.pokerSelected = function (pokers) { 305 | for (var i = 0; i < pokers.length; i++) { 306 | var p = this.findAPoker(pokers[i]); 307 | p.y = this.game.world.height - PG.PH * 0.8; 308 | } 309 | }; 310 | 311 | PG.Player.prototype.pokerUnSelected = function (pokers) { 312 | for (var i = 0; i < pokers.length; i++) { 313 | var p = this.findAPoker(pokers[i]); 314 | p.y = this.game.world.height - PG.PH / 2; 315 | } 316 | }; 317 | 318 | -------------------------------------------------------------------------------- /static/js/player_net.js: -------------------------------------------------------------------------------- 1 | PG.NetPlayer = function (seat, game) { 2 | PG.Player.call(this, seat, game); 3 | this._pokerPic = []; 4 | }; 5 | 6 | PG.NetPlayer.prototype = Object.create(PG.Player.prototype); 7 | PG.NetPlayer.prototype.constructor = PG.NetPlayer; 8 | 9 | PG.NetPlayer.prototype.pushAPoker = function (poker) { 10 | this._pokerPic.push(poker); 11 | this.updateLeftPoker(); 12 | }; 13 | 14 | PG.NetPlayer.prototype.removeAPoker = function (pid) { 15 | for (var i = this.pokerInHand.length - 1; i >= 0; i--) { 16 | if (this.pokerInHand[i] === pid) { 17 | this.pokerInHand.splice(i, 1); 18 | break 19 | } 20 | } 21 | if (i == -1) { 22 | this.pokerInHand.pop(); 23 | } 24 | for (var i = this._pokerPic.length - 1; i >= 0; i--) { 25 | if (this._pokerPic[i].id === pid) { 26 | this._pokerPic.splice(i, 1); 27 | break 28 | } 29 | } 30 | if (i == -1) { 31 | this._pokerPic.pop(); 32 | } 33 | this.updateLeftPoker(); 34 | }; 35 | 36 | PG.NetPlayer.prototype.arrangePoker = function () { 37 | if (this.pokerInHand.length > 0 && this.pokerInHand[0] < 54) { 38 | // PG.Player.prototype.arrangePoker.call(this); 39 | this.reDealPoker(); 40 | } 41 | }; 42 | 43 | PG.NetPlayer.prototype.replacePoker = function (pokers, start) { 44 | if (this.pokerInHand.length !== pokers.length - start) { 45 | console.log("ERROR ReplacePoker:", this.pokerInHand, pokers); 46 | } 47 | if (this._pokerPic.length !== pokers.length - start) { 48 | console.log("ERROR ReplacePoker:", this._pokerPic, pokers); 49 | } 50 | var length = this.pokerInHand.length; 51 | for (var i = 0; i < length; i++) { 52 | this.pokerInHand[i] = pokers[start + i]; 53 | this._pokerPic[i].id = pokers[start + i]; 54 | this._pokerPic[i].frame = pokers[start + i]; 55 | } 56 | }; 57 | 58 | PG.NetPlayer.prototype.findAPoker = function (pid) { 59 | for (var i = this._pokerPic.length - 1; i >= 0; i--) { 60 | if (this._pokerPic[i].id == pid) { 61 | return this._pokerPic[i]; 62 | } 63 | } 64 | return this._pokerPic[this._pokerPic.length - 1]; 65 | }; 66 | 67 | PG.NetPlayer.prototype.reDealPoker = function () { 68 | this.sortPoker(); 69 | var length = this.pokerInHand.length; 70 | for (var i = 0; i < length; i++) { 71 | var pid = this.pokerInHand[i]; 72 | var p = this.findAPoker(pid); 73 | p.bringToTop(); 74 | this.dealPokerAnim(p, this.seat == 1 ? length-1-i : i); 75 | } 76 | }; 77 | 78 | PG.NetPlayer.prototype.cleanPokers = function () { 79 | 80 | var length = this.pokerInHand.length; 81 | for (var i = 0; i < length; i++) { 82 | var pid = this.pokerInHand[i]; 83 | var p = this.findAPoker(pid); 84 | p.kill(); 85 | } 86 | this.pokerInHand = []; 87 | } 88 | 89 | PG.NetPlayer.prototype.dealPokerAnim = function (p, i) { 90 | var width = this.game.world.width; 91 | if (p.id > 53) { 92 | this.game.add.tween(p).to({ 93 | x: this.seat == 1 ? width - PG.PW/2 : PG.PW/2, 94 | y: this.seat == 1 ? this.uiHead.y + PG.PH/2 + 10 : this.uiHead.y + PG.PH/2 + 10 95 | }, 500, Phaser.Easing.Default, true, 25 + 50 * i); 96 | } else { 97 | this.game.add.tween(p).to({ 98 | x: this.seat == 1 ? (width - PG.PW/2) - (i * PG.PW * 0.44) : PG.PW/2 + i * PG.PW * 0.44, 99 | y: this.seat == 1 ? this.uiHead.y + PG.PH/2 + 10 : this.uiHead.y + PG.PH * 1.5 + 20 100 | }, 500, Phaser.Easing.Default, true, 50 * i); 101 | } 102 | }; 103 | 104 | PG.NetPlayer.prototype.initUI = function (sx, sy) { 105 | PG.Player.prototype.initUI.call(this, sx, sy); 106 | var style = {font: "22px Arial", fill: "#ffffff", align: "center"}; 107 | this.uiLeftPoker = this.game.add.text(sx, sy + PG.PH + 10, '17', style); 108 | this.uiLeftPoker.anchor.set(0.5, 0); 109 | this.uiLeftPoker.kill(); 110 | 111 | var style = {font: "20px Arial", fill: "#c8c8c8", align: "center"}; 112 | if (this.seat == 1) { 113 | this.uiName = this.game.add.text(sx - 40, sy - 80, '等待玩家加入', style); 114 | this.uiName.anchor.set(1, 0); 115 | } else { 116 | this.uiName = this.game.add.text(sx + 40, sy - 80, '等待玩家加入', style); 117 | this.uiName.anchor.set(0, 0); 118 | } 119 | }; 120 | 121 | 122 | PG.NetPlayer.prototype.updateInfo = function (uid, name) { 123 | PG.Player.prototype.updateInfo.call(this, uid, name); 124 | if (uid == -1) { 125 | this.uiName.text = '等待玩家加入'; 126 | } else { 127 | this.uiName.text = name; 128 | } 129 | }; 130 | 131 | PG.NetPlayer.prototype.updateLeftPoker = function () { 132 | var len = this.pokerInHand.length; 133 | if (len > 0) { 134 | this.uiLeftPoker.text = "" + this.pokerInHand.length; 135 | this.uiLeftPoker.revive(); 136 | } else { 137 | this.uiLeftPoker.kill(); 138 | } 139 | }; 140 | -------------------------------------------------------------------------------- /static/js/rule.js: -------------------------------------------------------------------------------- 1 | PG.Poker = function (game, id, frame) { 2 | 3 | Phaser.Sprite.call(this, game, game.world.width / 2, game.world.height * 0.4, 'poker', frame); 4 | 5 | this.anchor.set(0.5); 6 | this.id = id; 7 | 8 | return this; 9 | }; 10 | 11 | PG.Poker.prototype = Object.create(Phaser.Sprite.prototype); 12 | PG.Poker.prototype.constructor = PG.Poker; 13 | 14 | PG.Poker.comparePoker = function (a, b) { 15 | 16 | if (a instanceof Array) { 17 | a = a[0]; 18 | b = b[0]; 19 | } 20 | 21 | 22 | if (a >= 52 || b >= 52) { 23 | return -(a - b); 24 | } 25 | a = a % 13; 26 | b = b % 13; 27 | if (a == 1 || a == 0) { 28 | a += 13; 29 | } 30 | if (b == 1 || b == 0) { 31 | b += 13; 32 | } 33 | return -(a - b); 34 | }; 35 | 36 | PG.Poker.toCards = function (pokers) { 37 | var cards = []; 38 | for (var i = 0; i < pokers.length; i++) { 39 | var pid = pokers[i]; 40 | if (pid instanceof Array) { 41 | pid = pid[0]; 42 | } 43 | if (pid == 52) { 44 | cards.push('W'); 45 | } else if (pid == 53) { 46 | cards.push('w'); 47 | } else { 48 | cards.push("A234567890JQK"[pid % 13]); 49 | } 50 | } 51 | return cards; 52 | 53 | }; 54 | 55 | PG.Poker.canCompare = function (pokersA, pokersB) { 56 | var cardsA = this.toCards(pokersA); 57 | var cardsB = this.toCards(pokersB); 58 | return PG.Rule.cardsValue(cardsA)[0] == PG.Rule.cardsValue(cardsB)[0]; 59 | }; 60 | 61 | PG.Poker.toPokers = function (pokerInHands, cards) { 62 | var pokers = []; 63 | for (var i = 0; i < cards.length; i++) { 64 | var candidates = this.toPoker(cards[i]); 65 | for (var j = 0; j < candidates.length; j++) { 66 | if (pokerInHands.indexOf(candidates[j]) != -1 && pokers.indexOf(candidates[j]) == -1) { 67 | pokers.push(candidates[j]); 68 | break 69 | } 70 | } 71 | } 72 | return pokers; 73 | }; 74 | 75 | PG.Poker.toPoker = function (card) { 76 | 77 | var cards = "A234567890JQK"; 78 | for (var i = 0; i < 13; i++) { 79 | if (card == cards[i]) { 80 | return [i, i + 13, i + 13 * 2, i + 13 * 3]; 81 | } 82 | } 83 | if (card == 'W') { 84 | return [52]; 85 | } else if (card == 'w') { 86 | return [53]; 87 | } 88 | return [54]; 89 | 90 | }; 91 | 92 | PG.Rule = {}; 93 | 94 | PG.Rule.cardsAbove = function (handCards, turnCards) { 95 | 96 | var turnValue = this.cardsValue(turnCards); 97 | if (turnValue[0] == '') { 98 | return ''; 99 | } 100 | handCards.sort(this.sorter); 101 | var oneRule = PG.RuleList[turnValue[0]]; 102 | for (var i = turnValue[1] + 1; i < oneRule.length; i++) { 103 | if (this.containsAll(handCards, oneRule[i])) { 104 | return oneRule[i]; 105 | } 106 | } 107 | 108 | if (turnValue[1] < 1000) { 109 | oneRule = PG.RuleList['bomb']; 110 | for (var i = 0; i < oneRule.length; i++) { 111 | if (this.containsAll(handCards, oneRule[i])) { 112 | return oneRule[i]; 113 | } 114 | } 115 | if (this.containsAll(handCards, 'wW')) { 116 | return 'wW'; 117 | } 118 | } 119 | 120 | return ''; 121 | }; 122 | 123 | PG.Rule.bestShot = function (handCards) { 124 | 125 | handCards.sort(this.sorter); 126 | var shot = ''; 127 | var len = this._CardsType.length; 128 | for (var i = 2; i < len; i++) { 129 | var oneRule = PG.RuleList[this._CardsType[i]]; 130 | for (var j = 0; j < oneRule.length; j++) { 131 | if (oneRule[j].length > shot.length && this.containsAll(handCards, oneRule[j])) { 132 | shot = oneRule[j]; 133 | } 134 | } 135 | } 136 | 137 | if (shot == '') { 138 | oneRule = PG.RuleList['bomb']; 139 | for (var i = 0; i < oneRule.length; i++) { 140 | if (this.containsAll(handCards, oneRule[i])) { 141 | return oneRule[i]; 142 | } 143 | } 144 | if (this.containsAll(handCards, 'wW')) 145 | return 'wW'; 146 | } 147 | 148 | return shot; 149 | }; 150 | 151 | PG.Rule._CardsType = [ 152 | 'rocket', 'bomb', 153 | 'single', 'pair', 'trio', 'trio_pair', 'trio_single', 154 | 'seq_single5', 'seq_single6', 'seq_single7', 'seq_single8', 'seq_single9', 'seq_single10', 'seq_single11', 'seq_single12', 155 | 'seq_pair3', 'seq_pair4', 'seq_pair5', 'seq_pair6', 'seq_pair7', 'seq_pair8', 'seq_pair9', 'seq_pair10', 156 | 'seq_trio2', 'seq_trio3', 'seq_trio4', 'seq_trio5', 'seq_trio6', 157 | 'seq_trio_pair2', 'seq_trio_pair3', 'seq_trio_pair4', 'seq_trio_pair5', 158 | 'seq_trio_single2', 'seq_trio_single3', 'seq_trio_single4', 'seq_trio_single5', 159 | 'bomb_pair', 'bomb_single']; 160 | 161 | PG.Rule.sorter = function (a, b) { 162 | var card_str = '34567890JQKA2wW'; 163 | return card_str.indexOf(a) - card_str.indexOf(b); 164 | }; 165 | 166 | PG.Rule.index_of = function (array, ele) { 167 | if (array[0].length != ele.length) { 168 | return -1; 169 | } 170 | for (var i = 0, l = array.length; i < l; i++) { 171 | if (array[i] == ele) { 172 | return i; 173 | } 174 | } 175 | return -1; 176 | }; 177 | 178 | PG.Rule.containsAll = function (parent, child) { 179 | var index = 0; 180 | for (var i = 0, l = child.length; i < l; i++) { 181 | index = parent.indexOf(child[i], index); 182 | if (index == -1) { 183 | return false; 184 | } 185 | index += 1; 186 | } 187 | return true; 188 | }; 189 | 190 | PG.Rule.cardsValue = function (cards) { 191 | 192 | if (typeof(cards) != 'string') { 193 | cards.sort(this.sorter); 194 | cards = cards.join(''); 195 | } 196 | 197 | if (cards == 'wW') 198 | return ['rocket', 2000]; 199 | 200 | var index = this.index_of(PG.RuleList['bomb'], cards); 201 | if (index >= 0) 202 | return ['bomb', 1000 + index]; 203 | 204 | var length = this._CardsType.length; 205 | for (var i = 2; i < length; i++) { 206 | var typeName = this._CardsType[i]; 207 | var index = this.index_of(PG.RuleList[typeName], cards); 208 | if (index >= 0) 209 | return [typeName, index]; 210 | } 211 | console.log('Error: UNKNOWN TYPE ', cards); 212 | return ['', 0]; 213 | }; 214 | 215 | PG.Rule.compare = function (cardsA, cardsB) { 216 | 217 | if (cardsA.length == 0 && cardsB.length == 0) { 218 | return 0; 219 | } 220 | if (cardsA.length == 0) { 221 | return -1; 222 | } 223 | if (cardsB.length == 0) { 224 | return 1; 225 | } 226 | 227 | var valueA = this.cardsValue(cardsA); 228 | var valueB = this.cardsValue(cardsB); 229 | 230 | if ((valueA[1] < 1000 && valueB[1] < 1000) && (valueA[0] != valueB[0])) { 231 | console.log('Error: Compare ', cardsA, cardsB); 232 | } 233 | 234 | return valueA[1] - valueB[1]; 235 | }; 236 | 237 | PG.Rule.shufflePoker = function () { 238 | var pokers = []; 239 | for (var i = 0; i < 54; i++) { 240 | pokers.push(i); 241 | } 242 | 243 | var currentIndex = pokers.length, temporaryValue, randomIndex; 244 | while (0 != currentIndex) { 245 | randomIndex = Math.floor(Math.random() * currentIndex); 246 | currentIndex -= 1; 247 | 248 | temporaryValue = pokers[currentIndex]; 249 | pokers[currentIndex] = pokers[randomIndex]; 250 | pokers[randomIndex] = temporaryValue; 251 | } 252 | return pokers; 253 | } 254 | ; 255 | 256 | -------------------------------------------------------------------------------- /static/s/bg3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwg255/landlord/a0d77dedbc3efb73b044678ba6da1c7a65f939fe/static/s/bg3.ogg -------------------------------------------------------------------------------- /templates/poker.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 斗地主 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 | 35 | 36 | 37 | --------------------------------------------------------------------------------