1. 程式人生 > >我們一起學程式-五子棋

我們一起學程式-五子棋

前言


  小時候很喜歡玩電視上自帶的積木遊戲,那時候電子產品也不少,小學就認識了low和high兩個單詞,因此攢了零花錢搞到了高階版的遊戲--小霸王學習機,說錯了,是“遊戲機”,特別是一放暑假,插個小霸王遊戲機卡就能開始擼幾把魂鬥羅,坦克大戰,比夏天吃根冰棍可香多了,那時候沒有空調,不懼嚴寒酷暑的我們在這些小遊戲的陪伴下玩的不亦樂乎,陪我們度過一個又一個快樂冬夏,下圖為證,有沒有很熟悉的趕腳!

  看著這些畫面,帶著學習的心情的出發了,開始我們的五子棋大戰;砍柴磨刀互相不耽誤,先看看實現的網路版本效果吧,當然為了你看著舒服,體驗的開心,博主花了些功夫大致在客戶端(手機)做了適配,不至於你螢幕溢位,你爽我爽大家爽;簡單說明下本人主攻後端Java,前端不深入,頁面效果將就看看哈!

體驗效果


  看看你想下盤棋,你需要做什麼,首先你需要搶個位子啊【我媳婦說這個舉手的小人太醜了,能換個動漫嗎,我說我對漫畫沒啥審美,但是對另一半要求很高,所以我們在一起了,嘻,這個求生欲也特強了吧!】,然後你就到棋盤頁面了,點選舉手坐下,你就開始等,有人來了也舉手了你們就可以開始了,就像你去棋牌室打牌一模一樣,如果硬說有啥區別,咱們這是學習,他們那是賭博,好了,好了,扯遠了,簡單介紹下,五子棋的規則大家都懂,你五個棋子連成一條線就勝利了,一攻一防,勝率就看你的手段了,不知道你有沒有發現,博主為了不讓你有更多的束縛,連賬戶密碼都預設為你輸出了,同時為了讓程式不是那麼單調,下棋還能發聲,還實現了檢視覆盤,和線上聊天,聊天IP內容已經存檔,自動過濾色情資訊;博主為你們擔心的事情都精心考慮設計實現,是不是暖男你們說了算!下面兩個動畫分別是“準備過程”和“下棋過程”;

體驗地址:棋子棋體驗地址,有些功能可能有不完善的地方,功能也不特別太全,但基本功能應該是滿足了,如果沒有人陪你玩,你可以開兩個客戶端體驗或call me,一毛錢一把,歡迎來戰,如果有強迫症的或是有good idea,想要加入我們的修改的童鞋,歡迎留言加入我們,讓我們共同成長。

遊戲準備過程

下棋過程

 

關鍵技術簡介


  其實在開發完這個小遊戲後,回顧總了下,沒有啥高精尖的技術,前端頁面完全是html,css(樣式),js+jquery(指令碼語言),像“舉手準備”,“畫棋盤”等都是用到的這些前端技術,大家看到的淘寶頁面基本都是基於這些技術來完成的;後端用到的技術是websocket,很多面試官會問到websocket和http的區別,博主簡單介紹下,它是一個雙向通訊的協議,這個不難理解,如果你打我了,我也可以還手,不像http,它是單向的,比如你打我了,我就只能默默忍著,頂多就能回罵一句,絕不能還手,相信在這個人人平等的社會,你肯定喜歡websocket,他幫助我們實現了線上聊天,咿!我怎麼突然想到了QQ,感覺我像騰訊的高階攻城獅一樣牛掰;當然了,作為一個Java攻城獅,必須要理解的就是執行緒啦,我們裡面的“檢視覆盤”的功能就用到了Java讀寫檔案和多執行緒技術;是不是小小的程式還有大大的奧妙;好啦,就到這裡吧,明不明白都不要緊,因為我們後面詳細一點你就能明白了!

總結下:前端css+js實現畫頁面和互動,後臺Java+websocket實現儲存和通訊!相信有一點基礎的你,肯定也能實現這個程式;在實際開發中,前後端是兩個不同的崗位,前後端各司其職,當然有些小公司要求程式設計師都要會,所以有些時候在挑選公司時擦眼睛不是眼裡進了沙子,是為了看的更清楚!

 

