用 TypeScript 类型运算实现一个五子棋游戏

news2024/11/23 18:13:42

之前有看到有大佬用类型运算实现了中国象棋程序 和 Lisp 解释器 甚是震惊,之前不太会看不懂。

最近也学了点类型体操的内容想着自己也玩一下。选择五子棋的原因是相对来说规则是更简单一些的也比较好实现。此实现没有考虑性能上优化和最佳实现方式只关注功能的实现。以下变量全部为中文命名不要介意。

在这里插入图片描述

在这里插入图片描述

需求分析

首先我们了解下五子棋的规则。只实现核心的几个规则,一步一步的拆解去实现。

创建棋盘:创建一个棋盘的 “横线” 和 “纵线”,形成的交叉点就是落子点。

交替落子:实现 “黑棋” 和 “白棋” 交替落子,之前的落子点不能再落子。

判断胜利:当一方的棋子 “横联”、“纵连”、“左斜连”、“右斜连” 达到五个时候则为胜利。

创建棋盘

五子棋分为 “黑棋” 和 “白棋” 使用类型定义出来。

type 黑棋 = “⚫”;
type 白棋 = “⚪”;
type 棋子 = 黑棋 | 白棋;

创建一个 9 * 9 的棋盘。其实多大都无所谓只是现在有个问题没有解决,太大的棋盘在显示的时候会省略显示影响显示状态,这里就暂不考虑了。

type 棋盘 = {
“00”: [“01”, “02”, “03”, “04”, “05”, “06”, “07”, “08”, “09”];
“01”: [“□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”];
“02”: [“□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”];
“03”: [“□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”];
“04”: [“□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”];
“05”: [“□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”];
“06”: [“□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”];
“07”: [“□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”];
“08”: [“□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”];
“09”: [“□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”, “□□”];
};

创建落子点的限制,落子点为横纵的交叉点。这里使用字符串的分布式特性去创建。

type 横点 = “01” | “02” | “03” | “04” | “05” | “06” | “07” | “08” | “09”;
type 纵点 = “01” | “02” | “03” | “04” | “05” | “06” | “07” | “08” | “09”;
type 落子信息 = [棋子, 横点, 纵点];

type 交叉点 = ${横点}-${纵点};
type 拆分交叉点<S extends 交叉点> = S extends ${infer _横点}-${infer _纵点}
? [_横点, _纵点]
: [];
// test
type 测试拆分交叉点 = 拆分交叉点<“05-05”>;

以上就把棋盘的相关信息定义出来了。

交替落子

在五子棋规则中是交替落子:

如果当前位置中已有棋子则不能下棋。

如果上一步是【白棋】下的,那么下一步为【黑棋】下。反之如果上一步是【黑棋】下的,那么下一步为【白棋】下。

那么如何实现呢?这里最主要的是限制下一步棋的类型,所以我们需要在走完当前这一步棋之后返回一个新的类型。所以符合这种形式的就只有函数可以实现,执行一次函数后返回一个新的函数从而限制参数的类型,且可以无限调用。

type 交替落子限制<
可下棋子 extends 棋子 = 棋子,
可下交叉点 extends 交叉点 = 交叉点,
已下棋子 extends 棋子 | “” = “”,
已下交叉点 extends 交叉点 | “” = “”,
落子集合 extends 落子信息[] = []

= {
落子: <当前棋子 extends 可下棋子, 当前交叉点 extends 可下交叉点>(
棋子参数: 当前棋子,
交叉点参数: 当前交叉点
) => 交替落子限制<
Exclude<棋子, 当前棋子>,
Exclude<交叉点, 已下交叉点 | 当前交叉点>,
已下棋子,
已下交叉点 | 当前交叉点,
当前交叉点 extends 交叉点
? […落子集合, [当前棋子, …拆分交叉点<当前交叉点>]]
: 落子集合
;
落子结果: 落子集合;
};

