跳一跳辅助工具的原理分析,和Java实现。(其实没那么复杂)

一、前言

(Java代码的实现是基于另外一篇博客,我精简了计算方法而成,参考博客地址http://blog.csdn.net/lihushiwoa/article/details/78942322)

先说一说我的感受,之前觉得能做出做出辅助工具的技术要求一定很高,然而当自己真正分析原理并且动手实现之后发现门槛没有那么高。

想做这个工具首先得知道adb是什么,其次是会一门编程语言。我了解adb之后瞬间觉得思路豁然开朗。

adb是Android Debug Bridge。他的作用是可以通过在计算机cmd输入命令控制Android手机,比如马上要用到的效果----模拟实现触控屏幕和触控多长时间。所以你在java代码里控制adb命令是可行的。

二、思路与原理

想象一下手动能让棋子命中下一个platform的场景:

1:测量棋子到目标坐标的距离S;

2:按压时间T=S*时间系数;(时间系数:单位位移的用时,200ms的n倍,多次尝试的大致确认

  然后你按屏幕时间T就可以了。

在本代码中我发现每个跳板的连线与竖直方向大约呈60°夹角(跳板和跳板虽然是60°,但是棋子不一定与目标是60°角关系呀?没关系,经过测试与棋子的角度没太大关系),所以如果我们知道棋子和目标点的距离就可以算S了。

测量夹角计算斜边原理

代码实现也一样:

1:adb命令截屏,存放到指定文件夹。adb命令分别是:

adb shell screencap -p /sdcard/current.png

adb pull /sdcard/current.png  d:/jump_screencapture 

2:扫描棋子的X轴坐标。

图片是一个个像素点组成,从上往下遍历每一行像素点,如果某像素点的R、G、B值在棋子的RGB值区间(R∈(50,60),G∈(53,63),B∈(95,110)),那么可以确定该像素点在棋子内了,然后记下这是第几个(halmaXCount),并且计算横坐标的和halmaSum+=halmaSum(原理:多次测量取平均值,精确棋子的X轴坐标)。

3:扫描目标跳板的横坐标。原理与扫描棋子相似,但又不同。

原理:由于跳板是左右对称的,也就是说假如目标跳板的上表面是标准的菱形,那么一旦遍历到菱形上方的顶点,那么该顶点的X坐标就是目标跳板上表面中心的X坐标。

不同之处是:遍历完目标调班的首行像素点便停止,(缺点:样本空间略小,可能导致目标坐标不精确,留日后优化。)。记录每行首个像素点的RGB值之后,在接下来的遍历中如果发现本行与首个像素点RGB差异太大,则认为该像素点已经在目标跳板之内。找到所有不同于第一个像素点的X坐标求平均数,然后结束。

4:计算棋子与目标点的距离,和按压时间。

三、ADB配置

ADB主要是Path配置,配置完,在cmd中输入adb,如下图便是配置成功。需要的可以去http://download.csdn.net/download/thread_cooperation/10212920下载adb配置

四、代码实现

注意:不要在开发工具里跑,可能无法调用ADB。在cmd中是可行的。

 

import java.awt.image.BufferedImage;  
import java.io.BufferedReader;  
import java.io.File;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.util.Arrays;  
import java.util.concurrent.TimeUnit;  
  
import javax.imageio.ImageIO;  
  
/** 
 * 参考CSDN
 *  @link <a target="_blank" href="http://blog.csdn.net/lihushiwoa/article/details/78942322">http://blog.csdn.net/lihushiwoa/article/details/78942322</a> 
 *  
 * 说明:本版本是在上述参考CSDN的基础上,精简计算而成,目前支持分辨率1920x1080的安卓机(在下没有其他分辨率的手机用于测试)
 *  
 * 跳一跳辅助 
 * @author ykq 
 */  
public class Jump {  
  
    private static final String IMAGE_NAME              = "current.png";  
  
    private static final String STORE_DIR               = "d:/jump_screencapture";  
  
    //截图的数量上限  
    private static final int    imageLengthLength       = 5;  
  
    //存放图片的数组
    private static final long[] imageLength             = new long[imageLengthLength];  
  
    private final RGBInfo       rgbInfo                 = new RGBInfo();  
  
    //adb脚本
    private final String[]      ADB_SCREEN_CAPTURE_CMDS =  
                                                        { "adb shell screencap -p /sdcard/" + IMAGE_NAME,  
            "adb pull /sdcard/current.png " + STORE_DIR };  
  
    //截屏中游戏分数显示区域最下方的Y坐标,300是 1920x1080的值,根据实际情况修改  
    private final int           gameScoreBottomY        = 310;  
  
    //按压的时间系数,可根据具体情况适当调节  
    private final double        pressTimeCoefficient    = 1.37;
    
    //按压的起始点坐标,也是再来一局的起始点坐标  
    private final int           swipeX                  = 550;  
  
    private final int           swipeY                  = 1580;  
  
    //棋子的宽度,从截屏中量取,自行调节  
    private final int           halmaBodyWidth          = 74;

    //csc(pi/6)
    private final static double 		proportion 				= 2/Math.sqrt(3);
    
    public static void main(String[] args){  
        try{  
            File storeDir = new File(STORE_DIR);  //构建文件目录
            if (!storeDir.exists()) {  
               boolean flag = storeDir.mkdir();  //创建文件夹
               if (!flag) {  
                   System.err.println("创建图片存储目录失败");  
                   return;  
               }  
            }  
              
            Jump jumpjumpHelper = new Jump();  
            //执行次数  
            int executeCount = 0;  
            for (;;){  
                //执行ADB命令,获取安卓截屏  
                jumpjumpHelper.executeADBCaptureCommands();  
                File currentImage = new File(STORE_DIR, IMAGE_NAME);  
                if (!currentImage.exists()){  
                    System.out.println("图片不存在");  
                    continue;  
                }  
  
                long length = currentImage.length();  
                imageLength[executeCount % imageLengthLength] = length;  
                //查看是否需要重新开局  
                jumpjumpHelper.checkDoReplay();  
                executeCount++;  
                System.out.println("当前第" + executeCount + "次执行!");  
                //获取跳棋和底板的中心坐标  
                int[] result = jumpjumpHelper.getHalmaAndBoardXYValue(currentImage);   //Halma跳棋
                if (result == null){  
                    System.out.println("The result of method getHalmaAndBoardXYValue is null!");  
                    continue;  
                }  
                int halmaX = result[0];  
                int boardX = result[1];  
                System.out.println("halmaX: " + halmaX + "  ****  " + "boardX: " + boardX);  
                //计算跳跃的距离  
                double jumpDistance = Math.abs(boardX-halmaX)*proportion;
                jumpjumpHelper.doJump(jumpDistance);  
                //每次停留2.5秒  
                TimeUnit.MILLISECONDS.sleep(2500);  
            }  
        }  
        catch (Exception e){  
            e.printStackTrace();  
        }  
    } 
  
    /** 
     * 获取跳棋以及下一块跳板的中心坐标 
     * 
     * @return 
     * @throws IOException 
     */  
    public int[] getHalmaAndBoardXYValue(File currentImage) throws IOException{  
        BufferedImage bufferedImage = ImageIO.read(currentImage);  
        int width = bufferedImage.getWidth();  
        int height = bufferedImage.getHeight();  
        System.out.println("宽度:" + width + ",高度:" + height);  
        int halmaXSum = 0;  
        int halmaXCount = 0;  
        int boardX = 0;  
        //从截屏从上往下逐行遍历像素点,以棋子颜色作为位置识别的依据,最终取出棋子颜色最低行所有像素点的平均值,即计算出棋子所在的坐标  
        for (int y = gameScoreBottomY; y < height; y++){  
            for (int x = 0; x < width; x++){  
                processRGBInfo(bufferedImage, x, y);  //获取指定坐标的RGB值 
                int rValue = this.rgbInfo.getRValue();  
                int gValue = this.rgbInfo.getGValue();  
                int bValue = this.rgbInfo.getBValue();  
                //根据RGB的颜色来识别棋子的位置,  
                if (rValue > 50 && rValue < 60 && gValue > 53 && gValue < 63 && bValue > 95 && bValue < 110){  
                    halmaXSum += x;  
                    halmaXCount++;  
                }  
            }  
        }  
  
        if (halmaXSum != 0 && halmaXCount != 0){  
            //棋子底行的X坐标值  
            int halmaX = halmaXSum / halmaXCount;  
            
            //从gameScoreBottomY开始,获取棋子的x坐标
            for (int y = gameScoreBottomY; y < height; y++){  
                processRGBInfo(bufferedImage, 0, y);  //每行第一个像素的RGB(获取指定坐标的RGB值 )
                int lastPixelR = this.rgbInfo.getRValue();  
                int lastPixelG = this.rgbInfo.getGValue();  
                int lastPixelB = this.rgbInfo.getBValue();  
                //只要计算出来的boardX的值大于0,就表示下个跳板的中心坐标X值取到了。  
                if (boardX > 0){  
                    break;  
                }  
                int boardXSum = 0;  
                int boardXCount = 0;  
                
                for (int x = 0; x < width; x++){  
                    processRGBInfo(bufferedImage, x, y);  
                    int pixelR = this.rgbInfo.getRValue();  
                    int pixelG = this.rgbInfo.getGValue();  
                    int pixelB = this.rgbInfo.getBValue();  
                    //处理棋子头部比下一个跳板还高的情况      跳过棋子所在的x轴区域不遍历(防止检索到棋子的颜色)
                    if (Math.abs(x - halmaX) < halmaBodyWidth){    //abs绝对值
                        continue;  
                    }  
  
                    //从上往下逐行扫描至下一个跳板的顶点位置,下个跳板可能为圆形,也可能为方框,取多个点,求平均值  
                    if ((Math.abs(pixelR - lastPixelR) + Math.abs(pixelG - lastPixelG) + Math.abs(pixelB - lastPixelB)) > 8){   
                        boardXSum += x;  
                        boardXCount++;  
                    }  
                }  
  
                if (boardXSum > 0){  
                    boardX = boardXSum / boardXCount;  
                }  
            }  
  
            if (boardX > 0){  
                int[] result = new int[2];  
                //棋子的X坐标  
                result[0] = halmaX;  
                //下一块跳板的X坐标    
                result[1] = boardX;  
                return result;  
            }  
        }  
  
        return null;  
    }  
  
    /** 
     * 执行命令 
     * 
     * @param command 
     */  
    private void executeCommand(String command){  
        Process process = null;  
        try{  
            process = Runtime.getRuntime().exec(command);  
            System.out.println("exec command start: " + command);  
            process.waitFor();  
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));  
            String line = bufferedReader.readLine();  
            if (line != null){  
                System.out.println(line);  
            }  
            System.out.println("exec command end: " + command);  
        }  
        catch (Exception e){  
            e.printStackTrace();  
        }  
        finally{  
            if (process != null){  
                process.destroy();  
            }  
        }  
    }  
  
    /** 
     * ADB获取安卓截屏 
     */  
    public void executeADBCaptureCommands(){  
        for (String command : ADB_SCREEN_CAPTURE_CMDS)  
        {  
            executeCommand(command);  
        }  
    }  
  
    /** 
     * 跳一下 
     * 
     * @param distance 
     */  
    public void doJump(double distance){  
        System.out.println("distance: " + distance);  
        //计算按压时间,最小200毫秒  
        int pressTime = (int) Math.max(distance * pressTimeCoefficient, 200);  
        System.out.println("pressTime: " + pressTime);  
        //执行按压操作  
        String command = String.format("adb shell input swipe %s %s %s %s %s", swipeX, swipeY, swipeX, swipeY,  
                pressTime);  
        System.out.println(command);  
        executeCommand(command);  
        System.out.println();
        System.out.println();
    }  
  
    /** 
     * 再来一局 
     */  
    private void replayGame(){  
        String command = String.format("adb shell input tap %s %s", swipeX, swipeY);  //adb shell模拟点击事件 input
        executeCommand(command);  
    }  
  
    /** 
     * 检查是否需要重新开局 
     */  
    public void checkDoReplay(){  
        if (imageLength[0] > 0 && imageLength[0] == imageLength[1] && imageLength[1] == imageLength[2]  
                && imageLength[2] == imageLength[3] && imageLength[3] == imageLength[4]){  
            //此时表示已经连续5次图片大小一样了,可知当前屏幕处于再来一局  
            Arrays.fill(imageLength, 0);  
            //模拟点击再来一局按钮重新开局  
            replayGame();  
        }  
    }  
  
    /** 
     * 获取指定坐标的RGB值 
     * 
     * @param bufferedImage 
     * @param x 
     * @param y 
     */  
    private void processRGBInfo(BufferedImage bufferedImage, int x, int y){  
        this.rgbInfo.reset();  
        int pixel = bufferedImage.getRGB(x, y);  
        //转换为RGB数字    
        this.rgbInfo.setRValue((pixel & 0xff0000) >> 16);  
        this.rgbInfo.setGValue((pixel & 0xff00) >> 8);  
        this.rgbInfo.setBValue((pixel & 0xff));  
    }  
  
    
    /**************************  RGBInfo  ****************************/
    class RGBInfo{  
        private int RValue;  
  
        private int GValue;  
  
        private int BValue;  
  
        public int getRValue(){  
            return RValue;  
        }  
  
        public void setRValue(int rValue){ 
            RValue = rValue;  
        }  
  
        public int getGValue(){  
            return GValue;  
        }  
  
        public void setGValue(int gValue){  
            GValue = gValue;  
        }  
  
        public int getBValue(){  
            return BValue;  
        }  
  
        public void setBValue(int bValue){  
            BValue = bValue;  
        }  
  
        public void reset(){  
            this.RValue = 0;  
            this.GValue = 0;  
            this.BValue = 0;  
        }  
    }  
}  

 