從無到有思路


  這個程式博主一個人斷斷續續差不多花了兩個禮拜完成的,當然你肯定懷疑我是否有一氣呵成的能力,可以肯定的回答你,你的懷疑是對的,因為我也是人,我不能忘了吃飯和睡覺;最原始最原始的要追憶到兩週前,下圖是最初的版本,這是在idea控制檯輸出的,是的,你沒有看錯,五子棋程式實現了,“五子棋連線成功,恭喜你贏了”,說實話,打出這句話,連我這麼沒有追求的人都覺得他太枯燥了,生活它需要儀式感,你上面看到的頁面都是我在儀式感基礎上新增的;新事物,全新的頁面,至少我感受到了程式至少還有那麼一絲絲的美,是的,至少此刻我淪陷了,學程式我們要有意識的去追求一下她的美,不然他會教你從入門到放棄;

  慢慢的,“儀式感”讓我走向一條不歸路,首先需要解決的問題,就是棋盤的問題,現實中有木頭刻制的棋盤,網路上可以用程式碼生成的棋盤,現實的中棋盤有她獨特的美感,網路中這些棋盤他就更靈活了,每個方格,棋盤大小都是可調整的,成本微乎其微,有了棋盤那就需要把棋子落在棋盤上,這些可以用css+js畫出來的,還不用浪費木材,落子了每次都需要判斷這次走的這步棋是否連成了一個五子,如果連成五子就贏了,如果沒有就換對手下棋,每一步過程組合在一起就組成了棋盤,電腦有超強計算和儲存能力,人腦也有,但電腦沒有人腦靈活,人工智慧尚在起步,未來可期;

下完棋後想看下過程於是就有了“覆盤回看”功能;下棋要溝通就有了網路聊天室;棋牌室永遠不止一個座位,於是就需要支援多個座位,多人同時娛樂;當然你也可以說自己水平不夠,需要人機對戰,提升經驗,或者有人說遊戲贏了或輸了要有對應的獎懲機制,沒錯,你能想到的,有興趣的事情你都可以動手實現,興趣或者學習目的都好,總比無聊消磨時光來的更有意義;這就是我從無到有一步步構建出來的程式。歡迎和我一起討論學習!【公眾號:叫練】

程式碼實現過程


1.棋盤實現

  棋盤是用table表格畫的,像你看到博主實現的這個表格,橫軸是26,縱軸是23,每個格子的大小都是40畫素,不信你話可以量量哈,前端迴圈放置在div中就可以了,格子畫素是固定的,因為我們需要通過畫素計算最終得到座標,下棋過程就是通過點選格子,把白或黑的棋子(圖片)放置上去,那怎麼計算座標呢?你需要計算相對於棋盤點選的座標,也就是下圖我們的紅圈,我們知道棋盤表格(table)是整個頁面(body)中的一部分,js中click事件可以獲取到全域性事件event變數,所以能夠獲取到相對於body的event.left,event.top,再減去table的top,left就可以得到表格具體的畫素,table的top left可以通過getBoundingClientRect方法獲取;table的畫素再除40px就是具體座標了;大家在實現的時候還需要注意一個有效點選,只有在這個範圍內才算有效範圍,像我們這個程式,是以座標點15px都算有效點選;那麼無效點選範圍在16px-25px,也就是點選格子中間是無效的。舉個例子吧,比如我們現在算出表格具體座標是top:130px,left:70px,先計算這個座標是否有效,130%40=10, 70%40=30,都不在無效範圍內,說明是有效座標,我們這裡橫縱座標分別記為xy,那麼具體座標就y=130/40 =3,x=70/40=1,此時x座標需要加上1,因為x座標大於25,靠右,需要再除的基礎上加上1,所以最終橫縱座標為(2,3),這個過程涉及到一點小演算法,你對著下圖再思考下這個計算過程看看是否有不明白的,萬事開頭難!下面的圖方便大家理解!

