004-Kotlin界面开发快速入水之TicTacToe

在这里插入图片描述

程序界面和效果

在这里插入图片描述

快速入水

要学习一样跟程序设计有关的东西,最好的办法始终是把手打湿,整一个能够运行,可以实验的东西出来。

也只有在程序开发中,我们才能想一个魔法师而不是魔术师,我们真的能够创造一个东西。而且编译器不会因为我们很丑、我们学历不行、我们没女朋友、我们很穷而拒绝我们,只要我们严格按照手册,就真的可以梦想成真。真是幸运啊!

所以,我们先来写一个简单的程序,一个大家都完全不会感兴趣的游戏:井字棋。

这个游戏实在是无聊,我从来没有成功说服任何一个人跟我一起好好玩过……不过……无聊也是它的好处,马上就行。

构建工具链

现代化的程序设计语言通常都有完整的工具链,负责完成:

  • 管理工程-文件的组织
  • 编译源代码
  • 运行、运行、分发程序

热门但是无法流行的伟大Rust语言,它的工具链是cargo

不热门但是很流行的C/C++语言,它的工具链是make,现在也有cmake,或者ninja

热门流行的Java语言,它的工具链是antmavengradle

Kotlin是基于Java的,它的工具链我们一般选择gradle。实际上gradlemavenant都是Java的构建工具。

gradle构建Kotlin程序,最好玩的是,我们可以编写kotlin来完成。当然,以前还用groovy,现在大概可能或许提倡用kotlin

构成一个gradle工程的文件有:

  • settings.gradle.kts:工程的设置
  • build.gradle.kts:工程的构建
  • src:源代码目录
  • gradle.properties:gradle的属性文件
  • gradlewgradlew.bat:gradle的wrapper的启动脚本
  • gradle/wrapper目录:gradle的配置文件和wrapper
    • gradle-wrapper.jar
    • gradle-wrapper.properties

这是在IDEA中一个典型工程的结构:

在这里插入图片描述

这里展示了全部的文件,包括隐藏文件:

  • .gradle:gradle的缓存目录
  • .idea:IDEA的配置目录
  • build:gradle的构建目录
  • .kotlin:kotlin的配置目录
  • .run:运行配置目录

有些时候,还有:

  • out:编译输出目录

当然,这些都不需要自己来创建,只需要在IDEA中创建一个新的gradle工程就行了。甚至,下面大部分配置都不需要自己来创建,只需要安装一个Jetpack Compose的插件,然后新建一个Compose工程就行了。

但是,我们这里还是絮絮叨叨一下,可以大概知道这些都是什么。

Gradle Wrapper

为了让工程更加有移植性,我们一般会使用gradle wrapper,这个工具会自动下载指定版本的gradle,并且把gradle的启动脚本放在工程的根目录下。

我们可以通过gradlew或者gradlew.bat来启动gradle,这样就不需要在系统中安装gradle了。

这相关的几个文件,我们可以通过gradle wrapper命令来生成。

gradle wrapper

这个命令会生成gradlewgradlew.batgradle/wrapper/gradle-wrapper.jargradle/wrapper/gradle-wrapper.properties这几个文件。

在最后那个文件中,通常包含有下载的地址和版本号。

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

我们通(bi)常(xu)更改那个distributionUrl地址,指向我们自己的镜像源,这样下载会快一些。

gradle.properties

这个文件通常用来存放一些工程的属性,比如版本号、插件版本等等。

org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
kotlin.code.style=official
kotlin.version=2.0.0
compose.version=1.6.10

第一行的org.gradle.jvmargsgradle的启动参数,这里设置了堆内存和文件编码。

在调用gradle的时候,我们可以通过-P参数来传递这些属性,比如:

gradle build -Pkotlin.version=1.5.31

这里设定的属性,可以在build.gradle.kts文件中使用。

settings.gradle.kts

我们需要在settings.gradle.kts文件中添加一些内容,这个文件是一个Kotlin脚本,用来描述工程的设置。

