2017年就这么过去了,新的一年又到了。写这个博客估计是我坚持的最长时间的事情了,这应该归功于github,因为github的这个博客使用起来真的可以说是非常的方便简单。我之前在这博客里面一直都是只记录一些计算机相关的知识点,其中还有不少更是把代码一贴上去就不管了,只要自己看得懂就好了,没有别的原因,就是太懒了。今年我想要做些改变,在这上面写一些文字,记录下自己的成长过程。如果是技术点的话,尽可能写的通俗易懂一些。
首先要做下2017年的总结。在过去的一年里,我从厦门来到了深圳,3月份末尾来的。来到深圳的第一个星期就来上班,现在想想当时真的有点太累了。1、2月份的时候,那个时候我还在厦门帮忙做最后一个项目,我负责了客户端的C语言代码,是跑在单片机上的,挺好玩的。其实厦门那家公司挺好的,就是工资太低了。来了深圳之后进了一个游戏公司,现在还在这个公司,做棋牌游戏的。3月份刚进去那会,服务器还是用C++做的,我被安排去开发一款兰州麻将,我只想说我不会打麻将-_-。那个时候公司还挺乱的,无论是代码还是管理流程,没少加班,有段时间经常通宵到早上。不过我们的技术BOSS对我们还是不错的。这个项目持续了好几个月的时间。
中间有段时间闲了下来,就开始学习Golang,Golang今年在国内也特别的火。 然后现在改用Golang做服务器了,简单很多,写起来也很舒服,3周的时间,两个麻将的服务器逻辑就全部OK了。
2018年已经到了。我在这里给自己定了几个目标:
坚持写博客,还要写好。
坚持听“得到APP”的老师讲课。(很不错)
学习金融理财相关的知识。
学习新的技术就不列了,因为这是做IT必须具备的技能。
在这3个目标中,其实后面两个和我的本职工作并没有什么关系。但是前段时间,华为开除40出头程序员,中兴的40出头程序员跳楼,这些事情给我敲响了一个警钟。我只是希望我到了那个年龄的时候,能有更多回旋的余地。
另外,为什么需要把目标写在这里?因为有人做过调查,发现把目标写清楚,如果达到目标的方法写清楚,成功的几率会比只是在自己心里面想想要大得多。而我想要在这新的一年里面改变自己。加油!
点数
每张牌都有一个对应的点数
1-9的万、条、筒对应1-9点,字牌为10点。
吃碰杠
可以碰、杠,不可以吃。
庄家
第一局随机选庄。赢家坐庄。荒庄后庄家不变。
漏碰
可以碰牌的时候不碰,则在自己摸牌前不可以再碰这张牌。
但是对其他牌没有影响,如果漏碰了3万,那么对4万是不会有影响的。
杠牌
明杠:分为摸杠和点杠。
摸杠:先碰,后杠。
点杠:手上有3张一样的牌,其他玩家打出一样牌,选择开杠。
暗杠:手上有4张一样的牌,选择开杠,为暗杠。
抢杠:被抢杠胡算点炮,抢杠只能抢自摸明杠,被抢的杠就不再是杠。
杠上花:杠后补的牌刚好是自己要胡的那张牌,算自摸。
听牌
听牌的时候,听的牌中,至少要有一张牌是大于等于6点。
听牌后不能换牌。
听牌时打出去的这张牌不显示牌面,此牌也不可以被其他玩家碰杠胡。
听牌后自动摸打。
听牌后杠牌
听牌后仍然可以杠牌,即使杠后听的牌改变了也没有关系,只要杠后听的牌中至少有一张是大于等于6点就行。
胡牌规则
1、一局只能有一位胡牌者,如果点炮的时候有多家可胡,则逆时针方向离点炮者最近的优先胡牌。
2、牌的点数为1、2的不能胡牌,点数为3、4、5的只能自摸,点数为6、7、8、9、10的可以接炮胡和自摸胡。
例:
牌型
平胡: 1分
七对: 2分
豪华七对: 4分
清一色: 2分
一条龙: 2分
十三幺: 2分
胡牌点数:胡的那张牌的点数。
总分= 胡分 + 杠分
胡牌得分 = (胡牌点数 + 庄闲(庄5、闲0)) x 胡牌方式(接炮胡1,自摸2) x 牌型分
如果同时满足多个牌型,算分时只取其中分数最大的那个,但是界面上需要显示多个牌型。
胡牌算分
未听牌者点炮:只有点炮者输分
赢家得分 = (公式) x (人数 - 1) 输家1得分 = - (公式) x (人数 - 1) 输家2得分 = 0 输家3得分 = 0
听牌者点炮:3家都要输分
赢家得分 = (公式) + (公式) +(公式) 输家1得分 = -(公式) 输家2得分 = -(公式) 输家3得分 = -(公式)
自摸胡牌:3家都要输分
赢家得分 = (公式) + (公式) +(公式) 输家1得分 = -(公式) 输家2得分 = -(公式) 输家3得分 = -(公式)
杠牌算分
摸杠: 9 + 9 + 9, 赢27分,3家输分,每家输9分。
点杠:
未听牌者: 9x3=27,赢27分,1家输分,输27分。
听牌者: 9 + 9 + 9, 赢27分,3家输分,每家输9分。
暗杠: 9x2 + 9x2 + 9x2, 赢54分,3家输分,每家输18分。
因为暗杠的分数要翻倍。
每个玩家都需要统计
自摸次数:统计自摸的次数
接炮次数:统计接炮胡和抢杠胡的次数
点炮次数:统计点炮和被抢杠胡的次数
暗杠次数:统计暗杠次数
明杠次数:统计明杠次数
字牌只能是刻子。
总分 = 胡牌分 + 杠分
参考链接:
http://hp.vector.co.jp/authors/VA046927/mjscore/mjalgorism.html
http://hp.vector.co.jp/authors/VA046927/mjscore/AgariIndex.java
http://hp.vector.co.jp/authors/VA046927/mjscore/AgariBacktrack.java
核心思想:
9张万,9张条,9张饼,4张风,3张箭,一共34种牌。
那么34种牌就至少需要6bit才能够全部保存得下。(1 2 4 8 16 32 64 …)
麻将的手牌有14张,14张 * 6bit = 84bit
麻将的所有组合大约有1700万种,所以需要的内存为 84bit * 1700万 = 175MB 左右。
所以需要进行优化:
例如:
222456万345678饼北北,可以编码为30111011111102(三张相同牌,三张连续牌,六张连续牌,两张相同牌,中间隔开)。
下一步是将其二进制化,采用如下特制规则:
1→0
2→110
3→11110
4→1111110
10→10
20→1110
30→111110
40→11111110
很容易看出,这样编码后每张牌只占用1到2位空间,最恶情况子下(十四张不连单牌)仅占用27位。跟之前的84位相比,单组数据压缩了三分之二以上。更牛逼的是,和牌表从1700万种具体组合下降到仅仅9362种形状排列!
需要倒过来看,对照上面的规则从右往左看:
2 –> 0x7 –> 0111
23 –> 0xFB –> 1111 1011
32 –> 0xEF –> 1110 1111
233 –> 0x1F7B –> 0001 1111 0111 1011
2303 –> 0x3EFB –> 0011 1110 1111 1011
21110330 –> 0x1F7A3 –> 0001 1111 0111 1010 0011
package main
import (
"errors"
"fmt"
"net/http"
"net/rpc"
)
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
type Arith int
func (t *Arith) Multiply(args *Args, reply *int) error {
*reply = args.A * args.B
return nil
}
func (t *Arith) Divide(args *Args, quo *Quotient) error {
if args.B == 0 {
return errors.New("divide by zero")
}
quo.Quo = args.A / args.B
quo.Rem = args.A % args.B
return nil
}
func main() {
arith := new(Arith)
rpc.Register(arith)
rpc.HandleHTTP()
err := http.ListenAndServe(":1234", nil)
if err != nil {
fmt.Println(err.Error())
}
}
package main
import (
"fmt"
"log"
"net/rpc"
"os"
)
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
func main() {
if len(os.Args) != 2 {
fmt.Println("Usage: ", os.Args[0], "server")
os.Exit(1)
}
serverAddress := os.Args[1]
client, err := rpc.DialHTTP("tcp", serverAddress+":1234")
if err != nil {
log.Fatal("dialing:", err)
}
args := Args{16, 8}
var reply int
err = client.Call("Arith.Multiply", args, &reply)
if err != nil {
log.Fatal("arith error:", err)
}
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)
var quot Quotient
err = client.Call("Arith.Divide", args, ")
if err != nil {
log.Fatal("arith error:", err)
}
fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)
}
$ go run Client.go localhost
Arith: 16*8=128
Arith: 16/8=2 remainder 0
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.4.md
事情是这样的,前两天同事找到我帮忙看个bug。
在一个C++的类中,增加了一个新的成员函数funcA,结果程序就Crash掉。但是诡异的地方就在于Crash的地方并不在funcA这个函数中,而是在另外一个函数中的一个成员变量memB(memB是一个结构体),可新增的funcA这个函数完全没有修改过memB这个成员变量。
思路:
其实这非常明显就是内存越界了,在funcA修改内存的时候,越界了,修改到了memB的内存了。
那么为什么funcA修改的内存会影响到memB呢?那就要简单说下C++的内存了。
class Student
{
funcA()
funcB()
private:
int memA;
int memB;
int memC;
};
可以看下上面这个伪代码,在这个类中,memA,memB,memC中的内存都是并排的。
所以如果在funcA中修改了memA的内容,结果由于代码bug导致内存越界了,就有可能修改到memB,这样就会出现明明没有动过memB,程序却在memB的地方Crash掉。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "getopt.h"
int main(int argc, char ** argv)
{
int c;
while ((c = getopt(argc, argv, "i:p:abc:d:012")) != -1)
{
printf("c = %c\n", c);
int this_option_optind = optind ? optind : 1;
switch (c)
{
case 'i':
{
char acIP[128] = { 0 };
strcpy(acIP, optarg);
printf("acIP = %s\n", acIP);
break;
}
case 'p':
{
int port = atoi(optarg);
printf("port = %d\n", port);
break;
}
case '0':
case '1':
case '2':
break;
case 'a':
break;
case 'b':
break;
case 'c':
printf("option c with value %s\n", optarg);
break;
case 'd':
printf("option d with value %s\n", optarg);
break;
default:
printf("Unknown option.\n");
break;
}
}
if (optind < argc)
{
printf("non-option ARGV-elements: ");
while (optind < argc)
{
printf("%s ", argv[optind++]);
}
printf("\n");
}
return 0;
}
https://github.com/alex85k/wingetopt
https://en.wikipedia.org/wiki/Getopt