2.計算棋子是否獲勝

  在寫這個之前先給大家爆料下,沒思考聽到這個問題前呼吸都是混亂的,說實話真的好難啊,什麼?還要實現,我TM不想學習了!嘿,兄嘚,別怕,有我在呢!怕解決不了問題,我牽著你,我們在一起吧!

  兵馬未動,圖表先行,給大家先上個圖吧,其實就四條線;一個"米"子,會寫米字,問題就解決了,也就是說每次落子後需要逐一判斷這4條線上是否有五個相同顏色的棋子,其實對應Java程式程式來說,他是用一個二維陣列形式來儲存的棋盤資料的,我們以紅色為中心點,其實這個過程就是通過方式1(縱向):先向上找,找紅色的棋子,找到一個計數器就+1,不是紅色的棋子直接break退出,同理向下找,找紅色的棋子,找到一個計數器就+1,不是紅色的棋子直接break退出,最終如果滿足5個相同顏色的棋就算成功,也不用再通過方式2,3,4找了,如果方式1不滿足條件,方式1就結束了,接下來就按方式2(左斜)查詢,也就是同樣的套路,一直到方式4為止,哈哈,是不是有點感覺了!先以方式1查詢舉例子吧,對二維陣列來說,以先上找後下找,上找:橫座標不變,縱座標遞減;下找:橫座標不變,縱座標遞增。再按方式三舉例,先右上後左下查詢,右上:橫座標+1,縱座標-1,左下:橫座標-1,縱座標+1;你看看原理還是很簡單吧!isSuccess方法是按方式1查詢的。

public boolean isSuccess(int x, int y,int color,int[][] oriData)  {
        boolean flag = false;
        int count = 0;
        //(2)上-下,左-右,左上-右下,右上-左下; 4種方式
        // 方式1 :上-下 x相同,y不同
        //上 縱座標遞減;
        for (int i = y-1; i>-1; i--) {
            //判斷同一顏色的子;
            if (oriData[x][i] != color) break;
            count++;
        }
        //下 縱座標遞增;
        for (int i = y+1; i<GameManager.Y; i++) {
            //判斷同一顏色的子;
            if (oriData[x][i] != color) break;
            count++;
        }
        //重置
        if(count >=4) return true;
        else count = 0;
        // 方式2
        // 方式3
        // 方式4
        return falg
    }
 

3.網路聊天室實現

不知道你有沒有發現,你有時候瀏覽網頁會彈出一些廣告頁,上面時長會自動彈出人工客服視窗,不需要登陸什麼的,你們能線上對話,其實這個功能可能就是websocket實現的,我們說過,websocket是雙向的,在實現上和http一樣,都是基於TCP實現的應用層協議,除了http是單向,ws是雙向的。在用法上http是以http://頭請求的短連線,是一次請求,ws是以ws://請求頭的長連線,我們在“五子棋遊戲大廳”點選位置進入棋盤頁面其實就是一個連線過程,前端通過ws://ip:port/xx形式連線後臺暴露的地址,會觸發後臺onOpen函式,每個連線在後臺都是以不同Session物件存在,通過Session可以獲取每個每個網頁的session的id,就可以呼叫sendText方法,通知對應的瀏覽器,這樣一個過程就實現了瀏覽器和後臺互動;比如我們的對話,每個桌子是儲存兩個使用者(Session),當其中某一個使用者傳送了某段話,會通知對應的瀏覽器,另一個使用者也會把這句話發給對應的瀏覽器,所以你看到的兩個頁面都出現了一樣的文字,其實原理還是比較簡單的。

我們畫個圖來理解下這個過程。

websocket通訊過程

 

下面這兩個段程式碼是websocket客戶端和伺服器互動的程式碼,只貼了部分原始碼,限於篇幅有限,如果需要完整程式碼,下方【環境部署部分】給了原始碼下載壓縮包

webSocket = new WebSocket("ws://"+serverIp+"/game/game/"+table+"/" + nickname);
 1 /**
 2  * websocket服務連線入口
 3  * update by jiaolian 2020 8 12
 4  * 公眾號:叫練
 5  */
 6 @ServerEndpoint("/game/{table}/{nickname}")
 7 public class WebserviceSession {
 8     @OnOpen
 9     public void onOpen(@PathParam("table") String tableName,@PathParam("nickname") String nickname, Session session) throws IOException {}
10     @OnMessage
11     public void onMessage(@PathParam("table") String tableName,String message, @PathParam("nickname") String nickname) {}
12     @OnClose
13     public void onClose(Session session,@PathParam("nickname") String nickname,@PathParam("table") String roomName) {}
14 }

 