pluginManagement {repositories {maven("https://maven.aliyun.com/repository/public")maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")google()gradlePluginPortal()mavenCentral()}plugins {kotlin("jvm").version(extra["kotlin.version"] as String)id("org.jetbrains.compose").version(extra["compose.version"] as String)id("org.jetbrains.kotlin.plugin.compose").version(extra["kotlin.version"] as String)}
}rootProject.name = "Demo004"

这个文件首先描述了插件的管理,然后指定了工程的名称。

当然,我们也在这里添加了一些镜像源,这样下载插件会快一些。这个技能必须要掌握……不掌握就会觉得体验极其糟糕……什么都打不开,什么都运行不了。

build.gradle.kts

我们需要在build.gradle.kts文件中添加一些内容,这个文件是一个Kotlin脚本,用来描述工程的构建过程。

import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.targets.js.npm.fromSrcPackageJsonplugins {kotlin("jvm")id("org.jetbrains.compose")id("org.jetbrains.kotlin.plugin.compose")
}group = "org.cardc.fdii"
version = "1.0.0"repositories {maven("https://maven.aliyun.com/repository/public")mavenCentral()maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")google()
}dependencies {// Note, if you develop a library, you should use compose.desktop.common.// compose.desktop.currentOs should be used in launcher-sourceSet// (in a separate module for demo project and in testMain).// With compose.desktop.common you will also lose @Preview functionalityimplementation(compose.desktop.currentOs)// Gson dependencyimplementation("com.google.code.gson:gson:2.11.0")
}compose.desktop {application {mainClass = "MainKt"nativeDistributions {targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)packageName = "Demo004"packageVersion = "1.0.0"}}
}

这里面,通常只更改了几处:

  • group:工程的组织
  • version:工程的版本,通常我会从1.0-SNAPSHOT改成1.0.0,前者在分发的时候会有问题
  • repositories:镜像源,这里添加了一些镜像源,同样!这个技能必须要掌握!
  • dependencies:依赖,这里compose.desktop.currentOs,这是Compose Desktop的依赖,如果是Compose工程,自动就有;后面的gson是一个JSON库,我们可能会用到。

src目录

这个目录是源代码目录,我们可以在这个目录下创建kotlinjavaresources等等目录,用来存放源代码和资源文件。

当然,以这个工程为例,我们只需要在src/main/kotlin目录下创建一个Main.kt文件,这个文件就是程序的入口。

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.useResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import java.io.File@Composable
fun TicTocTile(x: Int,y: Int,ticTacToe: TicTacToe
) {Box(modifier = Modifier.size(100.dp).background(Color.LightGray),contentAlignment = Alignment.Center) {Button(modifier = Modifier.padding(5.dp).fillMaxSize(),colors = ButtonDefaults.buttonColors(backgroundColor = ticTacToe[x, y].color()),onClick = {if (ticTacToe.isGameOver()) {ticTacToe.startNewGame()return@Button}ticTacToe.nextMove(x, y)}) {Text(text = ticTacToe.textAt(x, y),fontSize = 36.sp,color = Color.Black)}}
}@Composable
@Preview
fun TicToc() {val board = remember {mutableStateMapOf<Pair<Int, Int>, Player>(Pair(0, 0) to Player.NULL,Pair(0, 1) to Player.NULL,Pair(0, 2) to Player.NULL,Pair(1, 0) to Player.NULL,Pair(1, 1) to Player.NULL,Pair(1, 2) to Player.NULL,Pair(2, 0) to Player.NULL,Pair(2, 1) to Player.NULL,Pair(2, 2) to Player.NULL)}val player = remember { mutableStateOf(Player.X) }val ticTacToe = TicTacToe(board, player)Column {for (i in 0..2) {Row {for (j in 0..2) {TicTocTile(i, j, ticTacToe)}}}Text(modifier = Modifier.padding(10.dp).align(Alignment.CenterHorizontally),text = ticTacToe.gameText(),fontSize = 30.sp,color = ticTacToe.color(),textAlign = TextAlign.Center)}}fun main() = application {useResource("config.json") {config = loadConfig(it)}Window(onCloseRequest = ::exitApplication,title = "Tic Tac Toe",state = rememberWindowState(position = WindowPosition.Aligned(Alignment.Center),size = DpSize(320.dp, 400.dp))) {Box(modifier = Modifier.background(Color.White).fillMaxSize(),contentAlignment = Alignment.Center) {TicToc()}}
}

然后是TicTacToe的实现:

enum class Player {NULL, X, O;fun nameString(): String {return when (this) {X -> "X"O -> "O"else -> ""}}}import androidx.compose.runtime.MutableState
import androidx.compose.runtime.snapshots.SnapshotStateMapdata class TicTacToe(val board: SnapshotStateMap<Pair<Int, Int>, Player>, val player: MutableState<Player>) {fun gameText(): String {val winner = winner()if (isGameOver()) {if (winner == Player.NULL) {return "Game Over Tie."}return "Player ${winner.nameString()} won!"}return "Player ${player.value.nameString()}'s turn"}fun winner(): Player {for (i in 0..2) {if (get(i, 0) != Player.NULL && get(i, 0) == get(i, 1) && get(i, 1) == get(i, 2)) {return get(i, 0)}if (get(0, i) != Player.NULL && get(0, i) == get(1, i) && get(1, i) == get(2, i)) {return get(0, i)}}if (get(0, 0) != Player.NULL && get(0, 0) == get(1, 1) && get(1, 1) == get(2, 2)) {return get(0, 0)}if (get(0, 2) != Player.NULL && get(0, 2) == get(1, 1) && get(1, 1) == get(2, 0)) {return get(0, 2)}return Player.NULL}fun nextMove(x: Int, y: Int) {if (isTaking(x, y)) returnboard.put(Pair(x, y), player.value)nextPlayer()}operator fun get(x: Int, y: Int): Player {return board[Pair(x, y)] ?: Player.NULL}fun textAt(x: Int, y: Int): String {return get(x, y).nameString()}fun startNewGame() {for (key in board.keys) {this.board.put(key, Player.NULL)}player.value = Player.X}fun isGameOver(): Boolean {return winner() != Player.NULL || isFilled()}private fun isFilled(): Boolean {board.values.find { it == Player.NULL }?.let {return false}return true}private fun isTaking(x: Int, y: Int): Boolean {return get(x, y) != Player.NULL}private fun nextPlayer() {player.value = if (player.value == Player.X) Player.O else Player.X}}

为了配合显示颜色,我们还需要一个扩展函数:

import Player.O
import Player.X
import androidx.compose.ui.graphics.Color
import com.google.gson.Gson
import java.io.File
import java.io.InputStreamdata class Config(val PlayerXColor: String="00FF00",val PlayerOColor: String="#0000FF",val GameOverColor: String="##FF0000"
)fun loadConfig(file: InputStream): Config {try {val json = file.readAllBytes().decodeToString()return Gson().fromJson(json, Config::class.java)} catch (e: Exception) {e.printStackTrace()return Config("#FF0000", "#0000FF", "#00FF00")}
}val String.colorget() = Color(removePrefix("#").toInt(16)).copy(alpha = 1f)lateinit var config: Configfun Player.color(): Color {return when (this) {X -> config.PlayerXColor.colorO -> config.PlayerOColor.colorelse -> Color.White}
}fun TicTacToe.color(): Color {if (isGameOver()) {return config.GameOverColor.color}return player.value.color()
}

这个负责从config.json文件中读取颜色配置,然后在游戏中对不同玩家显示不同颜色,并给出游戏结束时的颜色。

这个config.json文件的内容是:

{"PlayerXColor": "#00FF00","PlayerOColor": "#0000FF","GameOverColor": "#FF0000"
}

这样,我们就完成了一个简单的井字棋游戏。

运行

在windows下面,我们可以通过gradlew.bat来运行这个程序:

./gradlew.bat run

还能够通过gradle来构建这个程序:

./gradlew.bat createDistributable

这样就会在build/compose/binaries目录下生成一个可执行文件程序文件夹,里面包含了可执行文件和依赖库。

这个文件拷贝到其他地方,就可以运行了。

当然在IDEA中——默认你使用的是IDEA——你可以直接右边的Gradle工具栏中选择Tasks,然后选择application,然后选择run,就可以运行这个程序。

在这里插入图片描述

看看这个游戏的显示效果,其实还有点小清新的……特别是下棋的时候那个点击的爽感,还是不错的……

总结

这个程序是一个简单的井字棋游戏,我们通过Jetpack Compose来实现了界面,通过Kotlin来实现了逻辑。