declare function 创建交替落子(): 交替落子限制;
const 交替落子 = 创建交替落子();
// test
const 测试交替落子 = 交替落子.落子(“⚫”, “01-01”).落子(“⚪”, “01-02”);
const 测试交替落子结果 = 测试交替落子[“落子结果”];

可以看到已经限制到了下一步棋,并且获取到最终的落子结果集合。

在这里插入图片描述

在这里插入图片描述

更新棋盘

此时可以获取到最终的落子集合。遍历每个落子集合,根据【当前纵点】找到对应的横点集合,再根据【当前横点】和【横点集合】找到对应的位置更新为【当前棋子】。

更新棋盘交叉点就是通过索引找到对应的位置替换字符,因为我们传入是 01 这种字符串所以在查找时要转成数字。

export type 转数字<S extends string> = S extends `0${infer R extends number}`
  ? R
  : S extends `${infer R extends number}`
  ? R
  : never;
// test
type 测试转数字一 = 转数字<"01">;
type 测试转数字二 = 转数字<"10">;

type 更新棋盘交叉点<
  横点集合 extends string[],
  当前落子 extends 落子信息,
  当前索引 extends unknown[] = [unknown]
> = 横点集合 extends [infer _横点, ...infer _剩余横点 extends string[]]
  ? 当前索引["length"] extends 转数字<当前落子[2]>
    ? [当前落子[0], ...更新棋盘交叉点<_剩余横点, 当前落子, [unknown, ...当前索引]>]
    : [_横点, ...更新棋盘交叉点<_剩余横点, 当前落子, [unknown, ...当前索引]>]
  : [];
// test
type 测试更新棋盘交叉点 = 更新棋盘交叉点<["□□", "□□", "□□"], ["⚫", "01", "02"]>;

通过遍历棋盘的属性找到纵点的位置再更新棋盘交叉点完成一次更新。

type 更新一次棋盘<
当前棋盘 extends Record<string, string[]>,
当前落子 extends 落子信息

= {
[_纵点 in keyof 当前棋盘]: _纵点 extends 当前落子[1]
? 更新棋盘交叉点<当前棋盘[_纵点], 当前落子>
: 当前棋盘[_纵点];
};
// test
type 测试更新一次棋盘 = 更新一次棋盘<棋盘, [“⚫”, “05”, “05”]>;

type 更新棋盘<
当前棋盘 extends Record<string, string[]>,
落子集合 extends 落子信息[]

= 落子集合 extends [
infer _当前落子 extends 落子信息,
…infer _剩余落子 extends 落子信息[]
]
? 更新棋盘<更新一次棋盘<当前棋盘, _当前落子>, _剩余落子>
: 当前棋盘;
// test
type 测试更新棋盘 = 更新棋盘<棋盘, [[“⚫”, “04”, “04”], [“⚪”, “05”, “05”]]>;

当前的棋盘的显示状态算是处理完了。

在这里插入图片描述

判断胜利

在五子棋的规则中,如果横连、纵连、左斜连、右斜连达到五个为胜利。

在这里插入图片描述

查找分析

以最后一次落子的位置向四条线八个方向开始查找。

横连:向左查找【横点递减并纵点相同】的棋子 和 向右查找【横点递增并纵点相同】的棋子。

纵连:向上查找【纵点递减并横点相同】的棋子 和 向下查找【纵点递增并横点相同】的棋子。

左斜连:向左下查找【横点递减同时纵点递增】的棋子 和 向右上查找【横点递增同时纵点递减】的棋子。

右斜连:向左上查找【横点递减同时纵点递减】的棋子 和 向右下查找【横点递增同时纵点递增】的棋子。

查找连续落子

举个例子,当前落子为 [“⚫”, “03”, “01”] 那么左连的棋子为 [“⚫”, “02”, “01”],如果能找到那么就继续向左查找,如果不能找到代表中断了就停止查找。

这里涉及到一些加减的运算,比如 03 向左递减为 02。实现就是先把字符串转成数字,做完加减运算后再转为字符串。

export type 转数字<S extends string> = S extends `0${infer R extends number}`
  ? R
  : S extends `${infer R extends number}`
  ? R
  : never;