4.檢視覆盤實現

這個功能是執行緒+檔案實現的,既然需要檢視覆盤,那說明我們需要先寫覆盤,覆盤其實就是寫一個檔案,檢視覆盤就是讀檔案,在一局棋中,每次落子就會寫一行資料,每行資料會記錄棋子的x,y座標和棋子的顏色,按空格隔開,我們程式碼中Table【棋盤】物件儲存color約定1為黑子,2為白子,當一局遊戲結束後,檢視覆盤會生成對應一個執行緒,以每秒一行的速度讀取儲存在伺服器的檔案,檔名以“桌號+時間戳.wzq”命名,如:1_2020_09_01_15_45_15.wzq,每行資料讀取出來後,會單獨發給我們的User【Session】,瀏覽器收到通知後渲染頁面就完事了;貼一段簡單的程式碼;其中Table類是用來儲存棋盤的;User類是用來儲存使用者資訊的;finishReplayer()方法是讀取檔案,sendTextSingle()方法負責給瀏覽器傳送資訊;可以參考上面websocket通訊過程;

/**
 * @author :jiaolian
 * @date :Created in 2020-08-14 13:30
 * @description:桌號
 * @modified By:
 * 公眾號:叫練
 */
public class Table {

    //儲存使用者資訊
    @JsonIgnore
    private List<User> userList = new CopyOnWriteArrayList<>();
    //桌號名
    private String name;
    //預設棋盤
    private int[][] oriData = new int[GameManager.X][GameManager.Y];
    //約定:1 黑子 2 白子  -1 無子(頁面選棋子) 此時應該走動棋子顏色;
    //棋子顏色<預設黑子先走>
    private int color = CONST.CHEER_COLOR.BLACK;
    //對應的檔名<覆盤> 最近一次
    private String fileName;
    //桌子狀態; 已開始/未開始
    private int tableStatus;
    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
    private List<User> tempList;
}
/**
 * @author :jiaolian
 * @date :Created in 2020-08-14 13:56
 * @description:線上使用者
 * @modified By:
 * 公眾號:叫練
 */
public class User {

    //websocket sessoin;
    private Session session;
    private String nickname;
    //棋手是否已準備;
    private boolean isPrepared;

    //使用者的覆盤執行緒;key規則:桌號_使用者名稱,為什麼這麼設計? 因為一個使用者可能在多個桌子上同時遊戲;
    //val值設計成Boolean,主要是查詢對應桌數使用者執行緒是否執行完畢; 如果有疑問,請諮詢叫練;
    //設計這個目的主要實現:不能同時多次點選覆盤;
    private ConcurrentHashMap<String,Boolean> reviewThreadMap = new ConcurrentHashMap();
}
public void finishReplayer(Table table,User user) {
        inputStream = new FileInputStream(table.getFileName());
        inputStreamReader = new InputStreamReader(inputStream);
        bufferedReader = new BufferedReader(inputStreamReader);
        while ((res = bufferedReader.readLine()) != null) {
            System.out.println(res);
            //誰點誰看
            GameManager.sendTextSingle(table,res,user);
            try {
                //休眠一秒
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

 

遇到問題


  • springMVC jackson返回實體物件通過不能修改後不能正常轉化為json傳遞到前端?

在實現上述功能過程中,說沒遇到問題是不可能的,很多都是業務的bug,就不詳細介紹了 ,該五子棋程式沒有寫資料庫,都是在記憶體處理的,其中渲染首頁桌子頁面的資料都是從後臺一個大的List<User>獲取的,如果List資料在修改後,再重新整理首頁,會出現棧溢位,不能寫json的情況,目前處理的方式是用了@JsonIgnore忽略物件返回到前端,把這個List的User物件重新複製了一份到一個新的List<User>,查看了原始碼,暫時還沒找到原因!找到原因的童鞋歡迎下方留言,一起探討下!感謝了

Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind

 

 

原始碼下載地址及注意事項

  • JDK1.8及以上;有lamda表示式需要支援
  • maven;需要下載websocket包
  • 原始碼