至于程序的实现细节,源代码的解读,就放在下次。

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

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

相关文章

Spring Boot——日志介绍和配置

1. 日志的介绍 在前面的学习中&#xff0c;控制台上打印出来的一大堆内容就是日志&#xff0c;可以帮助我们发现问题&#xff0c;分析问题&#xff0c;定位问题&#xff0c;除此之外&#xff0c;日志还可以进行系统的监控&#xff0c;数据采集等 2. 日志的使用 在程序中获取日…

python opencv2

二、图像预处理 1、图像翻转 cv2.flip(src, flipCode) &#xff1a;flipCode &#xff1a;0&#xff1a;沿 X 轴翻转&#xff08;垂直翻转&#xff09;&#xff1b;1&#xff1a;沿 Y 轴翻转&#xff08;水平翻转&#xff09;&#xff0c;-1&#xff1a;沿 X 轴和 Y 轴翻转&am…

文件夹0字节:原因、恢复方案与预防措施

一、文件夹0字节现象描述 在日常使用电脑的过程中&#xff0c;我们可能会遇到这样一个问题&#xff1a;某个文件夹突然变成了0字节&#xff0c;这意味着该文件夹中的所有文件似乎都不见了&#xff0c;但实际上可能并未被彻底删除。文件夹0字节的情况常常让人困惑不解&#xff…

不需要复制粘贴,重复内容如何使用Mac快速完成输入

在Mac的日常使用中&#xff0c;必然有着重复内容需要重复输入的需求&#xff0c;但是Mac的剪切板又不具备历史记录的功能&#xff0c;所以只能一次次的复制粘贴&#xff0c;费时费力&#xff0c;那么该如何才能不这么麻烦 快捷短语就是为了解决这一问题而存在的 提前在设置好…

Java 打印流:PrintStream 与 PrintWriter 详解

在 Java 编程中&#xff0c;System.out.println() 的使用频率恐怕不亚于 main 方法的使用频率。其中&#xff0c;System.out 返回的正是打印流 PrintStream。除此之外&#xff0c;Java 还提供了另一个打印流 PrintWriter&#xff0c;它们分别继承自 OutputStream 和 Writer&…

RESTful风格

目录 一、什么是RESTful 1.1 RESTFul对WEB服务接口的规定包括&#xff1a; 1.2 REST对请求方式的具体约束如下&#xff1a; 1.3 REST对URL的具体约束如下&#xff1a; 1.4 RESTFul的核心概念&#xff1a; 二、RESTful风格与传统方式对比 三、RESTful风格演示 3.1 查询所…

运维工具之docker入门

1.容器与docker 1.什么是容器&#xff1f; 容器是一种轻量级的&#xff0c;可移植的软件运行环境。它将软件程序本身及软件依赖库打包在一起。可以在不同平台和系统上运行。 2.什么是LXC LXC就是Linux container,。LXC是一种虚拟化技术&#xff0c;可以在操作系统层级上为应…

Ubuntu系统安装NVIDIA驱动、CUDA、PyTorch等GPU深度学习环境

学习目标&#xff1a; 在Ubuntu系统上安装CUDA、PyTorch等GPU深度学习环境&#xff0c;主要目标是为深入研究深度学习和深度强化学习提供高效的计算支持。通过构建GPU环境&#xff0c;计划掌握深度学习的基本概念和算法应用&#xff0c;提高模型训练效率&#xff0c;特别是在复…

地理信息科学专业想搞GIS开发:学前端还是后端?

地理信息科学专业的同学是学前端开发比较好呢还是学后端开发比较好呢&#xff1f; 部分网友&#xff1a;学前端更好 主修前端更好&#xff0c;因为地信学后端&#xff0c;是卷不赢学计算机的 本科卷前端&#xff0c;硕士阶段可以卷后端 甚至有网友直呼&#xff0c;地信根本没有…