// test
type 测试转数字一 = 转数字<"01">;
type 测试转数字二 = 转数字<"10">;

export type 转字符串<N extends number> = 比较<10, N> extends true ? `0${N}` : `${N}`;
// test
type 测试转转字符串一 = 转字符串<1>;
type 测试转转字符串二 = 转字符串<10>;

export type 加<A extends number, B extends number> = [
  ...构建数组<A>,
  ...构建数组<B>
]["length"];
// test
type 测试加 = 加<3, 5>;

export type 减<A extends number, B extends number> = 构建数组<A> extends [
  ...args1: 构建数组<B>,
  ...args2: infer Rest
]
  ? Rest["length"]
  : never;
// test
type 测试减 = 减<5, 3>;

export type 比较<
  A extends number,
  B extends number,
  Result extends unknown[] = []
> = A extends B
  ? false
  : Result["length"] extends B
  ? true
  : Result["length"] extends A
  ? false
  : 比较<A, B, [unknown, ...Result]>;
// test
type 测试比较 = 比较<10, 5>;

export type 构建数组<
  Length extends number,
  Item = unknown,
  Result extends unknown[] = []
> = Result["length"] extends Length ? Result : 构建数组<Length, Item, [...Result, Item]>;
// test
type 测试构建数组 = 构建数组<5, number>;

type 查找类型 = 0 | 1 | -1; // 0: 不变、1: 递增、-1:递减
type 查找方向 = [查找类型, 查找类型]; // [横点查找类型, 纵点查找类型]
type 下一次查找位置<
  当前位置 extends string,
  当前查找类型 extends 查找类型
> = 当前查找类型 extends 0
  ? 当前位置
  : 当前查找类型 extends 1
  ? 加<转数字<当前位置>, 1> extends number
    ? 转字符串<加<转数字<当前位置>, 1>>
    : "00"
  : 当前查找类型 extends -1
  ? 减<转数字<当前位置>, 1> extends number
    ? 转字符串<减<转数字<当前位置>, 1>>
    : "00"
  : "00";
// test
type 测试下一次查找位置不变 = 下一次查找位置<"01", 0>;
type 测试下一次查找位置递增 = 下一次查找位置<"01", 1>;
type 测试下一次查找位置递减 = 下一次查找位置<"02", -1>;

以最后一次落子开始,向指定的方向递归查找并记录下来。

type 查找当前落子<
  落子集合 extends 落子信息[],
  当前落子 extends 落子信息
> = 落子集合 extends [
  infer _当前落子 extends 落子信息,
  ...infer _剩余落子 extends 落子信息[]
]
  ? _当前落子 extends 当前落子
    ? _当前落子
    : 查找当前落子<_剩余落子, 当前落子>
  : [];

type 查找连续落子<
  落子集合 extends 落子信息[],
  最后一次落子 extends 落子信息,
  当前查找方向 extends 查找方向,
  查找结果 extends 落子信息[] = []
> = 下一次查找位置<最后一次落子[1], 当前查找方向[0]> extends 横点
  ? 下一次查找位置<最后一次落子[2], 当前查找方向[1]> extends 纵点
    ? 查找当前落子<
        落子集合,
        [
          最后一次落子[0],
          下一次查找位置<最后一次落子[1], 当前查找方向[0]>,
          下一次查找位置<最后一次落子[2], 当前查找方向[1]>
        ]
      > extends 落子信息
      ? 查找连续落子<
          落子集合,
          [
            最后一次落子[0],
            下一次查找位置<最后一次落子[1], 当前查找方向[0]>,
            下一次查找位置<最后一次落子[2], 当前查找方向[1]>
          ],
          当前查找方向,
          [
            ...查找结果,
            [
              最后一次落子[0],
              下一次查找位置<最后一次落子[1], 当前查找方向[0]>,
              下一次查找位置<最后一次落子[2], 当前查找方向[1]>
            ]
          ]
        >
      : 查找结果
    : 查找结果
  : 查找结果;
