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

news2024/12/26 23:48:38

前言

为什么写这系列文章

虽然 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枚棋子(亦有求变化相邻放置)。通常黑子先行。双方轮流落子。只要落子和棋盘上任一枚己方的棋子在一条在线(横、直、斜线皆可)夹着对方棋子,就能将对方的这些棋子转变为我己方(翻面即可)。如果在任一位置落子都不能夹住对手的任一颗棋子,就要让对手下子。当双方皆不能下子时,游戏就结束,子多的一方胜。

p1.png

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

实现思路

我的目标

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

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

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

关于单机游戏的AI

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

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

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

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

s1.png

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

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

这是大佬的游戏主界面:

s2.png

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

game layout 的布局结构如下:

s3.png

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

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

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

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

reversiView.setOnTouchListener(new OnTouchListener() {
    boolean down = false;
    int downRow;
    int downCol;

    @Override
    public 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;
                    }
                    
                    // 判断完成后开始按照规则更新数据和UI
                    Move 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 运行效果:

g1.gif

Desktop 运行效果:

g2.gif

总结

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/549666.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能Python-pythonfor怎么用

PythonFor SEO&#xff1a;如何利用Python提高SEO效果 SEO&#xff08;搜索引擎优化&#xff09;是现代数字营销中至关重要的一环。随着搜索引擎算法不断发展&#xff0c;优化网站以提高排名已经成为了一门复杂的艺术。幸运的是&#xff0c;Python提供了一些强大的工具来简化这…

chatgpt赋能Python-pythonelem

PythonELEM - 简易的Python学习工具 作为一名有10年Python编程经验的工程师&#xff0c;我可以深刻地体会到新手们学习Python的难处。PythonELEM是一个以Python为主题的学习工具&#xff0c;它可以帮助初学者更容易地掌握Python编程。 PythonELEM的功能 PythonELEM是一个简易…

餐饮油烟排放监测管理系统的设计与应用

安科瑞虞佳豪 连日来&#xff0c;河东区生态环境保护综合行政执法支队组织开展餐饮行业油烟净化专项检查工作&#xff0c;有效应对即将到来的夏季餐饮油烟对环境的污染&#xff0c;着力解决群众身边的环境问题。 执法人员对辖区餐饮商户集中区域开展常态化巡查&#xff0c;重…

Metal入门学习:绘制渲染三角形

一、编程指南PDF下载链接(中英文档&#xff09; 1、Metal编程指南PDF链接 https://github.com/dennie-lee/ios_tech_record/raw/main/Metal学习PDF/Metal 编程指南.pdf 2、Metal着色语言(Metal Shader Language:简称MSL)编程指南PDF链接 https://github.com/dennie-lee/ios_te…

chatgpt赋能Python-pythoncumsum

Python中的cumsum-累积求和函数 在数据处理中&#xff0c;经常需要对一个序列的元素进行累加。Python中提供了累积求和函数cumsum()&#xff0c;用于对一个序列的元素进行累加求和操作。 什么是cumsum()函数 cumsum()函数是Python中numpy模块中的一个函数&#xff0c;用于对…

通过小米万兆路由器将小米SoundMove 无缝接入 ChatGPT

通过小米万兆路由器将小米SoundMove 无缝接入 ChatGPT 本教程内容参考 Github 地址(可选)部署查看小米 SoundMove 信息的环境(可选)查看小米 SoundMove 的信息以容器方式部署程序到小米万兆路由器实际效果有待改善点 本教程内容 1 是记录了将小米 SoundMove 接入 ChatGPT 的操…

面向《海贼王》领域数据的知识图谱项目

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 本次任务试图为《海贼王》中出现的各个实体&#xff0c;包括人物、地点、组织等&#xff0c;构建一个知识图谱&#xff0c;帮助我们更好的理解这部作品。 项目内容包括数据采集、知识存储、知识抽取、知识计算、知识应用五大部…

【运动规划算法项目实战】如何使用Pure Pursuit算法进行路径跟踪(附ROS C++代码)

文章目录 前言一、简介二、Pure Pursuit算法优缺点三、 代码实现3.1 算法实现步骤3.2 pure_pursuit.h3.3 pure_pursuit.cpp3.4 cubic_spline_path.py3.5 节点连接关系3.6 RVIZ显示四、总结前言 在自动驾驶和机器人导航领域,路径跟踪是一项关键技术,它使车辆或机器人能够按照…

多线程排序法

多线程排序法 chatGPT给我改的多线程排序法 using System.Collections.Concurrent; using System.Threading; ConcurrentBag<int> sortedList new ConcurrentBag<int>(); void Sort() { int[] arr {2, 6, 12, 8}; List<Thread> threads new List<Threa…

chatgpt赋能Python-pythondone

PythonDone&#xff1a;将Python编程变得更加简单 介绍 Python是一种有着广泛应用的高级编程语言&#xff0c;由于其简洁易学、开发效率高、可移植性好等特点&#xff0c;成为业内最热门的技术之一。但是&#xff0c;对于一些初学者来说&#xff0c;Python的学习过程可能还是…

【论文分享|SIGMOD‘22】WeTune 自动发现和验证重写规则

作者&#xff1a;谢其骏 北京航空航天大学在读硕士&#xff0c; Databend 研发工程师实习生 https://github.com/jun0315 论文原文&#xff1a; Zhaoguo Wang, Zhou Zhou, Yicun Yang, Haoran Ding, Gansen Hu, Ding Ding, Chuzhe Tang, Haibo Chen, Jinyang Li. WeTune: Auto…

【AIGC】11、MDETR | LeCun 团队于 2021 年推出的端到端多模态理解模型

文章目录 一、背景二、方法2.1 DETR2.2 MDETR 三、效果3.1 预训练调整后的检测器3.2 下游任务 论文&#xff1a;MDETR - Modulated Detection for End-to-End Multi-Modal Understanding 代码&#xff1a;https://github.com/ashkamath/mdetr 出处&#xff1a;ICCV 2021 Oral…

chatgpt赋能Python-pythonctrl快捷键

PythonCtrl快捷键使用指南 作为一名有10年Python编程经验的工程师&#xff0c;我深知PythonCtrl快捷键的重要性。PythonCtrl作为一个Python的开源编辑器&#xff0c;在每一个版本中都加入了更多的功能和快捷键&#xff0c;使得Python编程更加高效和易用。在本篇文章中&#xf…

卡方分布分析与应用

卡方检验(chi-square&#xff0c;记为χ2检验)是统计学中常用来计数数据分析的方法&#xff0c;对于总体的分布不作任何假设&#xff0c;因此它属于非参数检验法中的一种。本博文从理论到实际应用去阐述卡方检验&#xff0c;最后用python语言去实现卡方分布的代码。 1. 卡方分…

Spring Security的基本组件

一.简介 Spring Security通过一些列的过滤器完成了用户身份认证及其授权工作&#xff0c;每个过滤器都有不同分工&#xff0c;当然这些过滤器并不是全部都一起工作&#xff0c;而是根据我们需要什么功能&#xff0c;才会选取对应的过滤器加入。 当然这些过滤器并不是直接加入…

Linux 终端特殊符号含义大全

Linux特殊符号使用及含义 总结 Linux 终端中有许多特殊符号&#xff0c;本文对常用的进行了总结&#xff1a; $ 表示变量/普通终端用户&#xff1a;用于引用变量的值/表示终端中的普通用户。# 表示注释/超级用户&#xff1a;用于在脚本中注释代码/表示终端中的超级用户。/ &…

Doxygen 源码分析: QCString类

2023-05-20 23:41:56 ChrisZZ imzhuofoxmailcom Hompage https://github.com/zchrissirhcz 文章目录 1. Doxygen 版本2. QCString 类概览3. QCString 特殊成员函数3.1 default 方式的构造函数3.2 单个参数和两个参数的构造函数 4. inline方式实现的成员函数4.1 operator 函数4.…

chatgpt赋能Python-pythonguanwang

Python官网SEO分析 Python是一种高级编程语言&#xff0c;被广泛应用于Web开发、数据科学、人工智能、机器学习等领域。Python官网&#xff08;https://www.python.org&#xff09;是Python语言的官方网站&#xff0c;为Python用户和开发者提供了最新的Python解释器、文档、库…

中文Python(5)中文Python的while条件循语句

中文Python&#xff08;5&#xff09;中文Python的while条件循语句 Python是一种流行的编程语言&#xff0c;其简单而直观的语法吸引了很多人学习和使用。中文Python则是针对中文用户开发的一种版本。中文Python原先为了给不懂编写程序的人写量化程序&#xff0c;我们开发了中…

代码随想录算法训练营 Day 43 | 1049.最后一块石头的重量 II,494.目标和,474.一和零

1049.最后一块石头的重量 II 讲解链接&#xff1a;代码随想录-1049.最后一块石头的重量 II 确定 dp 数组以及下标的含义&#xff1a;dp[j]表示容量&#xff08;这里说容量更形象&#xff0c;其实就是重量&#xff09;为 j 的背包&#xff0c;最多可以背最大重量为 dp[j]。 石…