<script src="https://cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome/css/font-awesome.min.css"/>
<script src="https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget/autoload.js"></script>

 

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1) 第1章:对象入门<br>这一章是对面向对象的程序设计(OOP)的一个综述,其中包括对“什么是对象”之类的基本问题的回答,并讲述了接口与实现、抽象与封装、消息与函数、继承与合成以及非常重要的多形性的概念。这一章会向大家提出一些对象创建的基本问题,比如构建器、对象存在于何处、创建好后把它们置于什么地方以及魔术般的垃圾收集器(能够清除不再需要的对象)。要介绍的另一些问题还包括通过违例实现的错误控制机制、反应灵敏的用户界面的多线程处理以及连网和因特网等等。大家也会从中了解到是什么使得Java如此特别,它为什么取得了这么大的成功,以及与面向对象的分析与设计有关的问题。<br><br>(2) 第2章:一切都是对象<br>本章将大家带到可以着手写自己的第一个Java程序的地方,所以必须对一些基本概念作出解释,其中包括对象“句柄”的概念;怎样创建一个对象;对基本数据类型和数组的一个介绍;作用域以及垃圾收集器清除对象的方式;如何将Java中的所有东西都归为一种新数据类型(类),以及如何创建自己的类;函数、自变量以及返回值;名字的可见度以及使用来自其他库的组件;static关键字;注释和嵌入文档等等。<br><br>(3) 第3章:控制程序流程<br>本章开始介绍起源于C和C++,由Java继承的所有运算符。除此以外,还要学习运算符一些不易使人注意的问题,以及涉及造型、升迁以及优先次序的问题。随后要讲述的是基本的流程控制以及选择运算,这些是几乎所有程序设计语言都具有的特性:用if-else实现选择;用for和while实现循环;用break和continue以及Java的标签式break和contiune(它们被认为是Java中“不见的gogo”)退出循环;以及用switch实现另一种形式的选择。尽管这些与C和C++中见到的有一定的共通性,但多少存在一些区别。除此以外,所有示例都是完整的Java示例,能使大家很快地熟悉Java的外观。<br><br>(4) 第4章:初始化和清除<br>本章开始介绍构建器,它的作用是担保初始化的正确实现。对构建器的定义要涉及函数过载的概念(因为可能同时有几个构建器)。随后要讨论的是清除过程,它并非肯定如想象的那么简单。用完一个对象后,通常可以不必管它,垃圾收集器会自动介入,释放由它占据的内存。这里详细探讨了垃圾收集器以及它的一些特点。在这一章的最后,我们将更贴近地观察初始化过程:自动成员初始化、指定成员初始化、初始化的顺序、static(静态)初始化以及数组初始化等等。<br><br>(5) 第5章:隐藏实现过程<br>本章要探讨将代码封装到一起的方式,以及在库的其他部分隐藏时,为什么仍有一部分处于暴露状态。首先要讨论的是package和import关键字,它们的作用是进行文件级的封装(打包)操作,并允许我们构建由类构成的库(类库)。此时也会谈到目录路径和文件名的问题。本章剩下的部分将讨论public,private以及protected三个关键字、“友好”访问的概念以及各种场合下不同访问控制级的意义。<br><br>(6) 第6章:类再生<br>继承的概念是几乎所有OOP语言中都占有重要的地位。它是对现有类加以利用,并为其添加新功能的一种有效途径(同时可以修改它,这是第7章的主题)。通过继承来重复使用原有的代码时(再生),一般需要保持“基础类”不变,只是将这儿或那儿的东西串联起来,以达到预期的效果。然而,继承并不是在现有类基础上制造新类的唯一手段。通过“合成”,亦可将一个对象嵌入新类。在这一章中,大家将学习在Java中重复使用代码的这两种方法,以及具体如何运用。<br><br>(7) 第7章:多形性<br>若由你自己来干,可能要花9个月的时间才能发现和理解多形性的问题,这一特性实际是OOP一个重要的基础。通过一些小的、简单的例子,读者可知道如何通过继承来创建一系列类型,并通过它们共有的基础类对那个系列中的对象进行操作。通过Java的多形性概念,同一系列中的所有对象都具有了共通性。这意味着我们编写的代码不必再依赖特定的类型信息。这使程序更易扩展,包容力也更强。由此,程序的构建和代码的维护可以变得更方便,付出的代价也会更低。此外,Java还通过“接口”提供了设置再生关系的第三种途径。这儿所谓的“接口”是对对象物理“接口”一种纯粹的抽象。一旦理解了多形性的概念,接口的含义就很容易解释了。本章也向大家介绍了Java 1.1的“内部类”。<br><br>(8) 第8章:对象的容纳<br>对一个非常简单的程序来说,它可能只拥有一个固定数量的对象,而且对象的“生存时间”或者“存在时间”是已知的。但是通常,我们的程序会在不定的时间创建新对象,只有在程序运行时才可了解到它们的详情。此外,除非进入运行期,否则无法知道所需对象的数量,甚至无法得知它们确切的类型。为解决这个常见的程序设计问题,我们需要拥有一种能力,可在任何时间、任何地点创建任何数量的对象。本章的宗旨便是探讨在使用对象的同时用来容纳它们的一些Java工具:从简单的数组到复杂的集合(数据结构),如Vector和Hashtable等。最后,我们还会深入讨论新型和改进过的Java 1.2集合库。<br><br>(9) 第9章:违例差错控制<br>Java最基本的设计宗旨之一便是组织错误的代码不会真的运行起来。编译器会尽可能捕获问题。但某些情况下,除非进入运行期,否则问题是不会被发现的。这些问题要么属于编程错误,要么则是一些自然的出错状况,它们只有在作为程序正常运行的一部分时才会成立。Java为此提供了“违例控制”机制,用于控制程序运行时产生的一切问题。这一章将解释try、catch、throw、throws以及finally等关键字在Java中的工作原理。并讲述什么时候应当“掷”出违例,以及在捕获到违例后该采取什么操作。此外,大家还会学习Java的一些标准违例,如何构建自己的违例,违例发生在构建器中怎么办,以及违例控制器如何定位等等。<br><br>(10) 第10章:Java IO系统<br>理论上,我们可将任何程序分割为三部分:输入、处理和输出。这意味着IO(输入/输出)是所有程序最为关键的部分。在这一章中,大家将学习Java为此提供的各种类,如何用它们读写文件、内存块以及控制台等。“老”IO和Java 1.1的“新”IO将得到着重强调。除此之外,本节还要探讨如何获取一个对象、对其进行“流式”加工(使其能置入磁盘或通过网络传送)以及重新构建它等等。这些操作在Java的1.1版中都可以自动完成。另外,我们也要讨论Java 1.1的压缩库,它将用在Java的归档文件格式中(JAR)。<br><br>(11) 第11章:运行期类型鉴定<br>若只有指向基础类的一个句柄,Java的运行期类型标鉴定(RTTI)使我们能获知一个对象的准确类型是什么。一般情况下,我们需要有意忽略一个对象的准确类型,让Java的动态绑定机制(多形性)为那一类型实现正确的行为。但在某些场合下,对于只有一个基础句柄的对象,我们仍然特别有必要了解它的准确类型是什么。拥有这个资料后,通常可以更有效地执行一次特殊情况下的操作。本章将解释RTTI的用途、如何使用以及在适当的时候如何放弃它。此外,Java 1.1的“反射”特性也会在这里得到介绍。<br><br>(12) 第12章:传递和返回对象<br>由于我们在Java中同对象沟通的唯一途径是“句柄”,所以将对象传递到一个函数里以及从那个函数返回一个对象的概念就显得非常有趣了。本章将解释在函数中进出时,什么才是为了管理对象需要了解的。同时也会讲述String(字串)类的概念,它用一种不同的方式解决了同样的问题。<br><br>(13) 第13章:创建窗口和程序片<br>Java配套提供了“抽象Windows工具包”(AWT)。这实际是一系列类的集合,能以一种可移植的形式解决视窗操纵问题。这些窗口化程序既可以程序片的形式出现,亦可作为独立的应用程序使用。本章将向大家介绍AWT以及网上程序片的创建过程。我们也会探讨AWT的优缺点以及Java 1.1在GUI方面的一些改进。同时,重要的“Java Beans”技术也会在这里得到强调。Java Beans是创建“快速应用开发”(RAD)程序构造工具的重要基础。我们最后介绍的是Java 1.2的“Swing”库——它使Java的UI组件得到了显著的改善。<br><br>(14) 第14章:多线程<br>Java提供了一套内建的机制,可提供对多个并发子任务的支持,我们称其为“线程”。这线程均在单一的程序内运行。除非机器安装了多个处理器,否则这就是多个子任务的唯一运行方式。尽管还有别的许多重要用途,但在打算创建一个反应灵敏的用户界面时,多线程的运用显得尤为重要。举个例子来说,在采用了多线程技术后,尽管当时还有别的任务在执行,但用户仍然可以毫无阻碍地按下一个按钮,或者键入一些文字。本章将对Java的多线程处理机制进行探讨,并介绍相关的语法。<br><br>(15) 第15章 网络编程<br>开始编写网络应用时,就会发现所有Java特性和库仿佛早已串联到了一起。本章将探讨如何通过因特网通信,以及Java用以辅助此类编程的一些类。此外,这里也展示了如何创建一个Java程序片,令其同一个“通用网关接口”(CGI)程序通信;揭示了如何用C++编写CGI程序;也讲述了与Java 1.1的“Java数据库连接”(JDBC)和“远程方法调用”(RMI)有关的问题。<br><br>(16) 第16章 设计范式<br>本章将讨论非常重要、但同时也是非传统的“范式”程序设计概念。大家会学习设计进展过程的一个例子。首先是最初的方案,然后经历各种程序逻辑,将方案不断改革为更恰当的设计。通过整个过程的学习,大家可体会到使设计思想逐渐变得清晰起来的一种途径。<br><br>(17) 第17章 项目<br>本章包括了一系列项目,它们要么以本书前面讲述的内容为基础,要么对以前各章进行了一番扩展。这些项目显然是书中最复杂的,它们有效演示了新技术和类库的应用。<br>有些主题似乎不太适合放到本书的核心位置,但我发现有必要在教学时讨论它们,这些主题都放入了本书的附录。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值