软件测试学习笔记丨Flask框架-请求与响应

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/23408 请求方法 from flask import Flaskapp Flask(__name__)app.route("/cases", methods["get"]) def get_case():return {"code": 0, "msg": &…

你敢想象吗?我能远程控制家里的电脑进入Bios

老家的爸妈用电脑也不知怎的&#xff0c;偶尔就不行了。这个偶尔还挺频繁的&#xff0c;我自己又会修电脑&#xff0c;但奈何距离太远&#xff01;如果叫别人上门修电脑&#xff0c;一次就是200…… 我的心在滴血啊在滴血&#xff5e;如果有这么一款产品&#xff0c;就算电脑无…

基于STM32的温湿度监测器教学

引言 随着科技的发展&#xff0c;温湿度监测在农业、仓储、环境监测等领域的应用越来越广泛。本文将指导您如何基于STM32开发一个简单的温湿度监测器&#xff0c;使用常用的DHT11或DHT22传感器进行数据采集&#xff0c;并将监测结果显示在LCD或OLED屏幕上。 项目名称 STM32温湿…

科研绘图系列:R语言组合堆积图(stacked plot)

文章目录 介绍加载R包数据数据预处理画图1画图2组合图形系统信息介绍 堆积图(Stacked Chart),也称为堆叠图,是一种常用的数据可视化图表,主要用于展示不同类别的数据量在总体中的分布情况。堆积图可以是柱状图、条形图或面积图的形式,其中各个类别的数据量被叠加在一起,…

红黑树的平衡之舞:数据结构中的优雅艺术

文章目录 前言&#x1f680;一、红黑树的介绍1.1 红黑树的概念1.2 红黑树的特点1.3 红黑树的性质 &#x1f680;二、红黑树结点的定义&#x1f680;三、红黑树的框架&#x1f680;四、旋转操作&#x1f680;五、红黑树的插入操作5.1 uncle结点存在且为红5.2 uncle结点不存在或者…

ONLYOFFICE 8.2版本产品评测——遥遥领先,助力自动化办公

ONLYOFFICE 产品测试体验报告总结 知孤云出岫-CSDN博客 目录 产品介绍——篇【1】 一.关于 ONLYOFFICE 桌面编辑器 二.关于 ONLYOFFICE 协作空间 三.关于 ONLYOFFICE 文档 四.关于 ONLYOFFICE的版本介绍 产品新功能——篇【2】 一.关于 ONLYOFFICE的新增功能介绍 二.ONL…

【Linux驱动开发】通过设备树节点来配置和调用GPIO(pinctrl节点和gpio-controller)

【Linux驱动开发】通过设备树节点来配置和调用GPIO&#xff08;pinctrl节点和gpio-controller&#xff09; 文章目录 设备树下pinctrl节点GPIO控制节点调用方法GPIO输入附录&#xff1a;嵌入式Linux驱动开发基本步骤开发环境驱动文件编译驱动安装驱动自动创建设备节点文件 驱动…

【Linux】安装 SQL Server 命令行工具 mssql-tools18(Ubuntu 22.04)

引言 mssql-tools18 是一个包含 Microsoft SQL Server 命令行工具的软件包。这些工具包括 sqlcmd 和 bcp。 sqlcmd 是一个允许你用命令行与 SQL Server 进行交互的工具。你可以用它来运行 SQL 脚本&#xff0c;执行数据库维护任务&#xff0c;以及进行其他数据库管理操作。 b…

库存管理内训课件|39页PPT

文件是一份关于库存管理的内训课件&#xff0c;内容涵盖了库存管理的定义、分类、作用、存在的问题、管控目标以及具体管控措施。以下是对课件内容的总结&#xff1a; 1. 定义及分类 库存&#xff1a;为满足未来需求而暂时闲置的有价值的资源&#xff0c;与物品是否处于运动状…

大零售时代下融合发展的新路径:定制开发技术的应用与思考

摘要&#xff1a;本文探讨在大零售背景下&#xff0c;传统零售边界模糊&#xff0c;融合成为趋势。分析大零售包含的跨行业跨业态融合等三个层面&#xff0c;重点阐述定制开发技术中的 21 链动模式、AI 智能名片和 S2B2C 商城小程序在推动大零售发展中的作用和意义&#xff0c;…

OceanBase 安装使用详细说明

OceanBase 安装使用详细说明 一、系统环境要求二、安装OceanBase环境方案一:在线下载并安装all-in-one安装包方案二:离线安装all-in-one安装包安装前的准备工作三、配置OceanBase集群编辑配置文件部署和启动集群连接到集群集群状态和管理四、创建业务租户和数据库创建用户并赋…