// test
type 测试查找连续落子左连 = 查找连续落子<
  [["⚫", "01", "01"], ["⚫", "02", "01"], ["⚫", "04", "01"], ["⚫", "05", "01"]],
  ["⚫", "03", "01"],
  [-1, 0]
>;
type 测试查找连续落子右连 = 查找连续落子<
  [["⚫", "01", "01"], ["⚫", "02", "01"], ["⚫", "04", "01"], ["⚫", "05", "01"]],
  ["⚫", "03", "01"],
  [1, 0]
>;

在这里插入图片描述

在这里插入图片描述

判断对局状态

横连胜利:向左查找【横点递减并纵点相同】的棋子 和 向右查找【横点递增并纵点相同】的棋子。

type 横连胜利<落子集合 extends 落子信息[], 最后一次落子 extends 落子信息> = [
…查找连续落子<落子集合, 最后一次落子, [-1, 0]>,
最后一次落子,
…查找连续落子<落子集合, 最后一次落子, [1, 0]>
];

纵连胜利:向上查找【纵点递减并横点相同】的棋子 和 向下查找【纵点递增并横点相同】的棋子。

type 纵连胜利<落子集合 extends 落子信息[], 最后一次落子 extends 落子信息> = [
…查找连续落子<落子集合, 最后一次落子, [0, -1]>,
最后一次落子,
…查找连续落子<落子集合, 最后一次落子, [0, 1]>
];

左斜连胜利:向左下查找【横点递减同时纵点递增】的棋子 和 向右上查找【横点递增同时纵点递减】的棋子。

type 左斜连胜利<落子集合 extends 落子信息[], 最后一次落子 extends 落子信息> = [
…查找连续落子<落子集合, 最后一次落子, [-1, 1]>,
最后一次落子,
…查找连续落子<落子集合, 最后一次落子, [1, -1]>
];

右斜连胜利:向左上查找【横点递减同时纵点递减】的棋子 和 向右下查找【横点递增同时纵点递增】的棋子。

type 右斜连胜利<落子集合 extends 落子信息[], 最后一次落子 extends 落子信息> = [
…查找连续落子<落子集合, 最后一次落子, [-1, -1]>,
最后一次落子,
…查找连续落子<落子集合, 最后一次落子, [1, 1]>
];

其中一个方向达到五个则胜利。

type 判断对局状态<
落子集合 extends 落子信息[],
最后一次落子 extends 落子信息 | []

= 最后一次落子 extends 落子信息
? 横连胜利<落子集合, 最后一次落子>[“length”] extends 5
? 对局结束【${最后一次落子[0]}】✌🏻✌🏻✌🏻
: 纵连胜利<落子集合, 最后一次落子>[“length”] extends 5
? 对局结束【${最后一次落子[0]}】✌🏻✌🏻✌🏻
: 左斜连胜利<落子集合, 最后一次落子>[“length”] extends 5
? 对局结束【${最后一次落子[0]}】✌🏻✌🏻✌🏻
: 右斜连胜利<落子集合, 最后一次落子>[“length”] extends 5
? 对局结束【${最后一次落子[0]}】✌🏻✌🏻✌🏻
: “进行中”
: “未开始”;
// test
type 测试左斜连胜利 = 判断对局状态<
[
[“⚫”, “06”, “04”],
[“⚫”, “05”, “05”],
[“⚫”, “09”, “01”],
[“⚫”, “08”, “02”],
[“⚫”, “04”, “04”] // 不连
],
[“⚫”, “07”, “03”]
;

在这里插入图片描述

创建对局

创建对局我们只需要把上面的功能组合起来就可以了。

type 最后落子<落子集合 extends 落子信息[]> = 落子集合 extends [
…infer _剩余落子,
infer _最后落子
]
? _最后落子
: [];

type 转换棋盘<横点集合 extends string[]> = 横点集合 extends [
infer _当前横点 extends string,
…infer _剩余横点 extends string[]
]
? ${_当前横点}|${转换棋盘<_剩余横点>}
: “”;

