[转载]HTTP代理实现请求报文的拦截与篡改10--大结局 篡改部分的代码分析 - jivi - 博客园

mikel阅读(657)

[转载]HTTP代理实现请求报文的拦截与篡改10–大结局 篡改部分的代码分析 – jivi – 博客园.

  上节我们把篡改的功能简单的介绍了下,这些功能看起来挺玄乎的,但明白了原理后,其实没什么东西,下面我们就来分析一下这部分的代码。

 

 

  同样的,分析前我们先来回顾一下前面分析出来的内容。

 

  一。 一次会话(Session)的有四个过程 。

 

this.ObtainRequest()   // 获取请求信息 
this.Response.ResendRequest() // 将请求报文重新包装后转发给目标服务器 
this.Response.ReadResponse () // 读取从目标服务器返回的信息 
this.ReturnResponse() // 将从目标服务器读取的信息返回给客户端  

 

  二. 每个会话(Session)都是在独立的线程中执行的。

 

  知道了上面二点, 对于实现篡改来说就已经足够了,我们都知道线程有个很重要的特性,就是可以挂起和恢复,这就让我们的篡改实现起来非常容易了,在转发前挂起会话线程,然后在主线程(界面的那个线程)修改报文,然后恢复会话线程让其继续执行,那么这时候它转发到服务器的那个请求报文就是我们改过后的报文了。

 

  我们先来看看不实现篡改的情况下会话线程的执行步骤 (其中黑线为顺序流,红线为数据流)

 

 

 

  再来看看实现篡改功能的情况下会话线程和主线程的执行顺序

 

 

  从上面两张丑图可以看到,在实现篡改的情况下,运行到转发请求至服务器那一步的时候,转发的请求报文其实已经是在主线程里被修改过的报文了。 🙂  是不是很简单,下面我们就来看看代码是如何写的。

 

  相较于前面的代码,实现篡改后,我们增加了一个BreakPoint.cs的文件,这个文件里有两个类,一个BreakPoint类,一个是BreakPointManager类 ,还有一个枚举

 

复制代码
 1 using System;  2 using System.Collections.Generic;  3 using System.Linq;  4 using System.Text;  5 
 6 namespace JrIntercepter.Net  7 {  8   enum BreakPointType  9   { 10     Request, 11     Response 12   } 13 
14   class BreakPoint 15   { 16     public BreakPointType Type; 17     public String Url; 18   } 19 
20   class BreakPointManager { 21     private static IList<BreakPoint> breakPoints = new List<BreakPoint>(); 22     public static void Add(BreakPointType type, String url) 23     { 24       foreach (BreakPoint bp in breakPoints) 25       { 26         if (bp.Url.Trim().Equals(url.Trim())) 27         { 28           return; 29         } 30       } 31       breakPoints.Add(new BreakPoint { Type = type, Url=url}); 32     } 33 
34     public static BreakPoint Get(String url) 35     { 36       foreach (BreakPoint bp in breakPoints) 37       { 38         if (bp.Url.Trim().Equals(url.Trim())) 39         { 40           return bp; 41         } 42       } 43       return null; 44     } 45   } 46 }
复制代码

 

这两个类都很简单,就是用来记录断点的 。

 

BreakPointManager.Add(BreakPointType.Request,“www.baidu.com”) ;

 

  这样就对www.baidu.com这个网址下了一个断点,当在浏览器里 打开www.baidu.com的时候就会被断下来了。虽然BreakPointType里有一个Response类型,但没有实现,只是装装样子的。

 

  刚才讲过了,如果要实现篡改,需要在读取请求后挂起线程,而要想实现挂起线程自然要在Session的Execute方法里做文章了。因为前面回顾的一次会话的四个过程就是在这个方法体里实现的。

 

  我们还是只列出更改后的主干代码

 

复制代码
 1 // 从客户端获取请求信息 
 2 if (!this.ObtainRequest())  3 {  4   return;  5 }  6 
 7 // 如果当前状态小于ReadingResponse 
 8 if (this.State < SessionStates.ReadingResponse)  9 { 10   // 通知界面有新SESSION 
11   Intercepter.UpdateSession(this); 12  
13   String requestPath = this.Request.Headers.RequestPath.Trim().Trim('/') ; 14   string fullUrl = this.Request.Host.Trim().Trim('/') +  (string.IsNullOrEmpty(requestPath)? "":("/" + requestPath)); 15   fullUrl = fullUrl.Split('?')[0] ; 16   // 在这里截断 17   // 判断此在此网址下了断点 
18   BreakPoint bp = BreakPointManager.Get(fullUrl); 19   if (bp != null) 20   { 21     // 通知界面有断点被断 
22     Intercepter.BreakPoint(this, bp); 23     this.Pause(); 24   } 25  
26   // 将请求转发至目的服务器
27   this.State = SessionStates.SendingRequest; 28   if (!this.Response.ResendRequest()) 29   { 30     return; 31   } 32 
33   if (!this.Response.ReadResponse ()) 34   { 35   } 36 }
复制代码

 

  分析前先提一下,

 

