前言小时候很喜欢玩电视上自带的积木游戏,那时候电子产品也不少,小学就认识了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();
}
}
}
遇到问题
在实现上述功能过程中,说没遇到问题是不可能的,很多都是业务的bug,就不详细介绍了 ,该五子棋程序没有写数据库,都是在内存处理的,其中渲染首页桌子页面的数据都是从后台一个大的List<User>获取的,如果List数据在修改后,再刷新首页,会出现栈溢出,不能写json的情况,目前处理的方式是用了@JsonIgnore忽略对象返回到前端,把这个List的User对象重新复制了一份到一个新的List<User>,查看了源码,暂时还没找到原因!找到原因的童鞋欢迎下方留言,一起探讨下!感谢了 Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind
源码下载地址及注意事项 |
|