type 渲染棋盘<当前棋盘 extends Record<string, string[]>> = {
[_纵点 in keyof 当前棋盘]: 转换棋盘<当前棋盘[_纵点]>;
};

const 落子集合 = 交替落子
.落子(“⚫”, “01-01”)
.落子(“⚪”, “01-02”)
.落子(“⚫”, “02-02”)
.落子(“⚪”, “01-03”)
.落子(“⚫”, “03-03”)
.落子(“⚪”, “01-04”)
.落子(“⚫”, “04-04”)
.落子(“⚪”, “01-05”)
.落子(“⚫”, “05-05”);
type 落子结果 = typeof 落子集合[“落子结果”];
type 棋盘状态 = 渲染棋盘<更新棋盘<棋盘, 落子结果>>;
type 对局状态 = 判断对局状态<落子结果, 最后落子<落子结果>>;

在这里插入图片描述

在这里插入图片描述

写在最后

其实写这个并没有什么实际的用途,也算是锻炼熟练度了。👋🏻👋🏻

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

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

相关文章

详细步骤讲解matlab代码通过Coder编译为c++并用vs2019调用

项目上需要C&#xff0c;奈何本人不会&#xff0c;所以就用matlab写好测试后&#xff0c;用matlab Coder编译为c并用vs2019调用 一个简单的例子&#xff0c;求取两个4*4矩阵相加后&#xff0c;在求取最大值与最小值。matlab代码如下 function [a,b] min_max(m,n)temp mn;a m…

STM32F407 电机编码器测量

文章目录一、STM32F407 定时器编码器功能1.1 STM32定时器简介1.2 STM32定时器编码器功能二、带编码器的直流电机三、代码与验证3.1 初始化代码3.2 验证一、STM32F407 定时器编码器功能 1.1 STM32定时器简介 STM32的定时器功能非常强大&#xff0c;根据官方手册&#xff0c;定…

旅游网页设计 web前端大作业 全球旅游私人订制 旅游公司网站模板(HTML+CSS+JavaScript)

&#x1f468;‍&#x1f393;学生HTML静态网页基础水平制作&#x1f469;‍&#x1f393;&#xff0c;页面排版干净简洁。使用HTMLCSS页面布局设计,web大学生网页设计作业源码&#xff0c;这是一个不错的旅游网页制作&#xff0c;画面精明&#xff0c;排版整洁&#xff0c;内容…

OctaneRender界面布局自定义界面教程丨使用教程

您可以通过单击并拖动每个窗格左上角的方块&#xff08;图 1&#xff09;来重新排列 OctaneRender 界面&#xff08;图形编辑器、渲染视口、节点检查器和大纲视图&#xff09;中每个窗格的窗口。 图 1&#xff1a;窗格排列图标 用任何鼠标按钮单击同一个方块会显示更多用于…

SpringBoot SpringBoot 开发实用篇 5 整合第三方技术 5.20 ActiveMQ 安装

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇5 整合第三方技术5.20 ActiveMQ 安装5.20.1 下载5.20.2 安装5.20.3 使…

[附源码]SSM计算机毕业设计基于的城镇住房公积金管理系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Unity Hair 毛发系统 初体验

文章目录&#x1f388; 简介&#x1f388; 所需环境&#x1f388; 下载安装&#x1f388; 使用初体验&#x1f36d; 创建一个Hair示例&#x1f36d; Hair Asset&#x1f36d; 尝试给Avatar创建头发&#x1f36d; 如何更改材质&#x1f388; 简介 8月份的时候Unity官方发布了Ha…

第7章 Elasticsearch面试题

7 . 1 为什么要使用Elasticsearch? 系统中的数据&#xff0c;随着业务的发展&#xff0c;时间的推移&#xff0c;将会非常多&#xff0c;而业务中往往采用模糊查询进行数据的搜索&#xff0c;而模糊查询会导致查询引擎放弃索引&#xff0c;导致系统查询数据时都是全表扫描&am…

即将见面:SpreadJS V16:重大改进

