熬夜整理10个Java经典小游戏项目开发源代码,含可执行程序及源码
我相信现在有很多的小伙伴都已经看到了现在网上都在说Java找不到工作,
其实我想说的就是现在的那些找不到Java工作的其实最主要的就是没有项目实战的经验,这个才是最重要的,今天我给大家带来了10个Java经典的Java项目,让你在面试中更加有经验,竞争也是比较强一些。
当然了现在有很多的小伙伴们都在说现在有很多的Java项目开发的源代码不知道应该如何的更好的学习,同时现在也是有很多的小白在学习项目开发的时候找不到自己的学习方式和方法。
【文末有获取方式】
【文末有获取方式】
【文末有获取方式】现在这种免费经典还有实战提升自己的面试竞争的项目真的不多了,所以今天在这里我也是特地整理了10个Java的小项目,内涵可执行代码和源代码,非常的适合现在的入门和进阶的小伙伴去学习。无论是做毕业设计还是项目答辩都是不错的资源。如果说你想要学习这些项目程序的话那么在这里我还是非常的建议大家可以学习好这些项目。
10个Java经典小游戏:
1、超级玛丽
2、黄金矿工
3、坦克大战
4、王者荣耀
5、飞机大战
6、扫雷
7、推箱子
8、大鱼吃小鱼
9、飞翔的小鸟
10、俄罗斯方块
好了,上面的学习资料已经整理打包完毕了!!!想要那个Java小游戏项目直接带走
下图按步骤获取~
用Excel编写俄罗斯方块
看到这个题目,想必你一定会感到非常惊讶,什么,Excel居然能开发游戏?没错,Excel的强大取决于使用者,遇强则强,遇弱则弱。但我这篇文章并不是为了展示Excel使用过程中的奇技淫巧,而是主要写给那些准备学习编程但又没什么计算机基础的那些人,或者对Excel感兴趣的那些人。如果你已经是一名有经验的开发者,我也希望你能提出宝贵的意见。
对于那些正在学习编程的人,尤其是那些从其他领域跨入这个行业的人,兴趣才是最大的动力。我从事计算机编程这么长时间,感觉编程是一件非常有意义的事情。然而我却我经常听到有些人,尤其是那些在校学生们抱怨说编程学起来太枯燥了,坚持不下去。我觉得这部分人一方面是方向错了,另一方面就是在实际学习过程中对自己做出成果没有任何的成就感,而后者,往往占据了重要原因。
我认为,对于编程的初学者,选择第一门语言应该具备下面两个特点:
1),尽可能简单,尽量少与底层硬件(诸如内存管理等)相关联,调试方便,IDE界面简单;
2),功能相对强大,能开发各种小插件工具;
就目前行业内比较常用的编程语言中,能够同时满足上述两个特点的也就数Python,office for VBA,Java了,然而,Python虽然简单功能又强大,但是需要配置环境,而且要安装臃肿的IDE,这些对于初学者来说,无形之中又增加了学习成本,更别说Java了,所以剩下的就数VBA(Visual Basic for Application)了。VBA属于visual basic语言的子集,除了继承了部分vb功能外,还特意针对一些软件做了接口封装,使用方便。有人说VBA语法太随意,对于初学者不是件好事,如果学C的话,将来学C++就简单很多了。我不同意这种观点,对于前者,仁者见仁,但后者就是在扯淡,因为C++是一门极其复杂的编程语言,除了继承了C的繁琐指针以外,还衍生出了诸如多重继承、类模板、智能指针等恐怖级别的编程范式,所以说,对于初学者,我不建议直接就去学C++.
为什么选择VBA作为初学者的语言呢,因为他除了满足上面所说的两个特点外,还有一些其他的优点,诸如:
1),使用简单:不需要安装开发工具,更不需要配环境、安装语言包,只要你电脑里面有office软件即可。
2),用途广泛:几乎所有的工程软件、办公软件都支持用VBA做二次开发,例如财务人员如果发现Excel自带的公式有局限性的话,完全可以自己用VBA开发自己需要的控件;机械设计人员如果学了VBA后可以开发一些自己需要的代码块,极大程度地提高自己CAD的绘图速度。很难想象,Excel的重度用户尤其是财务人员不懂VBA的话他的工作量有多可怕。
3),调试简单方便。
所以,这次我也选择VBA作为这次编写Demo的语言,为了照顾更多的初学者,我将每一步的细节都尽可能地呈现出来,由于每个Excel版本不一样,我电脑用的是2010版的,所以,我就用2010版进行说明,其他版本也一样,只是界面可能稍有区别。我相信,只要亲手按照我的方法做出这个游戏,除了你将认识到Excel的强大之处外,你也将逐步体会到编程的乐趣。鉴于时间所限,内容可能有部分疏忽之处,还望大家提出改正。
下面是正文:
首先看一下游戏最终大致的效果图:
游戏大致效果图
首先我们思考一下俄罗斯方块游戏的大致架构:
1),初始化界面:创建方块所需要的地图。
2),随机生成俄罗斯方块:俄罗斯方块总共有7种形态,每种形态均有4个框格组成,每种方块各对应一种颜色。可以创建一个数组存储每种方块的坐标,再用另外一个数组存储方块的对应的颜色。
3),移动旋转方块:分为向左,向右,向下。擦拭完后重新绘制,产生移动旋转的效果。
4),没产生新的方块,都进行一定速度的下落,一旦碰到障碍物,不能下落,再生成新的方块。
5),不断扫描是否有任何一行填满,如果为真,则本行删除,上面下落。每行积分为10分。
首先创建一个Excel文件,随意命名。打开后,由于office默认隐藏了开发工具状态栏,所以我们需要在Excel选项>自定义功能区将其调出来,将其勾选后确认:
随后,我们发现主界面多了开发工具的选项:
我们再在Sheet1表格里面将A~K列的列宽大致调成跟行高一样,让它大致称为一个正方形的区域:
我们点击Visual Basic菜单,打开编写代码界面,我们插入首先插入一个代码模块,用于编写我们自己的代码:
插入代码块
由于方块有7中形状,为了让程序绘制方便,我用一个三维数组存储所有形状的坐标,每种形状都有一个中心坐标(0,0),其余三个方框的坐标按照中心坐标来计算相对坐标,例如丁字形状的方块:
如果中心的坐标为(0,0)的话,剩余三个从右到左逆时针三个坐标分别为(0,1),(-1,0),(0,-1),之所以将垂直方向作为X轴是因为Excel坐标的固有属性,例如Cells(1,2)代表第一行第二列个单元格。每个方块的对象有中心坐标,颜色,形状等属性,所以我们需要定义几个模块变量,代码如下:
考虑到每种方块坐标的不一样,所以我采用一个三维数组来存储方块坐标,为了方便,我采用VBA自带的接口Array()函数给自己的ShapeArr()赋值。同时要在主界面上显示出玩家的分数,所以这两个功能我们作为一个初始化函数,我们定义一个Init()子过程,代码如下:
这时候,我们初始化变量与功能的函数基本上实现了。下一步我们要编写生成一个新方块的函数,为了实现程序的模块化,低耦合,我们将本功能封装成一个独立的函数。
由于绘制函数DrawBlock()需要根据传递过来的做标数组来进行绘制,同时我们需要知道这个方块的中心坐标在哪里,还有对应的颜色,所以我们需要传递4个参数,其中数组需要传址(ByRef),代码如下:
至此,绘制函数已经完成,为了防止Bug出现,我们需要测试一下,我们再定义一个入口函数,Start(),同时定义一个临时方块数组,调用DrawBlock()进行测试。在主界面添加一个按钮,将其指定到Start函数,并将其拖入合适的位置:
指定函数
Start函数代码如下:
我们运行一下,看看效果:
好,测试结果显示完全没问题。
由于后期我们需要在表格最上方的固定位置不断随机生成新的方块,所以我们应该将此功能再次封装为一个独立函数,为了防止产生伪随机数,我们采用Timer作为当前种子,随机生成0~6之间的数组,每个对应形状数组与颜色数组的索引,代码如下:
既然生成了方块,我们就要让方块能够左右下移动,分为三个方向。移动的方法是首先擦除掉当前的方块,再根据规定的移动方向,计算新的坐标,再根据新的坐标重新绘制,这样就产生了移动的现象。但是,在移动之前,我们需要判断是否可以移动。
首先,我们需要编写判断是否能够移动或者旋转的函数CanMoveRotate,此函数很简单,也就是将移动后或者旋转后的坐标传递过来,判断是否越界,或者当前位置上是否有其他颜色即可,代码如下:
我们还需要一个擦除当前方块的函数EraseBlock,根据传递过来的坐标直接擦拭掉,代码如下:
我们再编写移动方块的函数MoveBlock,我们规定,形参direction代表方向,-1代表向左,0代表向下,1代表向右,注意移动后需要保存当前坐标。新增形参direction,代码如下:
移动方块实现后,我们再来编写旋转方块函数RotateBlock,这里我们统一规定为逆时针旋转。跟移动函数一样,方法也是先擦除掉旧坐标的后,再根据新坐标绘制出新的方块。只不过旋转稍微麻烦一点。
不难计算出,假如一个向量(x,y)在逆时针旋转90度后的坐标为(-y,x).根据这个公式,编写旋转函数。但是注意事先应该先判断是否达到旋转的条件。代码如下:
这时候,旋转、移动函数均已编写完毕。为了能够让游戏相应键盘事件,我们需要在对应的工作表代码层添加事件函数,注意这里我们需要调用Windows API。我们规定键盘的左键为方块向左MoveObject(-1),右键为方块向右MoveObject(1),下键为方块向下MoveObject(0),上键为方块旋转RotateObject()。我们再Sheet1工作表里面编写如下WorkSheet事件代码:
由于我们自己定义的MoveBlock与RotateBlock包类对象的形参,因此事件响应中不能直接调用。在这里我们将用两个 Public 的MoveObject与RotateObject函数在类模块里面再次封装,方便事件调用,代码如下:
至此,方块功能方面已经完全实现,我们随机生成一个进行测试:
为了方便,我们将按钮1里面的文字更改成“启动游戏四个字”:
随后,开始编写程序自动运行的代码。由于俄罗斯方块是生成方块后,按照一定的速度进行下降,一旦碰到障碍物后本方块结束,再生成新的方块,如此循环。由于VBA不支持定时器,所以我们采用while(true)循环的方法进行不断生成方块。为了避免CPU资源过度占用,我们在循环之间加入延时函数,供循环调用,代码如下:
在下降过程中,我们需要知道是否某一行已经满了,判断的方法很简单,查询整行是否全部涂色即可。如果满了,我们删除本行,同时将第一行到本行下降填充。同时更新分数。因此我们再引入一个函数DeleteFullRow,代码如下:
再在Start()函数里面添加while循环,上面两个函数一样添加进去代码如下:
到这里,本游戏的编写就算彻底结束了,点击Sheet1界面上面的“按钮1”按钮即可开始游戏。我们再试玩一下,向左键代表向左,右键代表向右,上键代表旋转,下键代表下降。看一下效果:
哈哈,试玩结束没问题,非常完美,过程虽然长久,但值得你细细研究,也希望你能从中够体会到编程的乐趣。
函数式编程的 Java 编码实践:利用惰性写出高性能且抽象的代码
本文会以惰性加载为例一步步介绍函数式编程中各种概念,所以读者不需要任何函数式编程的基础,只需要对 Java 8 有些许了解即可。
程序员的梦想就是能写出 “高内聚,低耦合”的代码,但从经验上来看,越抽象的代码往往意味着越低的性能。机器可以直接执行的汇编性能最强,C 语言其次,Java 因为较高的抽象层次导致性能更低。业务系统也受到同样的规律制约,底层的数增删改查接口性能最高,上层业务接口,因为增加了各种业务校验,以及消息发送,导致性能较低。
对性能的顾虑,也制约程序员对于模块更加合理的抽象。
一起来看一个常见的系统抽象,“用户” 是系统中常见的一个实体,为了统一系统中的 “用户” 抽象,我们定义了一个通用领域模型 User,除了用户的 id 外,还含有部门信息,用户的主管等等,这些都是常常在系统中聚合在一起使用的属性:
这看起来非常棒,“用户“常用的属性全部集中到了一个实体里,只要将这个 User 作为方法的参数,这个方法基本就不再需要查询其他用户信息了。但是一旦实施起来就会发现问题,部门和主管信息需要远程调用通讯录系统获得,权限需要远程调用权限系统获得,每次构造 User 都必须付出这两次远程调用的代价,即使有的信息没有用到。比如下面的方法就展示了这种情况(判断一个用户是否是另一个用户的主管):
为了能在上面这个方法参数中使用通用 User 实体,必须付出额外的代价:远程调用获得完全用不到的权限信息,如果权限系统出现了问题,还会影响无关接口的稳定性。
想到这里我们可能就想要放弃通用实体的方案了,让裸露的 uid 弥漫在系统中,在系统各处散落用户信息查询代码。
其实稍作改进就可以继续使用上面的抽象,只需要将 department, supervisor 和 permission 全部变成惰性加载的字段,在需要的时候才进行外部调用获得,这样做有非常多的好处:
- 业务建模只需要考虑贴合业务,而不需要考虑底层的性能问题,真正实现业务层和物理层的解耦
- 业务逻辑与外部调用分离,无论外部接口如何变化,我们总是有一层适配层保证核心逻辑的稳定
- 业务逻辑看起来就是纯粹的实体操作,易于编写单元测试,保障核心逻辑的正确性
但是在实践的过程中常会遇到一些问题,本文就结合 Java 以及函数式编程的一些技巧,一起来实现一个惰性加载工具类。
Java 8 引入了全新的函数式接口 Supplier,从老 Java 程序员的角度理解,它不过就是一个可以获取任意值的接口而已,Lambda 不过是这种接口实现类的语法糖。这是站在语言角度而不是计算角度的理解。当你了解了严格(strict)与惰性(lazy)的区别之后,可能会有更加接近计算本质的看法。
因为 Java 和 C 都是严格的编程语言,所以我们习惯了变量在定义的地方就完成了计算。事实上,还有另外一个编程语言流派,它们是在变量使用的时候才进行计算的,比如函数式编程语言 Haskell。
所以 Supplier 的本质是在 Java 语言中引入了惰性计算的机制,为了在 Java 中实现等价的惰性计算,可以这么写:
Supplier 还存在一个问题,就是每次通过 get 获取值时都会重新进行计算,真正的惰性计算应该在第一次 get 后把值缓存下来。只要对 Supplier 稍作包装即可:
通过 Lazy 来写之前的惰性计算代码:
通过这个惰性加载工具类来优化我们之前的通用用户实体:
一个简单的构造 User 实体的例子如下:
这看起来还不错,但当你继续深入使用时会发现一些问题:用户的两个属性部门和主管是有相关性,需要通过 rpc 接口获得用户部门,然后通过另一个 rpc 接口根据部门获得主管。代码如下:
但是现在 department 不再是一个计算好的值了,而是一个惰性计算的 Lazy 对象,上面的代码又应该怎么写呢?\”函子\” 就是用来解决这个问题的
快速理解:类似 Java 中的 stream api 或者 Optional 中的 map 方法。函子可以理解为一个接口,而 map 可以理解为接口中的方法。
1 函子的计算对象
Java 中的 Collection< T>,Optional< T>,以及我们刚刚实现 Lazy< T>,都有一个共同特点,就是他们都有且仅有一个泛型参数,我们在这篇文章中暂且称其为盒子,记做 Box< T>,因为他们都好像一个万能的容器,可以任意类型打包进去。
2 函子的定义
函子运算可以将一个 T 映射到 S 的 function 应用到 Box< T> 上,让其成为 Box< S>,一个将 Box 中的数字转换为字符串的例子如下:
在盒子中装的是类型,而不是 1 和 \”1\” 的原因是,盒子中不一定是单个值,比如集合,甚至是更加复杂的多值映射关系。
需要注意的是,并不是随便定义一个签名满足 Box< S> map(Function< T,S> function) 就能让 Box< T> 成为函子的,下面就是一个反例:
所以函子是比 map 方法更加严格的定义,他还要求 map 满足如下的定律,称为 函子定律(定律的本质就是保障 map 方法能如实反映参数 function 定义的映射关系):
- 单位元律:Box< T> 在应用了恒等函数后,值不会改变,即 box.equals(box.map(Function.identity()))始终成立(这里的 equals 只是想表达的一个数学上相等的含义)
- 复合律:假设有两个函数 f1 和 f2,map(x -> f2(f1(x))) 和 map(f1).map(f2) 始终等价
很显然 Lazy 是满足上面两个定律的。
3 Lazy 函子
虽然介绍了这么多理论,实现却非常简单:
可以很容易地证明它是满足函子定律的。
通过 map 我们很容易解决之前遇到的难题,map 中传入的函数可以在假设部门信息已经获取到的情况下进行运算:
4 遇到了更加棘手的情况
我们现在不仅可以构造惰性的值,还可以用一个惰性值计算另一个惰性值,看上去很完美。但是当你进一步深入使用的时候,又发现了更加棘手的问题。
我现在需要部门和主管两个参数来调用权限系统来获得权限,而部门和主管这两个值都是惰性的值。先用嵌套 map 来试一下:
返回值的类型好像有点奇怪,我们期待得到的是 Lazy< Set< String>>,这里得到的却多了一层变成 Lazy< Lazy< Set< String>>>。而且随着你嵌套 map 层数增加,Lazy 的泛型层次也会同样增加,三参数的例子如下:
这个就需要下面的单子运算来解决了。
快速理解:和 Java stream api 以及 Optional 中的 flatmap 功能类似
1 单子的定义
单子和函子的重大区别在于接收的函数,函子的函数一般返回的是原生的值,而单子的函数返回却是一个盒装的值。下图中的 function 如果用 map 而不是 flatmap 的话,就会导致结果变成一个俄罗斯套娃–两层盒子。
单子当然也有单子定律,但是比函子定律要复杂些,这里就不做阐释了,他的作用和函子定律也是类似,确保 flatmap 能够如实反映 function 的映射关系。
2 Lazy 单子
实现同样很简单:
利用 flatmap 解决之前遇到的问题:
三参数的情况:
其中的规律就是,最后一次取值用 map,其他都用 flatmap。
3 题外话:函数式语言中的单子语法糖
看了上面的例子你一定会觉得惰性计算好麻烦,每次为了取里面的惰性值都要经历多次的 flatmap 与 map。这其实是 Java 没有原生支持函数式编程而做的妥协之举,Haskell 中就支持用 do 记法简化 Monad 的运算,上面三参数的例子如果用 Haskell 则写做:
Java 中虽然没有语法糖,但是上帝关了一扇门,就会打开一扇窗。在 Java 中可以清晰地看出每一步在做什么,理解其中的原理,如果你读过了本文之前的内容,肯定能明白这个 do 记法就是不停地在做 flatmap 。
目前为止,我们写的 Lazy 代码如下:
利用 Lazy 我们写一个构造通用 User 实体的工厂:
工厂类就是在构造一颗求值树,通过工厂类可以清晰地看出 User 各个属性间的求值依赖关系,同时 User 对象能够在运行时自动地优化性能,一旦某个节点被求值,路径上的所有属性的值都会被缓存。
虽然我们通过惰性让 user.getDepartment() 仿佛是一次纯内存操作,但是他实际上还是一次远程调用,所以可能出现各种出乎意料的异常,比如超时等等。
异常处理肯定不能交给业务逻辑,这样会影响业务逻辑的纯粹性,让我们前功尽弃。比较理想的方式是交给惰性值的加载逻辑 Supplier。在 Supllier 的计算逻辑中就充分考虑各种异常情况,重试或者抛出异常。虽然抛出异常可能不是那么“函数式”,但是比较贴近 Java 的编程习惯,而且在关键的值获取不到时就应该通过异常阻断业务逻辑的运行。
利用本文方法构造的实体,可以将业务建模上需要的属性全部放置进去,业务建模只需要考虑贴合业务,而不需要考虑底层的性能问题,真正实现业务层和物理层的解耦。
同时 UserFactory 本质上就是一个外部接口的适配层,一旦外部接口发生变化,只需要修改适配层即可,能够保护核心业务代码的稳定。
业务核心代码因为外部调用大大减少,代码更加接近纯粹的运算,因而易于书写单元测试,通过单元测试能够保证核心代码的稳定且不会出错。
仔细想想,刚刚做了这么多,目的就是一个,让签名为 C f(A,B) 的函数可以无需修改地应用到盒装类型 Box< A>和 Box< B> 上,并且产生一个 Box< C>,在函数式语言中有更加方便的方法,那就是应用函子。
应用函子概念上非常简单,就是将盒装的函数应用到盒装的值上,最后得到一个盒装的值,在 Lazy 中可以这么实现:
不过在 Java 中实现这个并没有什么用,因为 Java 不支持柯里化。
柯里化允许我们将函数的几个参数固定下来变成一个新的函数,假如函数签名为 f(a,b),支持柯里化的语言允许直接 f(a) 进行调用,此时返回值是一个只接收 b 的函数。
在支持柯里化的情况下,只需要连续的几次应用函子,就可以将普通的函数应用在盒装类型上了,举个 Haskell 的例子如下(< *> 是 Haskell 中应用函子的语法糖, f 是个签名为 c f(a, b) 的函数,语法不完全正确,只是表达个意思):
参考资料
- 在 Java 函数式类库 VAVR 中提供了类似的 Lazy 实现,不过如果只是为了用这个一个类的话,引入整个库还是有些重,可以利用本文的思路直接自己实现
- 函数式编程进阶:应用函子 前端角度的函数式编程文章,本文一定程度上参考了里面盒子的类比方法:https://juejin.cn/post/6891820537736069134?spm=ata.21736010.0.0.595242a7a98f3U
- 《Haskell函数式编程基础》
- 《Java函数式编程》
作者 | 悬衡
原文链接:http://click.aliyun.com/m/1000304340/
本文为阿里云原创内容,未经允许不得转载。
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。