前言

为什么写这系列文章

虽然 compose 正式版已经出来很久了,也有很多大佬写了很多教程文章和实例 demo ,但是对于 compose 其实我也还是一知半解的。

特别是对于 compose 的状态管理,由于 compose 声明式的特性,如果不对状态进行完善的管理,那么界面代码和业务逻辑代码将会杂糅在一起,导致代码可读性、可维护性非常差。

很多大佬们都说使用 MVI 架构来管理 compose 的状态是天生一对。

我也尝试使用 MVI 架构编写了一个简单的游戏:基于 Jetpack Compose,使用MVI架构+自定义布局实现的康威生命游戏。

不过,这就存在一个很大的问题,大佬们几乎都是使用 ViewModel 来实现 MVI 架构。但是, ViewModel 是强依赖于安卓原生 API,这就导致无法将这个项目移植到 compose-jb 实现跨平台。

我也收集了大佬们的解决方案,无非以下几种:

  1. 不再使用安卓的 ViewModel,而是自己参照源码手撸一个跨平台的类似功能的库。例如:Compose Mutiplatform 实战联机小游戏
  2. 给不同的平台封装不同的状态管理实现类,例如:不止 Android,Compose Multiplatform 初探
  3. 索性直接不使用 ViewModel ,改用其它可以跨平台使用的库,例如:Compose 下的 MVI 架构实践,用 Compose 写业务逻辑,取代 ViewModel

回到我们标题的问题,为什么我要写这系列文章?

因为现在对于 compose 的使用方法,最佳实践都尚在探索期。我也不确定什么才是最适合自己使用的,唯有多尝试才知道。

所以我觉得我应该再试试不同的实现方式,这次就使用上面所说的方法3进行尝试。

由于这系列文章不同于以往采用的是代码已经写完并且测试没问题后才开始撰写文章,而是采用边尝试写代码,边记录的方式。

所以文章可能会有所纰漏或错误,但是我会在发现问题后第一时间在后续文章中说明并校正。

黑白棋是什么

黑白棋(英语:Reversi),又称翻转棋、苹果棋或奥赛罗棋(Othello),是一种双人对弈的棋类游戏。

一般棋子双面为黑白两色,故称“黑白棋”;因为行棋之时将对方棋子翻转,变为己方棋子,故又称“翻转棋”(Reversi);棋子双面为红、绿色的称为“苹果棋”,因苹果有红苹果和青苹果。

游戏规则:

棋盘共有8行8列共64格。开局时,棋盘正中央的4格先置放黑白相隔的4枚棋子(亦有求变化相邻放置)。通常黑子先行。双方轮流落子。只要落子和棋盘上任一枚己方的棋子在一条在线(横、直、斜线皆可)夹着对方棋子,就能将对方的这些棋子转变为我己方(翻面即可)。如果在任一位置落子都不能夹住对手的任一颗棋子,就要让对手下子。当双方皆不能下子时,游戏就结束,子多的一方胜。

以上内容和图片摘自 维基百科 黑白棋 条目

实现思路

我的目标

这个项目的目标是首先使用 Jetpack compose 实现安卓端的黑白棋游戏,然后移植到 compose-jb 实现跨平台。

我会优先实现单机版游戏,后期考虑加入联机游戏。

对于游戏的状态管理,依旧使用 MVI 作为架构,但是不再使用 Jetpack ViewModel 实现,而是尝试使用 composable 和 Flow 做一个平台无关的状态管理。

关于单机游戏的AI

由于这个项目的目的是找到对于我来说 compose 开发的最佳实践,所以算法逻辑不在这个项目的重点。

但是如果要做单机游戏,对战AI是必不可少的,所以我找到了一个开源项目 reversi , 之后项目中的AI算法将使用这个项目的,部分UI可能也会直接从这个项目里面拿。

准确的说,我现在是在将这个项目移植为使用 compose 实现。 (*_*)

所以在开始之前我们需要先简单分析一下这个项目的组成结构。

不得不说,大佬的项目看起来就是赏心悦目,各个模块分工明确:

模块 说明
activity 这个不用多解释,就是 Activity,我们需要留意的是 GameActivity ,承载游戏界面的 Activity
bean 一些数据 bean
game AI核心算法逻辑自定义的棋盘view
util 一些工具方法
widget 大佬在这里封装了几个 dialog

这是大佬的游戏主界面:

在这里我们需要重点关注的是 GameActivity 这个 Activity 承载了游戏的主界面和控制逻辑。

game layout 的布局结构如下:

可以看到,除了棋盘使用的是自定义 view : ReversiView 外,其他都是使用基础控件组成的 游戏信息控制按钮

ReversiView 的内容这里我们就不具体看了,如果我们要移植到 compose 的话可以很轻松的直接将它的代码 “copy” 过来并转成 compose 的 canvas 代码。当然,我们也可以完全自己重写,具体的绘制内容,我们将在下一篇文章详细说明。