内容摘自互联网&#xff1a;&#xff1a;&#xff1a;&#xff1a;&#xff1a;&#xff1a;&#xff1a; 新功能背景&#xff1a;在SpreadJS V16之前&#xff0c;关于文件toJSON()之后&#xff0c;生成的json文件太大&#xff0c;一直被很多客户诟病。例如&#xff0c;同样一…

PySide创建界面关联项目(五) 百篇文章学PyQT

本文章是百篇文章学PyQT的第五篇&#xff0c;本文讲述如何使用PySide创建UI界面&#xff0c;并且关联入PyCharm 新建的项目中成功运行第一个PyQT程序&#xff0c;博主在本篇文章中将遇到和踩过的坑总结出来&#xff0c;可以供大家参考&#xff0c;希望大家安装顺利。包括 安装、…

我的Vue组件化开发首个项目todolist

TodoList 学习笔记&#xff1a; 总结TodoList案例 1.组件化编码流程: (1).拆分静态组件:组件要按照功能点拆分&#xff0c;命名不要与htm|元素冲突。 (2).实现动态组件:考虑好数据的存放位置&#xff0c;数据是一个组件在用&#xff0c; 还是一些组件在用: . 1).-个组件在…

Vue简单实例——从webpack到vue,再到weex

这一章节&#xff0c;我们主要针对从webpack&#xff0c;vue&#xff0c;weex的框架结构上来说明对比这三个框架的区别 主要功能&#xff1a; webpack&#xff1a; webpack是前端项目工程化的具体解决方案。 它提供了友好的前端模块化开发支持&#xff0c;以及代码压缩混淆&…

基于身份的分段:三种技术路线解析与建议

SmartX 趋势分享 SmartX 趋势分享由 SmartX 团队内部分享的权威机构市场报告、全球重要媒体文章精选整理而成。内容涉及现代数据中心相关产业趋势以及金融、医疗、制造等行业全球用户需求与实践前沿洞察。在“零信任实践”系列的第一篇文章中&#xff0c;我们介绍了两种实现零信…

基于springboot“漫画之家”系统设计与实现-计算机毕业设计源码+LW文档

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&a…

vue3+ts部分场景示例

模板语法 插值变量 div{{插值}}div const message: number 84; 指定变量为数字类型 const message: string小明 ; 指定字符串类型 const message: booleanfalse ; 指定布尔值类型 const message: any小明 ; any指定任意类型 const message: object{} ; 指定对象…

代码随想录算法训练营第七天|二叉树(截止到层序遍历)

二叉树的递归遍历 递归遍历是最简单的 // 前序 class Solution { public:void traversal(TreeNode* cur, vector<int>& vec) {if (cur NULL) return;vec.push_back(cur->val); // 中traversal(cur->left, vec); // 左traversal(cur->right, vec); //…

【高级篇】线程与线程池

一、线程回顾 1、初始化线程的 4 种方式 1&#xff09;、继承 Thread public static class Thread01 extends Thread{Overridepublic void run() {System.out.println("当前线程&#xff1a;"Thread.currentThread().getId());int i 10 / 2;System.out.println(&qu…

web前端期末大作业——HTML+CSS简单的旅游网页设计与实现

&#x1f468;‍&#x1f393;静态网站的编写主要是用 HTML DⅣV CSSJS等来完成页面的排版设计&#x1f469;‍&#x1f393;&#xff0c;一般的网页作业需要融入以下知识点&#xff1a;div布局、浮动定位、高级css、表格、表单及验证、js轮播图、音频视频Fash的应用、uli、下拉…

项目上云实战:如何把Java项目搬上云服务器?

1.中小型企业项目开发完成后应如何运行&#xff1f; 最近在后台私信中&#xff0c;很多小伙伴问询博主&#xff0c;中小企业项目开发完成后&#xff0c;是否在pc机上直接运行。答案是否定的&#xff0c;专业的软件开发企业都会选择linux服务器作为运行环境&#xff0c;企业服务…

[附源码]java毕业设计学生档案管理系统论文

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…