// 通知界面有新SESSION 
Intercepter.UpdateSession(this);

 

  这个是原来就实现的,用来通知界面获得了新的Session。不过如果你翻看了上个版本的代码你就会发现,他被写在了this.Response.ResendRequest() 后面,这样的写法是错误的,因为这样我们就没办法实现篡改了,因为要想实现篡改就必须在请求转发前修改请求报文,而请求报文的两个部分,Headers(请求报文头)和RequestBodyBytes也就是请求报文体都被封装在了这个Session里 ,那么如果我们在转发请求后才将这个Session传给界面,那么又如何在转发前在主线程(界面线程来修改他呢,所以这次我们把他提到了ResendRequest之前 。 

 

  下面我们来看看这个方法的具体实现 和作用 。

 

  这个方法在Intercepter.cs 

 

复制代码
 1 internal delegate void DelegateUpdateSession(Session session);  2 internal static event DelegateUpdateSession OnUpdateSession;  3 
 4 internal static void UpdateSession(Session session)  5 {  6   if (OnUpdateSession != null)  7   {  8     OnUpdateSession(session);  9   } 10 }
复制代码

 

  方法很简单。就是如果有 OnUpdateSession 事件,就执行 OnUpdateSession 事件 。   

 

  再看看FrmMain.cs 里 FrmMain类的构造函数,里面有一句  

 

Intercepter.OnUpdateSession += new Intercepter.DelegateUpdateSession(this.OnUpdateSession);

 

  是不是已经串起来了 🙂

 

 

  再看OnUpdateSession方法(还是FrmMain类的)

 

复制代码
 1 private IList<Session> sessions = new List<Session>();  2 internal void OnUpdateSession(Session session)  3 {  4   try
 5   {  6     lock (lvSessions)  7     {  8       sessions.Insert(0, session);  9       ListViewItem lvi = new ListViewItem(); 10       lvi.Text = session.id.ToString(); 11       // FullUrl
12       lvi.SubItems.Add(session.Host); 13       lvi.SubItems.Add(session.Request.Headers.RequestPath); 14       lvi.SubItems.Add(session.Request.Headers.HTTPMethod); 15       lvi.SubItems.Add(session.LocalProcessName); 16  
17       this.lvSessions.Items.Insert(0, lvi); 18     } 19   } 20   catch {} 21 }
复制代码

 

  上面的lvSessions 就是左边的那个ListView (列表框)  。 

 

  看了上面的代码,绝大部分人应该已经明白它的作用了,这个类就相当于界面和Session之间的一个桥梁,是实现界面和Session的通讯 的 , 当然Session也可以直接和界面通讯,但这样写,可以大大的降低两者的耦合性,当然我们这个系列不是讲设计的,所以 这个只是提一下,主要的还是来看 他 看的作用,他的作用就是,每当 在 会话中(Session的一个实例获取了请求后 , 就会执行一次 FrmMain.cs 里的 OnUpdateSession 方法,然后在FrmMain.OnUpdateSession 方法里往一个全局变量 sessions 的顶部插入当前的这个session , 然后 再在左边的列表框里显示出当前的这个Session的主机名,请求的文件路径,HTTP的方法,以及进程名等信息 。

 

  注:我们这个类实现的是相当的不好的,是会出现问题的,你们可以改成更为安全的方式。

 

  好了,至此我们知道了执行完

 

Intercepter.UpdateSession(this);

 

  后 frmMain 里的sessions里就已经存储了当前这个Session,另外在左边的列表框里也会列出当前这个Session的相关信息。

 

  那么继续。 Intercepter.UpdateSession(this) 后面的代码如下

 

复制代码
String requestPath = this.Request.Headers.RequestPath.Trim().Trim('/') ; string fullUrl = this.Request.Host.Trim().Trim('/') +  (string.IsNullOrEmpty(requestPath)? "":("/" + requestPath)); fullUrl = fullUrl.Split('?')[0] ; // 在这里截断 // 判断此在此网址下了断点 
BreakPoint bp = BreakPointManager.Get(fullUrl); if (bp != null) {   // 通知界面有断点被断 
  Intercepter.BreakPoint(this, bp);   this.Pause(); } 
复制代码

 

  这里就是中断的核心代码了。 代码不多,也不难理解。先是获取当前请求的完整Url,这里的Url地址不包括http://或者https://这部分,也不包括?后面的部分。

 

  获取了完整的Url后,就从断点列表里找有没有这个网址的中断,如果没找到,继续执行后面的,如果找到了 就调用 

 

Intercepter.BreakPoint(this, bp);

 

  通知界面已经找到断点了。

 

  然后

 

this.Pause();

 

  暂停本线程的执行。

 

  我们先来看看 Intercepter.BreakPoint(this, bp);

 

  这个和前面的Intercepter.UpdateSession(this); 一样,最终会去执行FrmMain.cs里的 OnBreakPoint 方法。

 

  OKAY FrmMain 的 OnBreakPoint   方法  

 

复制代码
 1 internal void OnBreakPoint(Session session, BreakPoint breakPoint)  2 {  3   lock (lvSessions)  4   {  5     int sessionID = session.id;  6     foreach (ListViewItem li in lvSessions.Items)  7     {  8       Session tmp = this.sessions[li.Index];  9       if (tmp.id == sessionID) 10       { 11         li.BackColor = Color.Red; 12         break; 13       } 14     } 15 
16     this.Activate(); 17     lvSessions.Focus(); 18   } 19 }
复制代码

 

  这段代码同样不多,也不难理解,就是在sessions(存储所有session的列表)里找当前session所在的索引,这个索引也就是当前SessionListView里的索引, 然后,把ListView的那一行变红,并激活当前 程序 (弹到最前面,或者任务栏 闪动提示)  。 

 

  注 ListView的索引和sessions的索引是一致的,也就是ListView里索引为1的位置显示的正是 sessions 里索引为1Session 的信息 

 

  OKAY,把刚才的连起来,我们知道了执行完 Intercepter.UpdateSession(this); 后,程序会被弹到最前面或者在任务栏里闪动提示,同时,对应当前Session的那一行ListView 的背景会被变成红色  

 

  Intercepter.UpdateSession(this)  讲完了,后面自然是 thid.Pause()了。

 

  我们看一下他的代码。

 

  SessionPause 方法 , 顺便把Resume方法也列出来   

 

复制代码
 1 private AutoResetEvent syncEvent;  2 internal void Pause()  3 {  4   this.SState = SessionState.Pasue;  5   if (this.syncEvent == null)  6   {  7     this.syncEvent = new AutoResetEvent(false);  8   }  9   this.syncEvent.WaitOne(); 10 } 11 
12 internal void Resume() 13 { 14   if (this.syncEvent != null) 15   { 16     this.syncEvent.Set(); 17   } 18   this.SState = SessionState.Executing; 19 }
复制代码

 

  可以看到这里我们暂停线程使用的不是Suspend 而是AutoResetEvent 。因为Suspend是一个过时的方法。其它没什么讲的,反正执行了 this.Pause() 当前Session对应的线程会被挂起。这样ResendRequest (将请求转发至服务器的方法)将不会被执行,直到有人调用了Session.Resume  方法。 

 

   OKAY ,到此,我们已经将当前的请求中断下来了。下面我们就来修改 。 

 

  怎么修改???

 

  还记得上节里的操作说明吗,首先在左边的列表框里选中红色背景的那行(被断下来的那个Session) 。 

 

  注:刚才挂起的是Session线程,主线程也就是界面线程是没有被挂起,所以是可以继续进行 操作的。 

 

  既然是列表项的选中操作,我们自然要来看一下 ListView的 SelectedIndexChanged 事件的处理方法了,

 

  这个方法在FrmMain

 

复制代码
 1 private void lvSessions_SelectedIndexChanged(object sender, EventArgs e)  2 {  3   // 假如有被选中的行 
 4   if (this.lvSessions.SelectedItems.Count > 0)  5   {  6     // 选取第一个被选中的行,我们只处理单选的情况 
 7     int index = this.lvSessions.SelectedItems[0].Index;  8     // 调出选中的这行对应的session 
 9     Session session = sessions[index]; 10 
11     // 如果对应的session的状态是执行中 
12     if (session.SState == SessionState.Executing) 13     { 14       // 运行至完成 按钮变灰 就是不可用 
15       this.btnRunToComplete.Enabled = false; 16     } 17     else
18     { 19       // 运行至完成 按钮变亮 可以用 
20       this.btnRunToComplete.Enabled = true; 21     } 22 
23     // 在右边的请求文本框里(右边上面的文本框) 里显示选中行对应的session的报文头 
24     tbRequest.Text = session.Request.Headers.ToString(); 25     // 加个换行 
26     tbRequest.Text += "\r\n"; 27     // 显示请求报文体 
28     tbRequest.Text += Encoding.UTF8.GetString(session.RequestBodyBytes); 29 
30     // 如果 session.Response 头不为NULL 
31     if (session.Response != null && session.Response.Headers != null) 32     { 33       // 在响应文本框(右边下面的文本框)显示响应的报文头 
34       tbResponse.Text = session.Response.Headers.ToString(); 35       tbResponse.Text += "\r\n"; 36     } 37   } 38 }
复制代码

 

  这个方法也没什么好讲的,看看注释就能明白了,就是在右边上面的文本框显示请求报文,如果有响应报文的话,就在下面的文本框里显示响应报文头 。 另外如果对应的session不是Executing状态,也就是挂起状态,说明,那个session 被中断了,这时候就把 运行至完成按钮 变亮   。

 

  请求报文已经都被显示出来了,要想修改的话,只要在右边上面的文本框里修改就行了,这里我们为了方便就直接修改请求报文了,但这样不安全,如果你只想改GETPOST数据,可以继续处理 。  

 

  修改完成后,我们再点一下 运行至完成 按钮,那么整个篡改的过程就算完成了。

 

  再看 运行至完成 按钮的单击事件,仍然在FrmMain 里       

 

复制代码
 1 private void btnRunToComplete_Click(object sender, EventArgs e)  2 {  3   if (this.lvSessions.SelectedItems.Count > 0)  4   {  5     // 同样的获取选中行对应的session 
 6     ListViewItem selectItem = this.lvSessions.SelectedItems[0];  7     int index = selectItem.Index;  8     Session session = sessions[index];  9     // 利用Parse.ParseRequest方法分析文本框里的请求报文并重新封装成一个 10     // HttpRequestHeaders类型的变量,并替换session.Request里原来的Headers 11     // 这样报文头就被用修改后的报文头替换了 
12     session.Request.Headers = Parser.ParseRequest(this.tbRequest.Text); 13     // 替换报文主体部分 14     // 先将修改后的全部报文转化为byte[]数组。 
15     byte[] arrData = Encoding.UTF8.GetBytes(this.tbRequest.Text); 16     int lenHeaders = 0; 17     int entityBodyOffset = 0; 18     HTTPHeaderParseWarnings warnings; 19     // 利用Parse.FindEntityBodyOffsetFromArray 方法,获得报文体在整个报文中的偏移。 
20     Parser.FindEntityBodyOffsetFromArray(arrData, out lenHeaders, out entityBodyOffset, out warnings); 21     // 整个报文的长度-报文体的偏移 自然就是报文体的大小了 
22     int lenBody = arrData.Length - entityBodyOffset; 23     // 如果请求报文头时有Content-Length首部 
24     if (!String.IsNullOrEmpty(session.Request.Headers["Content-Length"])) 25     { 26       // 修改Content-Lenght首部的值为修改后的主体部分的大小 . 
27       session.Request.Headers["Content-Length"] = lenBody.ToString(); 28     } 29     // 构造一个和报文主体长度一样的MemoryStream的实例 ms 
30     MemoryStream ms = new MemoryStream(lenBody); 31     // 将报文体部分写到这个 ms 里 
32     ms.Write(arrData, entityBodyOffset, arrData.Length - entityBodyOffset); 33     // 将ms缓冲区地址赋值给 session.RequestBodyBytes 这样报文体也被修改了 
34     session.RequestBodyBytes = ms.GetBuffer(); 35 
36     // 恢复线程,线程恢复后做的是什么呢,当然是ResendRequest.翻翻前面的 37     // ResendRequest就是将 session.Request.Headers 38     // 转化成byte[]后和 session.ReqeustBodyBytes 一起被转发到目标服务器, 39     // 但此时这两个都已经被我们改过了 :) 
40     session.Resume(); 41 
42     // 将选中行的红色背景改回成白色 
43     selectItem.BackColor = Color.White ; 44     // 运行到完成按钮变灰 
45     this.btnRunToComplete.Enabled = false; 46   } 47 } 48
复制代码

 

  注释已经写的异常详细了,各位看看应该就可以明白了 。

  这个系列终于是完成了。 刚才粗略的统计了,二万五千多字。尼玛,再次不容易啊

[转载]吴昊品游戏核心算法 Round 15 —— 吴昊教你玩德州扑克(模拟+标志位存储) - 吴昊系列 - 博客园

mikel阅读(946)

[转载]吴昊品游戏核心算法 Round 15 —— 吴昊教你玩德州扑克(模拟+标志位存储) – 吴昊系列 – 博客园.

吴昊品游戏核心算法 Round 15 —— 吴昊教你玩德州扑克(模拟+标志位存储)

 梭哈

  梭哈,又称沙蟹,学名Five Card Stud,五张种马,是扑克游戏的一种。以五张牌的排列、组合决定胜负。游戏开始时,每名玩家会获发一张底牌(此牌只能在最后才翻开);当派发第二张牌后,便由牌面较佳者决定下注额,其他人有权选择“跟”、“加注”、“放弃”或“清底”。当五张牌派发完毕后,各玩家翻开所有底牌来比较,梭哈在全世界纸牌游戏地位非常高,深受人们的喜爱。游戏在国内和港台地区广泛流传,其特点为:上手容易、对抗性强,既有技巧也有一定的运气成分,因此流传非常广泛,五张牌(梭哈)高手必须具备良好的记忆力、综合的判断力、冷静的分析能力再加上些许运气。该游戏紧张刺激,集益智和乐趣于一身。

 

各种牌型

● High Card:杂牌(不属于下面任何一种)。根据牌从大到小的顺序依次比较。

● Pair:有一对,加3张杂牌组成。先比较对的大小,再从大到小的顺序比较杂牌。

● Two Pairs:有两对,加1帐杂牌。先从大到小比较对的大小,再比较杂牌。

● Three of a Kind:有3张值相同的牌。比较这个值即可。

● Straingt:一条龙。即5张牌连续。比较最大的一张牌即可。

● Flush:清一色。即5张牌花色相同。和杂牌一样比较。

● Full House:3张值相同的牌,加上一对。比较三张相同的值即可。

● Four of a kind:有4张牌相同,即相当于一副“炸弹”。

● Straight flush:同花顺。即5张牌花色相同,并且连续。例如同花色的34567。

各种花色

  花(club),方块(diamond),红桃(heart)和黑桃(spade)—— 在后面的控制台输入中会以C,D,H,S表示

 各种花色的数值

  每种花色会有一个对应的数值,分别为2,3,4,5,6,7,8,9,10,jack,queen,ace(J,Q,K)牌型大小的比较

  牌型大小的比较

  牌 型比较:Straight flush > Four of a kind > Full House > Flush > Straingt > Three of a Kind > Two Pairs > Pair > High Card。

 

  数字比较:A>K>Q>J>10>9>8>7>6>5>4>3>2

 

  花式比较:黑桃>红桃>草花>方块

 

  关于A2345,这手牌可以算顺子,但大小在各种扑克中不一样,梭哈里是第2大顺(例如赌神里就是这样),德州中却是最小的顺子。

  这里阐述了德州扑克和梭哈的一些区别(具体的区别我在后文中也会详细解释),这里说明一下,为了我们这里的控制台程序有一定的抽象性,就是说我们屏蔽了花色比较的细节,只考虑数字的比较和牌型的比较,这也是为了之后的计分方便。

 

  传统规则

  各家一张底牌, 底牌要到决胜负时才可翻开。从发第二张牌开始,每发一张牌,以牌面大者为先,进行下注。有人下注,想继续玩下去的人,选择跟,跟注后会下注到和上家相同的 筹码,或可选择加注,各家如果觉得自己的牌况不妙,不想继续,可以选择放弃,认赔等待牌局结束,先前跟过的筹码,亦无法取回。

  最后一轮下注是比赛的关键,在这一轮中,玩家可以进行梭哈,所谓梭哈是押上所有未放弃的玩家所能够跟的最大筹码。等到下注的人都对下注进行表态后,便掀开底牌一决胜负。这时,牌面最大的人可赢得桌面所有的筹码。

 现代规则

  在 现代的拓展中可以有2到10个玩家同时玩这个游戏。发牌前,必须先下基本的注额。每位玩家发两张牌。一张暗牌,一张明牌。第一圈,拥有最大的明牌的玩家首 先发言,他可以下注、不下注(让牌)或盖牌(放弃)也可以全压(梭哈)。其他玩家可以跟注(有玩家全压时必须全压)、加注或盖牌(放弃)。然后发明牌。第 二圈和第三圈如此类推。第四圈玩家要以他们手上的牌组合成最大的牌型,拥有最大的明牌的玩家首先发言,他可以下注、梭哈(又称全压)(摊牌)或盖牌(放 弃)。其他玩家可以跟注或盖牌(放弃)。最后,每位玩家要比牌型的大小以确定赢家。牌最大的玩家赢得所有桌上的赌金。

 各种术语

(1)全梭:以最小玩家的金币数目为每个玩家梭哈时下注的最大数目,但是最大下注数目由房间确定。

(2)封顶:以最小玩家的金币数目的50%为每个玩家梭哈时下注的最大数目。但是最大下注数目依然为房间确定。最高封顶为 100万金币。

 各种规则

(1)先发给各家一张底牌,底牌除本人外,要到决胜负时才可翻开。

(2)从发第二张牌开始,每发一张牌,以牌面发展最佳者为优先,进行下注。

(3)有人下注,想继续玩下去的人,要按“跟注”键,跟注后会下注到和上家相同的筹码,或可选择加注。根据房间的设定,可以在特定的时间选择“梭”,梭哈是加入桌面允许的最大下注。

(4)各家如果觉得自己的牌况不妙,不想继续,可以按“放弃”键放弃下注,先前跟过的筹码,亦无法取回。

(5)牌面最大的人可赢得桌面所有的筹码。当多家放弃,已经下的注不能收回,并且赢家的底牌不掀开。

(6)纸牌种类:港式五张牌游戏用的是扑克牌,取各门花色的牌中的“8、9、10、J、Q、K、A”,共28张牌。

  关于梭哈(或者以其改编的德州扑克)的具体技巧,我会在Round 15后面的具体AI中再阐述,其中还包括一些心理战,这些都是目前的AI所或缺的。

  我们这里先实现一个牌型比较程序,对于给定的两手牌,我们的程序可以将其进行相应的处理。这里,输入的每一行有十张牌(前面五张是黑方的,后面五张是白方 的),借鉴我的Round 2之“吴昊教你玩斗地主”中的方式,我们利用“数字+字母”的方式来表征一张牌的两个标准特征,也就是点数和花色,而五张牌又可以构成一手牌。

  在输出中,如果黑方获胜,我们输出“Black wins”,如果白方获胜,我们输出“White wins”,如果是平局的话,我们输出“Tie”。

 

  我们Source(ZOJ 1111)中的独特的技巧

关于Source的选择问题,我有考虑过一些常见的模拟算法,比如 “yllfever的专栏”和“hoodlum1980(发发)的技术博客”,但是,其代码都过于冗长,所以这里给出了一种独特的方法,将字符运算转为了 数字运算(利用数字来进行字符存储),所以,亮点在于数据结构的编排,算法的复杂程度也变高了一些。

Source的数据结构剖析

首先,如何利用一个32位的整型int变量来存储一副牌?

  我们用两个数组分别存储一副牌的点数和花色信息:

  char *deck=“23456789TJQKA”(T代表10,也就是ten)

  char *suit=“CDHS”( 梅花(club),方块(diamond),红桃(heart)和黑桃(spade))

  存储一张牌的时候,考虑到一个int类型的数值是32位的,那么,第0位和第1位可以存储花色信息(恰好有四种花色信息——2^2),后面四位来存储数值 (2^4),所以,感觉在空间上面还是有很大的浪费的,毕竟前面26位都没有使用了。所以说,在读取扑克牌的数值的时候,要右移两位。

  黑白双方的牌利用数组来表示:int deal[2][6];

  利用count计数器数组来记录重复的值 int count[13];

  利用rank来标记梭哈游戏的每一种规则 int rank;

  一手牌中不同值的个数为 int number[9]={5,4,3,1,1,5,2,1,1},对应每一种规则的点数不同的牌的数目;

  利用二维数组int value[2][7]来保存牌的大小

  牌型剖析

(1)       在比较的时候,首先要比较最大的那个,所以,首先对扑克牌进行降序排序:qsort(deal[i],5,sizeof(int),&compare),其中i可以选择0或者是1来对应白方和黑方

(2)       统计5张牌地数值重复的个数,将统计的结果放在数组count中

(3)       分别对13张扑克进行枚举,级别放在变量rank中(这也就是牌型剖析的内容

(A)如果重复的次数为2,可以判定为1个对子,2个对子或者葫芦

(B)如果重复的次数为3,可以判定为1个条子或者葫芦

(C)如果重复的次数是4,可以判定为铁支

(D)如果重复的次数是5,相邻牌的值相差为1,可以判定为顺子(已经排序过了)

(E)如果重复的次数是5,五张牌的花色都一样,可以判定为同花

(F)同时满足条件(D)和条件(E),则可以判定为同花顺

 (4)   进行级别的判断的时候,可以保存不同牌的值

 (5)   在value中存放的是number的值,rank的值以及number个不同牌的值(位数由低到高)两家通过级别rank和牌的大小number进行比较,决定胜负。

 

示范输入: 2H 3D 5S 9C KD 2C 3H 4S 8C AH

 示范输出: White wins.

  Source中用到了很多位标志存储的技巧(可以借鉴对溢出的一些处理)

 

复制代码
  1  //输入输出函数的控制 
  2  #include<stdio.h>
  3  //这两个头文件主要为了开启qsort和memset函数 
  4  #include<memory.h>
  5  #include<stdlib.h>
  6
  7  //一副牌的值和花色 
  8  char *deck=23456789TJQKA,*suit=CDHS;
  9
 10  //一手牌中不同值的个数(按照rank进行排列的) 
 11  int number[9]={5,4,3,1,1,5,2,1,1};
 12
 13  //这里定义了一个排序因子,后来会在qsort中调用 
 14  int compare(const void *a,const void *b)
 15  {
 16    //在返回时,认定a,b为int类型的变量(最开始a,b未定型)按照int的宽度读指针所在的数值 
 17    return ((*((int *)b))-(*((int *)a)));
 18  }
 19
 20  int main()
 21  {
 22    //为主函数开启各种数据结构 
 23    int deal[2][6]; //发两家的牌 
 24    int value[2][7]; //两家牌的大小(包括number和rank) 
 25    int count[13]; //扑克牌中每种牌值的重复次数 
 26    int *p_deal; //指向数组deal一行的指针 
 27    int *p_value; //指向数组value一行的指针 
 28    int i,j; //辅助变量 
 29    char card[10]; //一张牌 
 30    while(1//持续不断地读到文件尾 
 31    {
 32      //存储每行的10张牌的点数和花色 
 33      for(i=0;i<2;i++)
 34      {
 35        for(j=0;j<5;j++)
 36        {
 37          //说明读到文件尾 
 38          if(scanf(%s,card)==EOF) return 0;
 39          //保存每张牌,利用strchr函数比对,前两位方花色信息,后四位放点数信息 
 40          deal[i][j]=((strchr(deck,card[0])-deck)<<2)+(strchr(suit,card[1])-suit);
 41        }
 42      }
 43      int rank; //等级信息 
 44      int k; //记录某种规则出现的次数 
 45      memset(value,0,sizeof(value)); //将value数组清0,对于有些编译器而言,这个过程可以忽略
 46      //分别处理每一家的牌 
 47      for(i=0;i<2;i++)
 48      {
 49        qsort(deal[i],5,sizeof(int),&compare); //将5张牌降序排序 
 50        p_deal=deal[i];
 51        p_value=value[i];
 52        memset(count,0,sizeof(count)); //将计数器清0
 53        //利用计数器来记录每张牌重复的个数(这里的原理和麻将(吴昊系列的新年特别篇)的原理是一样的) 
 54        for(j=0;j<5;j++)
 55          count[p_deal[j]>>2]++;
 56        rank=0//rank初始置0
 57        //以下分别处理每一张牌,这里的点数一共13种,从最有威力的开始,相当于牌型分析的过程 
 58        for(j=12;j>=0;j–)
 59        {
 60          //如果有重复的牌的话 
 61          if(count[j]>1)
 62          {
 63            //由于同一点数的牌只可能有四张,故不可能出现count[j]为5这种情况 
 64            switch(count[j])
 65            {
 66              //有一个对子 
 67              case 2:
 68                   switch(rank)
 69                   {
 70                     case 0: rank=1; p_value[2]=j; break//第一个对子 
 71                     case 1: rank=2; p_value[3]=j; break//第二个对子 
 72                     case 3: rank=6break//存在一个三条           
 73                   }
 74                   break;
 75              case 3:
 76                   //这样写防止错误 
 77                   if(0==rank) rank=3;//只有一个三条 
 78                   else
 79                   {
 80                     //存在一个葫芦 
 81                     rank=6;
 82                     //记录这个三条的值 
 83                     p_value[2]=j;
 84                   }
 85                   break;
 86              case 4:
 87                   //存在一个铁支 
 88                   rank=7;
 89                   //记录该铁支的值 
 90                   p_value[2]=j;
 91                   break;
 92            }
 93          }
 94          //剩下的可能性只有count[j]=1,也就是单张牌
 95          //现在来考虑这5张牌是否有可能为顺子,同花或者更进一步地,是同花顺这种情况 
 96          if(rank<6)
 97          {
 98            //k有助于我们判断出是顺子,同花还是同花顺,这里置k的初始值为3
 99            //首先判断花色,利用k的第0位来判断 
100            for(j=1;j<5;j++)
101              if((p_deal[j]&3)!=(p_deal[0]&3))
102              {
103                k&=2;//因为2的二进制表示为”10″,这样与了之后可以置第0位为0 
104                break;
105              }
106            //然后我们来判断牌的点数是不是顺的,利用k的第1位为判断 
107            for(j=1;j<5;j++)
108              if((p_deal[j]>>2)!=(p_deal[j-1]>>2)-1)
109              {
110                k&=1;//因为1的二进制表示为”01″,这样与了之后可以置第1位为0 
111                break;
112              }
113            //现在我们可以加以判断了,因为一共四种情况,利用k的第0位和第1位就可以快速进行分类 
114            if(k==1) rank=5//同花 
115            if(k==2) rank=4//顺子 
116            if(k==3) rank=8//同花顺 
117          }
118          //记录顺子的最大值,这里首先要判定确实是一个顺子的最小值(这里由于是顺子,最大值和最小值一样) 
119          if((rank==4)||(rank==8))
120          {
121            p_value[2]=p_value[4]>>2;
122          }
123          //保存散牌或者同花(散牌)的所有值 
124          if((rank==0)||(rank==5))
125          {
126            for(j=0;j<5;j++)
127            {
128              p_value[j+2]=(p_deal[j]>>2);
129            }
130          }
131          //当只有一个对子的时候,为了防止对子相等的情况,还是需要保留除了对子以外的其余牌的值 
132          if(rank==1)
133          {
134            //这里的k有另外的含义,其标识数组下标的位置 
135            k=3;
136            for(j=0;j<5;j++)
137            {
138              if((p_deal[j]>>2)!=p_value[2])
139                p_value[k++]=(p_deal[j]>>2);
140            }
141          }
142          //当有两个对子的时候(也就是有一张牌是单牌),这里同上,保存除了对子以外的那张牌的值 
143          if(rank==2)
144          {
145            //还是先找到对应的数组下标 
146            k=4;
147            for(j=0;j<5;j++)
148            {
149              if((p_deal[j]>>2)!=p_value[2])
150              {
151                if((p_value[j]>>2)!=p_value[3])
152                {
153                  p_value[k++]=(p_value[j]>>2);
154                }
155              }
156            }
157          }
158          //value的第一位放置等级值,而第0位放置这个等级所对应的牌的张数 
159          p_value[1]=rank;
160          p_value[0]=number[rank];
161        }
162      }
163      int match=value[0][0]; //读第一家牌中的不同值的个数 
164      int *hand1=value[0]; //读第一家牌的具体情况 
165      int *hand2=value[1]; //读第二家牌的具体情况
166      //两家牌比大小,每比较一次,将不同值牌的个数–,换一张不同值的牌,首先比较的还是等级rank 
167      while(*(++hand1)==*(++hand2))
168      {
169        //一直到所有的牌比完位置 
170        if((–match)<0)
171          break;
172      }
173      //最后的判断了! 
174      if(match<0) printf(Tie.\n);
175      else if(*hand1>*hand2) printf(Black wins.\n);
176      else printf(White wins.\n);
177    }
178    return 0;
179  }
180
复制代码

   梭哈的变种——德州扑克

  七张牌梭哈是五张牌梭哈的变体,大约诞生于20世纪初,因为上述其四明一暗的方式暴露过多信息,同时由于不易成牌,容易作弊等缘故,七张牌梭哈更为流行,并且在美国德州扑克诞生前是最为流行的玩法,而至今该玩法还有众多玩家,在WSOP等世界级大赛中也有其项目。中国一直到1999年澳门的和记娱乐城才引进了这一欧洲流行游戏。

  七张牌梭哈通常以有限注的形式游戏(当然无限注也可了),它没有公共牌并可供2-9人游戏。开始时每人发两张面朝下和一张面朝上的牌;两张底牌和一张门 牌。在游戏的过程中,每个游戏的玩家再发3张面朝上的牌和最后1张面朝下的牌,他们必须在摊牌时用其中的5张牌组成一手牌。拿到最大一手牌的玩家赢得本轮 和底池。

 

  德州扑克与梭哈的区别

 梭哈是先发一张底牌一张明牌,根据牌面大小可以选择加注或放弃,然后一次发剩余的3张牌,每发一张都有加注和放弃的权利。
  德州扑克是先发两张底牌,根据底牌下注过牌或放弃,然后在发5张明牌,明牌是所有人的牌,第一次发3张明牌,然后每次一张,每次都有加注或放弃的权利。最后由手里的两张底牌加上5张明牌组合出你手里最大的牌的组合和其他对手比较。
  比牌梭哈和德州扑克一样,都是同花大顺最大,其次四条,然后葫芦(3+2),同花,顺子,三条,两对,一对,散牌最小。(如果是真人赌博的话,要询问好同花和顺子的大小,因为有些地方是顺子赢同花,其他不变)
  梭哈和德州扑克下注也有所区别;梭哈一般都要求有最低限度的筹码,低于最低限度的筹码不能进行游戏。德州扑克在这一点上有所改善,只要你还有超过底注的多余筹码就可以继续这个游戏,当然你赢得也是你底注+上你多余的筹码。
  压注叫法:如果你想一次性赢光对手的筹码或者将自己的筹码全部作为赌注—梭哈叫梭哈,德州扑克叫all in

[转载]PowerDesigner初体验 - 张龙豪 - 博客园

mikel阅读(1220)

[转载]PowerDesigner初体验 – 张龙豪 – 博客园.

PowerDesigner简介

工具的用途 :这里我只介绍他在SQLServer数据库的应用,其它的我也不懂。

工具的好处 : 个人体会理解,图视化数据库设计,更直观简单的设计,更便于分析数据库逻辑关系,与数据库全局把控。

祈求:哪位大虾那里藏的有PowerDesigner更好更全的操作文章,希望能给我和不动这货的攻城师们留言分享下。

PowerDesigner的下载以及破解

一、下载

1、PowerDesigner 12.5官方下载地址

   http://download.sybase.com/eval/PowerDesigner/powerdesigner125_eval.exe (复制到浏览器或迅雷下载)

 大小125 MB (132,006,349 字节) 

二、破解

1、安装完成后,修改安装目录下的pdflm12.dll文件,使用 UEDIT32 二进制编辑器打开此文件,查找:83 C4 14 8B 85 E4 FE FF FF将此字符串改为83 C4 14 33 C0 90 90 90 90 并保存(win7或者vista需要更改文件夹权限方可保存)

2、制作license.lic文件

拷贝以下内容,并保存为license.lic文件(注意扩展名是lic)

FEATURE PD_SHELL SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=1

FEATURE PD_DEV   SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=2

FEATURE PD_DA    SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=3

FEATURE PD_STU   SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=4

FEATURE PD_DEVE SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=5

FEATURE PD_DAE   SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=6

FEATURE PD_STUE SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=7

FEATURE PD_VW    SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=8

FEATURE PD_CDM   SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=9

FEATURE PD_XSM   SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=0

FEATURE PD_ILM   SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=1

FEATURE PD_RQM   SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=2

FEATURE PD_PDM   SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=3

FEATURE PD_OOM   SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=4

FEATURE PD_BPM   SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=5

FEATURE PD_RMG   SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=6

FEATURE PD_FRM   SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=7

FEATURE PD_RTP   SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=8

FEATURE PD_MMR   SYBASE 12.50 permanent uncounted 1 TS_OK HOSTID=ANY SIGN2=9

3、导入授权文件

A、打开PD12.5,菜单Tools -> License Parameters..,显示License Management Wizard。单击[下一步] 

B、显示License Load窗口 

C、点击[Load],选择上面生成的license.lic文件,之后显示License Key。 

D、单击[下一步]显示选择语言窗口,选择PRC。   

F、点击[完成],系统提示授权配置成功更新!

PowerDesigner创建SQL表的演示

1、我先声明下,接下来的介绍中 PowerDesigner = pd

2、在SQL数据库中创建 Test_Test 数据库

3、打开 pd ,看到右上角file –> 点开 ,看到new –> 点开

4、  别忘了点确定

5、

6、双击Table_1后

7、上边等于表建好了,但没有字段,下边开始添加字段,还是上边的弹出框

8、字段说明是很重要的,必须标明,那么你就要双击字段列

9.点击应用,确定,然后这个表就创建完成了,下面我们该把这个表,插入你创建好的Test_Test数据库了,点击pd菜单栏的database—>configure connections…

10、然后确定,选择应该是倒数第二个,Sql Server, 然后

11、

12、然后,pd菜单,的Database—>GenerateDatabase

13、可能要配置链接然后一直点确认,点run,就ok了。

14、也就是见证奇迹的时刻,打开你的数据库,就能看到你在pd创建的user表跑了进来!!

 

结束:

另外表可以建立约束,默认值,等,也可以改变表的样式….这里就不一一累述了。。自己扣持吧,要是弄不出来,给我留言,咱在讨论!

[转载]经验分享:CSS浮动(float,clear)通俗讲解 - 杨元 - 博客园

mikel阅读(969)

[转载]经验分享:CSS浮动(float,clear)通俗讲解 – 杨元 – 博客园.

很早以前就接触过CSS,但对于浮动始终非常迷惑,可能是自身理解能力差,也可能是没能遇到一篇通俗的教程。

前些天小菜终于搞懂了浮动的基本原理,迫不及待的分享给大家。

写在前面的话:

由于CSS内容比较多,小菜没有精力从头到尾讲一遍,只能有针对性的讲解。

如果读者理解CSS盒子模型,但对于浮动不理解,那么这篇文章可以帮助你。

小菜水平有限,本文仅仅是入门教程,不当之处请谅解!

本文以div元素布局为例。

 

教程开始:

 

首先要知道,div是块级元素,在页面中独占一行,自上而下排列,也就是传说中的。如下图:

 

 

可以看出,即使div1的宽度很小,页面中一行可以容下div1和div2,div2也不会排在div1后边,因为div元素是独占一行的。

注意,以上这些理论,是指标准流中的div。

小菜认为,无论多么复杂的布局,其基本出发点均是:“如何在一行显示多个div元素”。

显然标准流已经无法满足需求,这就要用到浮动。

       浮动可以理解为让某个div元素脱离标准流,漂浮在标准流之上,和标准流不是一个层次。

例如,假设上图中的div2浮动,那么它将脱离标准流,但div1、div3、div4仍然在标准流当中,所以div3会自动向上移动,占据div2的位置,重新组成一个流。如图:

 

从图中可以看出,由于对div2设置浮动,因此它不再属于标准流,div3自动上移顶替div2的位置,div1、div3、div4依次排列,成为一个新的流。又因为浮动是漂浮在标准流之上的,因此div2挡住了一部分div3,div3看起来变“矮”了。

这里div2用的是左浮动(float:left;),可以理解为漂浮起来后靠左排列,右浮动(float:right;)当然就是靠右排列。这里的靠左、靠右是说页面的左、右边缘。

如果我们把div2采用右浮动,会是如下效果:

 

此时div2靠页面右边缘排列,不再遮挡div3,读者可以清晰的看到上面所讲的div1、div3、div4组成的流。

目前为止我们只浮动了一个div元素,多个呢?

下面我们把div2和div3都加上左浮动,效果如图:

 

同理,由于div2、div3浮动,它们不再属于标准流,因此div4会自动上移,与div1组成一个“新”标准流,而浮动是漂浮在标准流之上,因此div2又挡住了div4。

咳咳,到重点了,当同时对div2、div3设置浮动之后,div3会跟随在div2之后,不知道读者有没有发现,一直到现在,div2在每个例子中都是浮动的,但并没有跟随到div1之后。因此,我们可以得出一个重要结论:

       假如某个div元素A是浮动的,如果A元素上一个元素也是浮动的,那么A元素会跟随在上一个元素的(如果一行放不下这两个元素,那么A元素会被挤到下一行);如果A元素上一个元素是标准流中的元素,那么A的相对垂直位置不会改变,也就是说A的顶部总是和上一个元素的底部对齐。

       div的顺序是HTML代码中div的顺序决定的。

       靠近页面边缘的一端是,远离页面边缘的一端是

 

为了帮助读者理解,再举几个例子。

假如我们把div2、div3、div4都设置成浮动,效果如下:

 

 

根据上边的结论,跟着小菜理解一遍:先从div4开始分析,它发现上边的元素div3是浮动的,所以div4会跟随在div3之后;div3发现上边的元 素div2也是浮动的,所以div3会跟随在div2之后;而div2发现上边的元素div1是标准流中的元素,因此div2的相对垂直位置不变,顶部仍 然和div1元素的底部对齐。由于是左浮动,左边靠近页面边缘,所以左边是前,因此div2在最左边。

假如把div2、div3、div4都设置成浮动,效果如下:

 

道理和左浮动基本一样,只不过需要注意一下前后对应关系。由于是右浮动,因此右边靠近页面边缘,所以右边是前,因此div2在最右边。

假如我们把div2、div4左浮动,效果图如下:

依然是根据结论,div2、div4浮动,脱离了标准流,因此div3将会自动上移,与div1组成标准流。div2发现上一个元素div1是标准流中的 元素,因此div2相对垂直位置不变,与div1底部对齐。div4发现上一个元素div3是标准流中的元素,因此div4的顶部和div3的底部对齐, 并且总是成立的,因为从图中可以看出,div3上移后,div4也跟着上移,div4总是保证自己的顶部和上一个元素div3(标准流中的元素)的底部对齐

至此,恭喜读者已经掌握了添加浮动,但还有清除浮动,有上边的基础清除浮动非常容易理解。

经过上边的学习,可以看出:元素浮动之前,也就是在标准流中,是竖向排列的,而浮动之后可以理解为横向排列。

清除浮动可以理解为打破横向排列。

清除浮动的关键字是clear,官方定义如下:

 

语法:

clear : none | left | right | both

取值:

none  :  默认值。允许两边都可以有浮动对象

left   :  不允许左边有浮动对象

right  :  不允许右边有浮动对象

both  :  不允许有浮动对象

 

定义非常容易理解,但是读者实际使用时可能会发现不是这么回事。

定义没有错,只不过它描述的太模糊,让我们不知所措。

根据上边的基础,假如页面中只有两个元素div1、div2,它们都是左浮动,场景如下:

此时div1、div2都浮动,根据规则,div2会跟随在div1后边,但我们仍然希望div2能排列在div1下边,就像div1没有浮动,div2左浮动那样。

这时候就要用到清除浮动(clear),如果单纯根据官方定义,读者可能会尝试这样写:在div1的CSS样式中添加clear:right;,理解为不允许div1的右边有浮动元素,由于div2是浮动元素,因此会自动下移一行来满足规则。

其实这种理解是不正确的,这样做没有任何效果。看小菜定论:

对于CSS的清除浮动(clear),一定要牢记:这个规则只能影响使用清除的元素本身,不能影响其他元素。

怎么理解呢?就拿上边的例子来说,我们是想让div2移动,但我们却是在div1元素的CSS样式中使用了清除浮动,试图通过清除div1右边的浮动元素 (clear:right;)来强迫div2下移,这是不可行的,因为这个清除浮动是在div1中调用的,它只能影响div1,不能影响div2。

根据小菜定论,要想让div2下移,就必须在div2的CSS样式中使用浮动。本例中div2的左边有浮动元素div1,因此只要在div2的CSS样式中使用clear:left;来指定div2元素左边不允许出现浮动元素,这样div2就被迫下移一行。

那么假如页面中只有两个元素div1、div2,它们都是右浮动呢?读者此时应该已经能自己推测场景,如下:

此时如果要让div2下移到div1下边,要如何做呢?

同样根据小菜定论,我们希望移动的是div2,就必须在div2的CSS样式中调用浮动,因为浮动只能影响调用它的元素。

可以看出div2的右边有一个浮动元素div1,那么我们可以在div2的CSS样式中使用clear:right;来指定div2的右边不允许出现浮动元素,这样div2就被迫下移一行,排到div1下边。

 

至此,读者已经掌握了CSS+DIV浮动定位基本原理,足以应付常见的布局。

其实,万变不离其宗,只要读者用心体会,再复杂的布局都可以通过小菜总结的规律搞定。

 

写在后面的话:

 

必须严正声明,CSS这块极其混乱,尤其是浏览器的兼容性问题,小菜水平有限,本文很可能有不当之处,望读者见谅。

其实真不想写这么长的文章,可为了读者能够理解,总是不由自主的想多举些例子。

为了减轻读者心理压力,本文没有任何CSS、HTML代码,因为现在很多教程上来就是一大堆CSS代码,看到就烦,别说细读了。

最后预祝读者阅读愉快,开心掌握知识。

[转载]js javascript:void(0) 真正含义(转) - 梅桦 - 博客园

mikel阅读(1018)

[转载]js javascript:void(0) 真正含义(转) – 梅桦 – 博客园.

我想使用过ajax的都常见这样的代码:
<a href=”JavaScript:doTest2();void(0);”>here</a>
但这儿的void(0)究竟是何含义呢?

JavaScript中void是一个操作符,该操作符指定要计算一个表达式但是不返回值。

void 操作符用法格式如下:
1. javascript:void (expression)
2. javascript:void expression

expression 是一个要计算的 Javascript 标准的表达式。表达式外侧的圆括号是选的,但是写上去是一个好习惯。 (实现版本   Navigator 3.0   )

你以使用 void 操作符指定超级链接。表达式会被计算但是不会当前文档处装入任何内容。

下面的代码创建了一个超级链接,当用户以后不会发生任何事。当用户链接时,void(0) 计算为 0,但 Javascript 上没有任何效果。

<A HREF=”javascript:void(0)”>单此处什么也不会发生</A>

下面的代码创建了一个超级链接,用户单时会提交表单。
<A HREF=”javascript:void(document.form.submit())”>
单此处提交表单</A>
a href=#与 a href=javascript:void(0) 的区别 链接的几种办法

#包含了一个位置信息

默认的锚是#top 也就是网页的上端

而javascript:void(0)   仅仅表示一个死链接

这就是为什么有的时候页面很长浏览链接明明是#是

跳动到了页首

而javascript:void(0) 则不是如此

所以调用脚本的时候最好用void(0)

或者<input onclick>

<div onclick>等

链接的几种办法
1.window.open(”url”)

2.用自定义函数
<script>
function openWin(tag,obj)
{
obj.target=”_blank”;
obj.href = “Web/Substation/Substation.aspx?stationno=”+tag;
obj.click();
}
</script>

<a href=”javascript:void(0)” onclick=”openWin(3,this)”>株洲</a>

window.location.href=””

外出网 www.waichu.net 发酷网 www.faako.com

[转载]jquery sortable..笔记 - 永不止步 - ITeye技术网站

mikel阅读(1049)

[转载]jquery sortable..笔记 – 永不止步 – ITeye技术网站.

所有的事件回调函数都有两个参数:eventui,浏览器自有event对象,和经过封装的ui对象
ui.helper – 表示sortable元素的JQuery对象,通常是当前元素的克隆对象
ui.position – 表示相对当前对象,鼠标的坐标值对象{top,left}
ui.offset – 表示相对于当前页面,鼠标的坐标值对象{top,left}
ui.item – 表示当前拖拽的元素
ui.placeholder – 占位符(如果有定义的话)
ui.sender – 当前拖拽元素的所属sortable对象(仅当元素是从另一个sortable对象传递过来时有用)

·参数(参数名 : 参数类型 : 默认值)
appendTo : String : ‘parent’
Defines where the helper that moves with the mouse is being appended to during the drag (for example, to resolve overlap/zIndex issues).
初始:$(‘.selector’).sortable({ appendTo: ‘body’ });
获取:var appendTo = $(‘.selector’).sortable(‘option’, ‘appendTo’);
设置:$(‘.selector’).sortable(‘option’, ‘appendTo’, ‘body’);

axis : String : false
如果有设置,则元素仅能横向或纵向拖动。可选值:’x’, ‘y’
初始:$(‘.selector’).sortable({ axis: ‘x’ });
获取:var axis = $(‘.selector’).sortable(‘option’, ‘axis’);
设置:$(‘.selector’).sortable(‘option’, ‘axis’, ‘x’);

cancel : Selector : ‘:input,button’
阻止排序动作在匹配的元素上发生。
初始:$(‘.selector’).sortable({ cancel: ‘button’ });
获取:var cancel = $(‘.selector’).sortable(‘option’, ‘cancel’);
设置:$(‘.selector’).sortable(‘option’, ‘cancel’, ‘button’);

connectWith : Selector : false
允许sortable对象连接另一个sortable对象,可将item元素拖拽到另一个中。
初始:$(‘.selector’).sortable({ connectWith: ‘.otherlist’ });
获取:var connectWith = $(‘.selector’).sortable(‘option’, ‘connectWith’);
设置:$(‘.selector’).sortable(‘option’, ‘connectWith’, ‘.otherlist’);

containment : Element, String, Selector : false
约束排序动作只能在一个指定的范围内发生。可选值:DOM对象, ‘parent’, ‘document’, ‘window’, 或JQuery对象
初始:$(‘.selector’).sortable({ containment: ‘parent’ });
获取:var containment = $(‘.selector’).sortable(‘option’, ‘containment’);
设置:$(‘.selector’).sortable(‘option’, ‘containment’, ‘parent’);

cursor : String : ‘auto’
定义在开始排序动作时,如果的样式。
初始:$(‘.selector’).sortable({ cursor: ‘crosshair’ });
获取:var cursor = $(‘.selector’).sortable(‘option’, ‘cursor’);
设置:$(‘.selector’).sortable(‘option’, ‘cursor’, ‘crosshair’);

cursorAt : Object : false
当开始移动时,鼠标定位在的某个位置上(最多两个方向)。可选值:{ top, left, right, bottom }.
初始:$(‘.selector’).sortable({ cursorAt: ‘top’ });
获取:var cursorAt = $(‘.selector’).sortable(‘option’, ‘cursorAt’);
设置:$(‘.selector’).sortable(‘option’, ‘cursorAt’, ‘top’);

delay : Integer : 0
以毫秒为单位,设置延迟多久才激活排序动作。此参数可防止误点击。
初始:$(‘.selector’).sortable({ delay: 500 });
获取:var delay = $(‘.selector’).sortable(‘option’, ‘delay’);
设置:$(‘.selector’).sortable(‘option’, ‘delay’, 500);

distance : Integer : 1
决定至少要在元素上面拖动多少像素后,才正式触发排序动作。
初始:$(‘.selector’).sortable({ distance: 30 });
获取:var distance = $(‘.selector’).sortable(‘option’, ‘distance’);
设置:$(‘.selector’).sortable(‘option’, ‘distance’, 30);

dropOnEmpty : Boolean : true
是否允許拖拽到一個空的sortable对象中。
初始:$(‘.selector’).sortable({ dropOnEmpty: false });
获取:var dropOnEmpty = $(‘.selector’).sortable(‘option’, ‘dropOnEmpty’);
设置:$(‘.selector’).sortable(‘option’, ‘dropOnEmpty’, false);

forceHelperSize : Boolean : false
If true, forces the helper to have a size.
初始:$(‘.selector’).sortable({ forceHelperSize: true });
获取:var forceHelperSize = $(‘.selector’).sortable(‘option’, ‘forceHelperSize’);
设置:$(‘.selector’).sortable(‘option’, ‘forceHelperSize’, true);

forcePlaceholderSize : Boolean : false
If true, forces the placeholder to have a size.
初始:$(‘.selector’).sortable({ forcePlaceholderSize: true });
获取:var forcePlaceholderSize = $(‘.selector’).sortable(‘option’, ‘forcePlaceholderSize’);
设置:$(‘.selector’).sortable(‘option’, ‘forcePlaceholderSize’, true);

grid : Array : false
将排序对象的item元素视为一个格子处理,每次移动都按一个格子大小移动,数组值:[x,y]
初始:$(‘.selector’).sortable({ grid: [50, 20] });
获取:var grid = $(‘.selector’).sortable(‘option’, ‘grid’);
设置:$(‘.selector’).sortable(‘option’, ‘grid’, [50, 20]);

handle : Selector, Element : false
限制排序的动作只能在item元素中的某个元素开始。
初始:$(‘.selector’).sortable({ handle: ‘h2’ });
获取:var handle = $(‘.selector’).sortable(‘option’, ‘handle’);
设置:$(‘.selector’).sortable(‘option’, ‘handle’, ‘h2’);

helper : String, Function : ‘original’
设置是否在拖拽元素时,显示一个辅助的元素。可选值:’original’, ‘clone’
初始:$(‘.selector’).sortable({ helper: ‘clone’ });
获取:var helper = $(‘.selector’).sortable(‘option’, ‘helper’);
设置:$(‘.selector’).sortable(‘option’, ‘helper’, ‘clone’);

items : Selector : ‘> *’
指定在排序对象中,哪些元素是可以进行拖拽排序的。
初始:$(‘.selector’).sortable({ items: ‘li’ });
获取:var items = $(‘.selector’).sortable(‘option’, ‘items’);
设置:$(‘.selector’).sortable(‘option’, ‘items’, ‘li’);

opacity : Float : false
定义当排序时,辅助元素(helper)显示的透明度。
初始:$(‘.selector’).sortable({ opacity: 0.6 });
获取:var opacity = $(‘.selector’).sortable(‘option’, ‘opacity’);
设置:$(‘.selector’).sortable(‘option’, ‘opacity’, 0.6);

placeholderType: StringDefault: false
设置当排序动作发生时,空白占位符的CSS样式。
初始:$(‘.selector’).sortable({ placeholder: ‘ui-state-highlight’ });
获取:var placeholder = $(‘.selector’).sortable(‘option’, ‘placeholder’);
设置:$(‘.selector’).sortable(‘option’, ‘placeholder’, ‘ui-state-highlight’);

revert : Boolean : false
如果设置成true,则被拖拽的元素在返回新位置时,会有一个动画效果。
初始:$(‘.selector’).sortable({ revert: true });
获取:var revert = $(‘.selector’).sortable(‘option’, ‘revert’);
设置:$(‘.selector’).sortable(‘option’, ‘revert’, true);

scroll : Boolean : true
如果设置成true,则元素被拖动到页面边缘时,会自动滚动。
初始:$(‘.selector’).sortable({ scroll: false });
获取:var scroll = $(‘.selector’).sortable(‘option’, ‘scroll’);
设置:$(‘.selector’).sortable(‘option’, ‘scroll’, false);

scrollSensitivity : Integer : 20
设置当元素移动至边缘多少像素时,便开始滚动页面。
初始:$(‘.selector’).sortable({ scrollSensitivity: 40 });
获取:var scrollSensitivity = $(‘.selector’).sortable(‘option’, ‘scrollSensitivity’);
设置:$(‘.selector’).sortable(‘option’, ‘scrollSensitivity’, 40);

scrollSpeed : Integer : 20
设置页面滚动的速度。
初始:$(‘.selector’).sortable({ scrollSpeed: 40 });
获取:var scrollSpeed = $(‘.selector’).sortable(‘option’, ‘scrollSpeed’);
设置:$(‘.selector’).sortable(‘option’, ‘scrollSpeed’, 40);

tolerance : String : ‘intersect’
设置当拖动元素越过其它元素多少时便对元素进行重新排序。可选值:’intersect’, ‘pointer’
intersect:至少重叠50%
pointer:鼠标指针重叠元素
初始:$(‘.selector’).sortable({ tolerance: ‘pointer’ });
获取:var tolerance = $(‘.selector’).sortable(‘option’, ‘tolerance’);
设置:$(‘.selector’).sortable(‘option’, ‘tolerance’, ‘pointer’);

zIndex : Integer : 1000
设置在排序动作发生时,元素的z-index值。
初始:$(‘.selector’).sortable({ zIndex: 5 });
获取:var zIndex = $(‘.selector’).sortable(‘option’, ‘zIndex’);
设置:$(‘.selector’).sortable(‘option’, ‘zIndex’, 5);

·事件

start
当排序动作开始时触发此事件。
定义:$(‘.selector’).sortable({ start: function(event, ui) { … } });
绑定:$(‘.selector’).bind(‘sortstart’, function(event, ui) { … });

sort
当元素发生排序时触发此事件。
定义:$(‘.selector’).sortable({ sort: function(event, ui) { … } });
绑定:$(‘.selector’).bind(‘sort’, function(event, ui) { … });

change
当元素发生排序且坐标已发生改变时触发此事件。
定义:$(‘.selector’).sortable({ change: function(event, ui) { … } });
绑定:$(‘.selector’).bind(‘sortchange’, function(event, ui) { … });

beforeStop
当排序动作结束之前触发此事件。此时占位符元素和辅助元素仍有效。
定义:$(‘.selector’).sortable({ beforeStop: function(event, ui) { … } });
绑定:$(‘.selector’).bind(‘sortbeforeStop’, function(event, ui) { … });

stop
当排序动作结束时触发此事件。
定义:$(‘.selector’).sortable({ stop: function(event, ui) { … } });
绑定:$(‘.selector’).bind(‘sortstop’, function(event, ui) { … });

update
当排序动作结束时且元素坐标已经发生改变时触发此事件。
定义:$(‘.selector’).sortable({ update: function(event, ui) { … } });
绑定:$(‘.selector’).bind(‘sortupdate’, function(event, ui) { … });

receive
当一个已连接的sortable对象接收到另一个sortable对象的元素后触发此事件。
定义:$(‘.selector’).sortable({ receive: function(event, ui) { … } });
绑定:$(‘.selector’).bind(‘sortreceive’, function(event, ui) { … });

over
当一个元素拖拽移入另一个sortable对象后触发此事件。
定义:$(‘.selector’).sortable({ over: function(event, ui) { … } });
绑定:$(‘.selector’).bind(‘sortover’, function(event, ui) { … });

out
当一个元素拖拽移出sortable对象移出并进入另一个sortable对象后触发此事件。
定义:$(‘.selector’).sortable({ out: function(event, ui) { … } });
绑定:$(‘.selector’).bind(‘sortout’, function(event, ui) { … });

activate
当一个有使用连接的sortable对象开始排序动作时,所有允许的sortable触发此事件。
定义:$(‘.selector’).sortable({ activate: function(event, ui) { … } });
绑定:$(‘.selector’).bind(‘sortactivate’, function(event, ui) { … });

deactivate
当一个有使用连接的sortable对象结束排序动作时,所有允许的sortable触发此事件。
定义:$(‘.selector’).sortable({ deactivate: function(event, ui) { … } });
绑定:$(‘.selector’).bind(‘sortdeactivate’, function(event, ui) { … });

·方法
destory
从元素中移除拖拽功能。
用法:.sortable( ‘destroy’ )

disable
禁用元素的拖拽功能。
用法:.sortable( ‘disable’ )

enable
启用元素的拖拽功能。
用法:.sortable( ‘enable’ )

option
获取或设置元素的参数。
用法:.sortable( ‘option’ , optionName , [value] )

serialize
获取或设置序列化后的每个item元素的id属性。
用法:.sortable( ‘serialize’ , [options] )

toArray
获取序列化后的每个item元素的id属性的数组。
用法:.sortable( ‘toArray’ )

refresh
手动重新刷新当前sortable对象的item元素的排序。
用法:.sortable( ‘refresh’ )

refreshPositions
手动重新刷新当前sortable对象的item元素的坐标,此方法可能会降低性能。
用法:.sortable( ‘refreshPositions’ )

cancel
取消当前sortable对象中item元素的排序改变。
用法:.sortable( ‘cancel’ )

排序后保存有两种方法,一是cookie,二是数据库配置文件等。
这个是cookie  的例子 大家可以参考 http://www.cnblogs.com/tianxiangbing/archive/2010/01/26/jQuery_sortable.html

下面是数据库的部分代码 原作:
$(function() {
var show = $(“.loader”);
var orderlist = $(“.orderlist”);
var listleft = $(“div[id = ‘column_left’]”);
var listcenter = $(“div[id = ‘column_center’]”);
var listright = $(“div[id = ‘column_right’]”);
$( “.column” ).sortable({
opacity: 0.5,//拖动的透明度
revert: true, //缓冲效果
cursor: ‘move’, //拖动的时候鼠标样式
connectWith: “.column”,
//开始用update 结果执行两次,浪费资源,古改成stop
//但是stop在元素没有改变位置的时候也会执行,
//用update其他js会有问题,^_^
stop: function(){
var new_order_left = []; //左栏布局
var new_order_center = [];//中栏布局
var new_order_right = [];//右栏布局
listleft.children(“.portlet”).each(function() {
new_order_left.push(this.title);
});
listcenter.children(“.portlet”).each(function() {
new_order_center.push(this.title);
});
listright.children(“.portlet”).each(function() {
new_order_right.push(this.title);
});
var newleftid = new_order_left.join(‘,’);
var newcenterid = new_order_center.join(‘,’);
var newrightid = new_order_right.join(‘,’);
$.ajax({
type: “post”,
url: jsonUrl, //服务端处理程序
data: { leftid: newleftid, centerid: newcenterid, rightid:newrightid},   //id:新的排列对应的ID,order:原排列顺序
//               beforeSend: function() {
//                    show.html(” 正在更新”);
//               },
success: function(msg) {
//alert(msg);
show.html(“”);
}
});
}
});

[转载]关于.NET下开源及商业图像处理(PSD)组件 - asxinyu - 博客园

mikel阅读(1154)

[转载]【原创】关于.NET下开源及商业图像处理(PSD)组件 – asxinyu – 博客园.

我的博客其他文章链接:

【原创】.NET开源压缩组件介绍与入门

【原创翻译】C#开源轻量级对象数据库NDatabase介绍

【转载总结】关于泛型基类的相关知识

NewLife.Xcode组件资源目录

Newlife.XCode对象容器与接口操作实例

拥有自己的代码生成器—NewLife.XCode代码生成器分析

拥有自己的代码生成器—Newlife.XCode模板编写教程

1 前言

这篇博客的背景是:为了完成吉日嘎拉的 “PSD文件损坏检测和图层检测”任务,查找了目前.NET各种开源的及商业的图像处理资料,在完成任务之后,进行总结。此次任务主要是用C#操作 PSD(PhotoShop)文件,中文资料很少,主要的英文资料都在CodeProject网站。项目要求对用户上传的PSD文件进行检测,是否损坏, 是否能正常打开,并判断是否只有1个图层。俗话说:“冰冻三尺非一日之寒”,这次找关于C#处理PSD的开源资料,真是“挖地三尺”,我也觉得这“非一日 之力”就可以做到。平时关注技术,了解很多,找起来才快,也需要一点主动学习的能力。

2 .NET图像处理组件总结

说起图像处理,肯定很多人都知道博客园的xiaoite,用.NET做图像处理,很强大。由于图像处理的特殊性,可能很多人认为.NET做图像处理效率有问题,但xiaoite用实际证明了其实这都不是问题,大家可以去他的博客详细了解下。呵呵,我只是一个菜鸟,把别人现成的东西组合一下,会使用而已。那下面介绍一下,我找到的几个.NET图像处理组件(和PSD相关的)。

2.1 Paint.NET

.NET下的图像处理开源项目,最有名的应该是Paint.NET,小巧,灵活,足够强大,相比笨重收费的PhotoShop,这真是一个不错的选择。虽然该项目源代码现在不开放了,但软件使用仍然是免费的。昨天看了一下他的dll,没有混淆,有精力的可以去反编译看看。我的解决办法也是从这个项目找到的答案。Paint.NET的源代码3.1版本的应该还可以在网上找得到,另外Paint.NET也有很多插件,比如Paint.NET_PSD_Plugin,就可以用插件来加载PSD文件,并转换为Paint.NET的格式。前几天,给一个朋友PS2张简单的图片,用的就是Paint.NET,操作和PhotoShop差不多,界面很清爽,功能足够强大吧,推荐一下。

2.2 Imagemagick.NET

Imagemagick.NET是 用C#对开源的Imagemagick图像处理组件的进行了一个封装。里面的代码大部分都是C++/CLI,能力有限,没有深入研究。看了一下说明,支持 的格式很多,但对PSD文件的图层和文件损坏检测也没有现成的,不过,这个项目的确很值得学习,有机会研究下 C++/CLI,对图像处理应该有很大帮 助。Imagemagick支持的语言和平台都很广泛,值得一看。

2.3 Skimpt

Skimpt其实这个组 件以前没看到过,是一个.NET下开源的屏幕截图软件。它的大部分代码我没仔细看,只发现它支持PSD文件,所以就主要看了看PSD操作的部分,相对不 错,PSD代码部分也是借鉴了CodeProject上面的几篇文章。都很相似,不过都修改了不少错误,这个其实还不错,勉强可以正确判断图层,但是对 PSD文件的损坏检测不了,它只是直接读取文件的头信息,并不去检验数据的正确性。如果有做屏幕截图之类的工具,这是一个不错的参考。虽然没用里面的代 码,但也发现Google Code其实也有很多不错的项目。

2.4 ImageGlue.NET

ImageGlue.NET是商业的图像处理组件,支持的格式列了一大堆:支持的格式。 由于这个组件我很早就知道,但没用过,手上也有破解版,所以就试了一下,可以对PSD文件的损坏检测,但是对图层的判断一直出错,有时候是正确的,有时候 是错误的,商业组件做成这样,一怒之下,抛弃它了,而且破解版也不太敢用,当时只是试试看好用不,然后把里面的一小段代码弄出来就可以了,没想到这货还调 用了非托管的dll,代码没虽然有混淆,但命名空间足够的坑爹,对图像的一些基本处理应该还是不错的,关键是支持的格式全面。留个脚印,让大家以后也有个 选择。

2.5 其他关于.NET操纵PSD的文章

其实我首先查到的还是codeproject上的一篇2006年发表的英文博客Yet Another PSD Parser。该作者也是为了完成一个PSD的项目,需要.NET操作PSD文件,而处于版权和许可证的原因,没有采用Adobe公司的SDK,而是自己开发了这套组件。这个组件也是作者Endogine项目(一个2D的图像游戏引擎)的一部分,但可惜的是这个组件好像没有完善,这篇文章也没有更新,有不少错误。

编程爱好者,关注.NET软件开发,Matlab编程及网络技术

http://www.cnblogs.com/asxinyu/E-mail:1287263703@qq.com

[原创]SQL获取字段html代码中的img标签图片文件的路径

mikel阅读(1820)

SQL获取字段html代码中的img标签图片文件的路径,

 

SELECT SUBSTRING(Description,0,PATINDEX('%"%',Description) as f
from(
select 
substring(InfoContent,patindex('%src="%',InfoContent)+5,patindex('%src="%',InfoContent)+5+PATINDEX('%" />%',substring(InfoContent,patindex('%src="%',InfoContent),len(InfoContent)-patindex('%src="%',InfoContent)))) as Description
from Tang_Information) as img

[原创]ASP.NET MVC控制器中动态解析用户控件

mikel阅读(1089)

目前项目涉及到要分模块进行页面调整的需求,想到用ASP.NET mvc的用户控件进行实现,将页面拆分成多个模块,每个模块是一个用户控件,用户可以拖拽模块进行页面布局的调整并保存成配置信息,然后控制器读取每个用户的个性化配置在action中进行控件的加载解析,然后呈现到视图上,下面是动态解析用户控件的代码:

 

        #region 装修

        public ActionResult Design(string shopid)
        {
            var page = new System.Web.Mvc.ViewPage();
            ViewUserControl calendar = (ViewUserControl)page.LoadControl("~/Components/Design.ascx");//要转换为MVC的用户控件
            StringWriter output = new StringWriter();
            ViewData["data"]="data";

            using (var sw =new System.Web.UI.HtmlTextWriter(output))
            {
                page.Controls.Add(calendar);//控件需要绑定到page中
                calendar.ViewData["data"] = "data";//控件的data赋值
                 page.RenderControl(sw);//页面解析控件
                ViewData["control"]=sw.InnerWriter.ToString();//赋值到viewdata
            }
            output.Dispose();//释放资源

            return View();

        }

控件的代码:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Design.ascx.cs" Inherits="NewTang.Components.Design" %>
<div>design:<%=ViewData["data"] %></div>

[转载]如何高效利用GitHub ← 阳志平的个人网站::技术

mikel阅读(1132)

[转载]如何高效利用GitHub ← 阳志平的个人网站::技术.

如何高效利用GitHub

正是Github,让社会化编程成为现实。本文尝试谈谈GitHub的文化、技巧与影响。

Q1:GitHub是什么

A1:一家公司

github

位于旧金山,由Chris Wanstrath, PJ HyettTom Preston-Werner三位开发者在2008年4月创办。迄今拥有59名全职员工,主要提供基于git的版本托管服务。

在此之前,它是由TomChris 在本地程序员聚会中,开始的一个用于托管git的项目。正如每个伟大的传奇都开始于一场冒险,Tom在这篇文章我如何辞掉微软30万年薪邀约,创办GitHub中谈到:

当我老去,回顾一生,我想说,“哇,那是一场冒险“;而不是,“哇,我真的很安稳。“

另一位创始人Chris也详细描述了GitHub初创的前因后果,他说道:

Do whatever you want.

于是,在2008年4月10号这一天,GitHub正式成立。

目前看来,GitHub这场冒险已经胜出。根据来自维基百科关于GitHub的描述,我们可以形象地看出GitHub的增长速度:

github

今天,GitHub已是:

  • 一个拥有143万开发者的社区。其中不乏Linux发明者Torvalds这样的顶级黑客,以及Rails创始人DHH这样的年轻极客。
  • 这个星球上最流行的开源托管服务。目前已托管431万git项目,不仅越来越多知名开源项目迁入GitHub,比如Ruby on Rails、JQuery、Ruby、Erlang/OTP;近三年流行的开源库往往在GitHub首发,例如:BootStrapNode.jsCoffeScript等。
  • alexa全球排名414的网站

Q2:GitHub风格

A2: GitHub只是GitHub

强调敏捷开发与快速原型,而又的确成功的创业团队,常具备一个重要气质:有自己的文化风格。如GitHub,又如37signals。通过他们的快速开发,向用户证明了团队在技术上的能力,并且时常有惊喜。同时,通过强调特立独行的文化,将对半衰期过短的产品族群的信任转为对GitHub团队的信任。

Gravatars的创始人(对,就是互联网最流行的头像托管系统)、Jekyll(对,它就是我近几年用的博客系统)作者、GitHub创始人,现任CTO Tom在GitHub第一年学到的10大教训创业学校演讲中谈到GitHub文化的方方面面。我尝试将这种风格总结为以下要点:

  • 专注创作,高创意
  • 运营良好与较高的内外满意度
  • 高利润,较低的融资额或零融资

创业公司多半死在钱上,就让我们先从钱谈起:

高利润,较低的融资额或者零融资

类似于GitHub这样的公司,拿到风险投资很难吗?恰恰相反,创始人PJ Hyett 在Hacker News的一篇评论中提到,自从GitHub创办以来,已与几十个VC沟通过。但是,直到今天,GitHub的融资额还是为零,并引以为豪。让我们看看GitHub官网的自我介绍:

image

运营良好与较高的内外满意度

在Quora上有人问道,GitHub是否寻找被收购?,还是PJ Hyett ,他的回答是:No。

GitHub从一开始就运营良好,员工拥有较高满意度,看看这些不太一样的做法:

  • 每一位GitHub公司的新员工,官方博客将发表文章欢迎。
  • 在GitHub内部,没有经理,需求内容与优先级由项目组自行决策。
  • 选择自己的工作时间、工作地点。
  • 员工来自开源社区。
  • 能开源的尽可能开源。

富有激情、创意的员工使得GitHub得到了社区的广泛认同,从而拥有极高的客户满意度,并从创业一开始就盈利。一份早期的调查表明,GitHub很快成为Git托管首选。

专注创作,高创意

GitHub59名全职员工仅有29名员工在本地工作!不仅仅是工作地点的安排富有创意,GitHub员工Holman, 详细介绍了GitHub的工作方式:

Q3:在GitHub,如何跟牛人学习

A3:在学习区刻意练习

追随牛人,与他们一起修行

修行之道: 关注大师的言行, 跟随大师的举动, 和大师一并修行, 领会大师的意境, 成为真正的大师。 

正如这首禅诗所言,与其在墙内仰望牛人,不如直接在GitHub:

  • watch、fork牛人们
  • 对他们的项目提交pull request
  • 主动给牛人们的项目写wiki或提交测试用例,或者问题
  • 还可以帮他们翻译中文

GitHub本身建构在git之上,git成为勾搭大师们的必要工具,以下读物成为首选:

如果希望进一步深入,可以阅读已有中文翻译版的材料:

同样,如果希望了解更多GitHub自身的知识,GitHub官方文档值得推荐:

牛人在哪里?

关于学习的心理学研究,常常会谈到一个术语:元认知、元学习、元知识。是的,关于认知的认知、关于学习的学习、关于知识的知识,你对这些信息的偏好与熟练掌握,会让你在学习一门新东西时更加轻车熟路。对一手信息进行回溯,比如作者、创始人、最初文献出处,总是会让你更容易理解知识。

在学习区刻意练习:借助GitStats进行项目统计

如何学习一门新的编程语言?——在学习区刻意练习中,我已谈过:

学习编程最好的方式是在学习区刻意练习。

如何进行自我监督?

借助于GitStats,我们能很好地统计自己的每个项目的工作量,从而看到工作进展。

用法如下,

#复制GitStats项目到本地 cd ~/dev git clone git://github.com/trybeee/GitStats.git python ~/dev/gitstats/git-stats /youproject public 

以下为生成结果示范:

每周代码提交次数:

github

每天代码提交行数:

github

如果Fork别人的项目或者多人合作项目,最好每人都拥有一个独立分支,然后由项目维护人合并。如何建立自己的分支?

# 分支的创建和合并 # git branch yourbranch # git checkout yourbranch 切换到yourbranch # 开发yourbranch分支,然后开发之后与master分支合并 # git checkout master # git merge yourbranch # git branch -d yourbranch 合并完后删除本地分支 

如何将牛人的远程分支更新到自己的本地分支?

# 查看当前项目下远程 # git remote # 增加新的分支链接,例如 git remote add niuren giturl… # 获取牛人的远程更新 git fetch niuren # 将牛人的远程更新合并到本地分支 git merge niuren/master 

生产力小技巧

codeshelver:给git库做标签

观察的项目如果多了,怎么管理?用codeshelver,安装扩展之后,可以对GitHub项目做标签。

gollum:利用git与github做wiki

gollum是一个基于git的轻型wiki系统。

GitHubwatcher: 监测重点项目

GitHubwatcher适用于通知不频繁的情景。

GitHub官方资源

GitHub官方列出了一些有用的脚本与书签

社区驱动的安装与配置文件

GitHub中各类配置文件层出不穷,一些常用的:

  • osh-my-zsh:将终端从bash改为zsh之后,可考虑安装社区驱动的zsh配置文件,含有多个插件。可参考旧文zsh与oh-my-zsh
  • gitignore:GitHub官方出品
  • yourchili:服务器各类安装shell,比如安装nginx等。

Q4: 享受纯粹的写作与演讲

A4:回归创作的初始

写作

早在2008年,就有技术图书作者通过Git来写作,以下是示范:

你能想到的技术前沿话题,大多能在GitHub找到相应的培训材料或者开源图书。

个人写作照样适用。在前文理想的写作环境:Git+GitHub+Markdown+Jekyll,我已经格外赞美过这些美好事物了。

暖色调的灯光,足够宽度的工作台,听着清脆的键盘声音,基于Git、GitHub、Markdown与Jekyll来写作,不担心写废与排版,只关 注最纯粹的写作,是一种享受。我有时候会想,如果Git、Github、Markdown、Jekyll,再加上Yaml、Json的作者,让这些作者们 重新来设计今天互联网基础架构偏文本的部分,会诞生一些什么?

个人博客

借助于Jekyllbootstrap,可以在Github上快速搭建一个基于jekyll的博客系统。

除了这个简单易行的办法之外,还存在一些其他方法,例如:

演讲

借助于GitHub,可以享受更纯粹、更酷的演讲。GitHub 2011年收购Ordered List之后,从此可以通过speakerdeck更好的分享ppt文档。

我们还可以:

  • 使用GitHub著名传教士、Progit作者Scott Chacon开发的showoff
  • 来自开源社区的其他演讲库impress.js

Q5: 代码帮你找工作

A5:GitHub简历很诚实

NumEricR(非GitHub工作人员)基于GitHub Pages功能做了一个简历生成器,使用极其简单,登陆网站GitHub简历生成器,填入你的GitHub网站用户名即可。

fredwu是Ruby中文社区活跃份子,他的开源项目angel_nest,一个天使投资与创业者对接的网站,适合Ruby初学者升级为Ruby中级开发者时学习,也在Hacker News上被热烈讨论过,让我们来看看他的简历:

http://resume.GitHub.com/?fredwu

正是因为GitHub上的代码无法造假,也容易通过你关注的项目来了解知识面的宽度与深度。现在越来越多知名公司活跃在GitHub,发布开源库并招募各类人才,例如:FacebookTwitterYahoo

开始有了第三方网站提供基于GitHub的人才招聘服务,例如:

  • GitHire:通过它,可以找出你所在地区的程序员。
  • Gitalytics.com:通过它,能评估某位程序员在GitHub、LinkedIn、StackOverflow、hackernews等多个网站的影响力。

Q6: GitHub还在影响一些什么

A6:让计算机增强人类智慧

很多年前,在某个名声显赫的学府中,两位先后拿过图灵奖的牛人有一段对话:

  • 牛人A:我们要给机器赋予智慧,让他们有自我意识!
  • 牛人B:你要给机器做那么多好事?那你打算给人类做点什么呢?

这段对话来自《失控》。牛人A是明斯基,他最喜欢将人类看做有血肉的机器,他的框架理论成为认知心理学、人工智能入门基础。牛人B则是恩格尔巴特。当明斯基1961年发表他著名的文章人工智能走向时,恩格尔巴特还籍籍无名。直到次年,恩格尔巴特发表宏文:人类智力的增强:一种概念框架。提出不同于明斯基的另一条增强人类智力的道路:不要尝试发明自动打字的机器,而是尝试发明鼠标,并且他真的发明鼠标成功了!

从近些年的发展来看,仍然是明斯基占上风,但是,三十年河东,三十年河西,明斯基的人工智能方向又有多少年没有大突破了?相反,来自恩格尔巴特的群 件、集体智慧等思想,逐步成为步入Web2.0时代之后的共识。无关对错,可以说,恩格尔巴特为增强人类智力,提供了可行的框架。与其去发明聪明的、昂贵 的、功能一体化的智能机器人,还不如发明类似于鼠标这样笨笨的、廉价的、功能单一的人类智慧服务单件。明斯基的机器人很容易陷入死胡同,没有上升到哲学的 高度。现在慢慢又回到恩格尔巴特这个方向来了。比如现在IBM开始宣传认知计算

从git与GitHub设计与解决的问题本质来看,明显加速了代码生产流程,促进了卓越智力产品的诞生。这就是一种典型的web2.0对智力生产流 程的改良与人类智慧的增强。同样,某种意义上,小说写作网站也起到类似作用。但是,学术界尤其是社会科学类的智力产品生产似乎还停留在一个古老阶段。在开 源领域,好想法层出不穷,极客影响极客,最终产生的是酷玩意。这些酷玩意抛弃浮华,直奔问题本质。那么,有没有科学界的GitHub?

类似问题层出不穷,以下为其他领域产品不完全名单。

学术研究

数据

科学计算

  • opani:雏形中,支持R、Python等多种。

教育

  • OpenStudy:一个社会性学习网络,通过互助来更好地学习,主题涉及到计算机、数学、写作等。
  • openhatch: 通过练习、任务等帮助新手更好地进入开源社区

Q7:除了GitHub,还可以选择什么?

A7:nil

因为进化的需要,多数裸猿存在选择强迫症:哪种程序语言更好?哪个web开发框架更好?当然,最令宅男技术男们羡慕的问题是,高白瘦御姐还是青春小萝莉好?:D

除了GitHub之外,

  • 中国山寨品是不是更好?(为什么不写他们名字,你懂的,山寨品总是善于争论谁是第一个山寨的,各自的排名先后:D)
  • 免费的BitBucket是不是更适合Python程序员?
  • 作为一名折腾族,我不自己搭建一个gitlabhq,是不是对不起自己?

我们可以理解,正是因为无数条分岔路口,让人类不再受制于某种基因、特定疾病、独裁家族,从而拥有无限的可能。但是,这种选择强迫症与远古时代可怜的信息量相比较,

  • 今天这个大数据时代,它还会有助于人类作为族群的整体进化与作为个体的幸福吗?
  • 今天一位一线城市30岁大学毕业生经历的选择与孔子整个一生经历的选择,纯论数量,谁多谁少?

生命如此短暂,为什么总要将青春浪费在不断的选择之中呢?罚你,回头阅读心理学家施瓦茨(Barry Schwartz)的TED演讲:选择之困惑——为何多即是少,1百遍啊1百遍。请记住施瓦茨的演讲要点:

  • 更多的选择不代表更多的自由;
  • 更多的选择导致决策的延迟和降低的满意感;
  • 快乐之秘诀,在于降低自己的期望值。

最后,让我再抒情一把吧,

美好的事物总是离不开被墙的命运,让我们静静地期待那一天的来临… 也让我们在各自行业的努力,让下一代、下一代、下一代…(希望N<=1,如果N>=4,我做鬼也放不过你们!)不再拥有这一天。