这里我们着重看一下如何使用它的AI算法。

首先,在 GameActivity 中,他使用 setOnTouchListener 监听了 ReversiView 的触摸事件:

reversiView.setOnTouchListener(new OnTouchListener() {boolean down = false;int downRow;int downCol;@Overridepublic boolean onTouch(View v, MotionEvent event) {if (gameState != STATE_PLAYER_MOVE) { // 没有轮到玩家下子,直接返回return false;}float x = event.getX();float y = event.getY();if (!reversiView.inChessBoard(x, y)) {  // 判断是否在棋盘范围内return false;}// 计算棋盘的横纵坐标int row = reversiView.getRow(y);int col = reversiView.getCol(x);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:// 按下时记录按下的棋盘坐标down = true;downRow = row;downCol = col;break;case MotionEvent.ACTION_UP:if (down && downRow == row && downCol == col) { // 只有在抬起坐标和按下坐标一致时才继续处理down = false;if (!Rule.isLegalMove(chessBoard, new Move(row, col), playerColor)) { // isLegalMove 这个方法用于判断往这个坐标下子是否合法return true;}// 判断完成后开始按照规则更新数据和UIMove move = new Move(row, col);List<Move> moves = Rule.move(chessBoard, move, playerColor);reversiView.move(chessBoard, moves, move, playerColor);// 轮到AI下子aiTurn();}break;case MotionEvent.ACTION_CANCEL:down = false;break;}return true;}
});

我已经把不重要的代码删除,并加上了注释。

这里我们需要关注更新数据的方法:Rule.move() ;AI下子的方法:aiTurn()

aiTurn() 这个方法首先会调用 Rule.analyse() 方法计算当前玩家和 AI 拥有的棋子数量,然后根据计算出的数量更新游戏界面,并将游戏状态更改为 STATE_AI_MOVE 即轮到 AI 下子,最后启动一个新的线程 new ThinkingThread(aiColor).start(); 用于运行AI算法。

ThinkingThread 的代码如下:

class ThinkingThread extends Thread {private byte thinkingColor;public ThinkingThread(byte thinkingColor) {this.thinkingColor = thinkingColor;}public void run() {try {sleep(20 * 100);} catch (InterruptedException e) {e.printStackTrace();}int legalMoves = Rule.getLegalMoves(chessBoard, thinkingColor).size();if (legalMoves > 0) {Move move = Algorithm.getGoodMove(chessBoard, depth[difficulty], thinkingColor, difficulty);List<Move> moves = Rule.move(chessBoard, move, thinkingColor);reversiView.move(chessBoard, moves, move, thinkingColor);}updateUI.handle(0, legalMoves, thinkingColor);}
}

可以看到,这个线程首先暂停了自己 2000 ms …… 额,为了让人看起来这个算法很厉害需要算 2s 吗?哈哈~

不管这个奇怪的暂停,咱们接着往下看。

首先,调用 Rule.getLegalMoves().size(); 获取到所有可以下子的位置数量,如果数量大于 0 则继续处理。

通过调用 Algorithm.getGoodMove() 获取到算法计算出的最佳下子位置,然后更新数据和UI。

因为这里我们只需要知道怎么复用作者的算法即可,所以我们不深究算法的具体实现。

如果感兴趣的可以看看作者自己写的解读:android黑白棋游戏实现

综上所述,我们已经明了应该如何使用这位大佬编写的AI算法了。

基础架构demo

正如上文所述,我们现在需要使用 Flow 和 composable 实现一个平台无关的数据管理框架。

这里我们按照上文大佬的思路编写一个简单的 demo 验证可行性:

@Composable
fun Demo() {val channel = remember { Channel<Action>() }val flow = remember(channel) { channel.consumeAsFlow() }val state = presenter(action = flow)Column {Text(text = state.count.toString())Button(onClick = {channel.trySend(Action.ClickAdd)}) {Text(text = "ADD")}}
}sealed class Action {object ClickAdd : Action()
}data class State (val count: Int = 0,
)@Composable
fun presenter(action: Flow<Action>,
): State {var count by remember { mutableStateOf(0) }LaunchedEffect(action) {action.collect { action: Action ->when (action) {is Action.ClickAdd -> count++}}}return State(count = count)
}

因为这里只是为了验证可行性,所以我直接把所有代码写到了一起,实际编写时肯定是要分开的

Android 运行效果:

Desktop 运行效果:

总结

经过上面的分析和实践,证明不依赖安卓的 ViewModel 确实是可以实现 MVI 架构,这就意味着之后移植至 compose-jb 将更加方便。

当然,本文只是简单的梳理了一下思路,从下一篇开始我们将正式开始编写。

下一篇我们介绍怎么绘制棋盘和棋子,以及编写界面布局。

跟我一起使用 compose 做一个跨平台的黑白棋游戏(1)整体实现思路相关推荐

  1. 跟我一起使用 compose 做一个跨平台的黑白棋游戏(2)界面布局

    前言 在上一篇文章中,我们讲解了实现这个游戏的总体思路,这篇文章我们将讲解如何实现游戏界面. 本文将涉及到 compose 的自定义绘制与触摸处理,这些内容都可以在我往期的文章中找到对应的教程,如果对 ...

  2. 跟我一起使用 compose 做一个跨平台的黑白棋游戏(4)移植到compose-jb实现跨平台

    前言 在上一篇文章中,我们已经实现了游戏的所有界面和逻辑代码,并且在 Android 上已经可以正常运行. 这篇文章我们将讲解如何将其从使用 jetpack compose 修改为使用 compose ...

  3. 如何做一个跨平台的游戏App?

    如何做一个跨平台的游戏App? iOS和安卓系统上的应用程序,根据提供的内容不同,按照开发方式和用户体验不同,可区分为app和游戏: 首先从开发方式不同来说明,app开发一般是用操作系统官方提供的开发 ...

  4. 用Jetpack Compose做一个俄罗斯方块游戏机

    本文介绍如何使用Jetpack Compose打造一个经典版的俄罗斯方块游戏. 玩过上面这种游戏机的朋友应该会对本文内容感到亲切,废话不多说,先看东西: 1. 为什么Compose适合做游戏? 通常一 ...

  5. 怎样用cocos2d-x做一个基于地图块的游戏(Part One)

    怎样用cocos2d-X做一个基于地图块的游戏 (Part One) 在这个分为上下两部分的教程中,我们将介绍如何使用Cocos2D-X和地图编辑器做一款基于地图块的游戏.在这个简单的地图块游戏里,一 ...

  6. 用pygame做一个简单的python小游戏---贪吃蛇

    用pygame做一个简单的python小游戏-贪吃蛇 贪吃蛇游戏博客链接:(方法一样,语言不一样) c++贪吃蛇:https://blog.csdn.net/weixin_46791942/artic ...

  7. 用pygame做一个简单的python小游戏---七彩同心圆

    用pygame做一个简单的python小游戏-七彩同心圆 这个小游戏原是我同学python课的课后作业,并不是很难,就简单实现了一下,顺便加强一下pygame库的学习. 玩法:每次点击鼠标时,会以鼠标 ...

  8. 用pygame做一个简单的python小游戏---生命游戏

    用pygame做一个简单的python小游戏-生命游戏 生命游戏(Game of Life) 生命游戏(Game of Life)是剑桥大学约翰·何顿·康威(John Horton Conway)教授 ...

  9. python七彩同心圆_用pygame做一个简单的python小游戏---七彩同心圆

    用pygame做一个简单的python小游戏---七彩同心圆 用pygame做一个简单的python小游戏-七彩同心圆 这个小游戏原是我同学python课的课后作业,并不是很难,就简单实现了一下,顺便 ...

最新文章

  1. SVN 常用操作命令 使用笔记
  2. T-SQL 之 多表联合更新
  3. 互联网晚报 | 8月11日 星期三 | 苏炳添成为小米品牌代言人;联想企业购正式上线;中国电信A股IPO战略配售结果出炉...
  4. 【云速建站】SSL证书自助部署
  5. 【Python】Python简介
  6. [软件更新]卡巴斯基全功能安全软件2010简体中文版程序发布
  7. OpenProj打开不了或者提示”Failed to load Java VM Library”的错误的解决方案
  8. Javascript中new关键字和this指向
  9. 莫再用唐僧式的唠叨施加影响----家长式管理者实施HOLA的障碍
  10. android手机锁屏了打不开怎么办?
  11. 学生管理系统报错合集
  12. java_系列3_数组
  13. 基于单片机智能波形发生器设计
  14. fNIRS在发育科学中的应用
  15. java8 map_Java8 Map 示例:一个略复杂的数据映射聚合例子及代码重构
  16. android recocery模式,recovery模式怎么进入 recovery菜单翻译
  17. C++图书借阅信息管理系统
  18. yolact-训练自己的数据集
  19. 红帽首席架构师:CentOS Stream 并非要革了 CentOS 的“命”
  20. Microsoft Project 2010 简介

热门文章

  1. 二叉树遍历的递归算法
  2. 迷茫的程序员和中国软件业[转]
  3. MySQL完全备份和增量备份
  4. 如何成长为一名Java高级架构师
  5. Aydogan Ozcan:手机变身低成本智能显微镜|42问AI与机器人未来
  6. 推荐 7 个 Vue.js 插件,也许你的项目用的上(一)
  7. 本地连接Docker的Mysql
  8. CSS之选择器(三)伪类选择器
  9. PyCharm配置国内镜像源
  10. 字符0、数字0和‘\0’