小游戏开发:使用 React 和 Redux Tool Kit 实现俄罗斯方块

2023-11-19

大家好,我是若川。我持续组织了近一年的源码共读活动,感兴趣的可以 点此扫码加我微信 lxchuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外:目前建有江西|湖南|湖北籍前端群,可加我微信进群。欢迎星标我的公众号~不错过推文~

俄罗斯方块是一款经典小游戏。通过从零开发一款小游戏,我们可以更好地了解开发使用的语言,同时也得到更强烈的“所见即所得”的反馈,所以开发小游戏会是不错的练手项目。

7897bc48e0f9bee906375afb8098ff49.gif

游戏动态展示

从一个主力语言是JavaScript的开发者角度来分析俄罗斯方块,不同形状的方块的下落、移动、消除其实是 复杂的状态管理和组件通信。这不刚好就是React和Redux的射程范围嘛!(并没有说它们适合游戏开发)

我将通过这篇博文拆解实现俄罗斯方块的一些核心逻辑,以及Redux Tool Kit的使用。

你可以在我的Github仓库上找到项目代码,也可以通过Github Page来试玩这个游戏。

本文涉及的技术

  • JavaScript数组高阶函数

  • 使用Redux Tool Kit对React单页面项目进行状态管理

  • 使用requestAnimationFrame实现动画效果

文件结构

项目总结构

|-- src
    |-- App.js
 |-- index.js
 |-- store.js
 |-- features/
  |-- game/
   |-- components/
   |-- game-slice.js
 |-- styles/
 |-- utils/

我是通过create-react-app创建的项目,游戏界面及功能所有代码都在src路径下:

  • 我保留了index.jsApp.js,同时添加了store.js来处理Redux store的配置代码;

  • 样式全部放在了styles文件夹中,游戏运行的核心逻辑放到了utils文件夹中;

  • Redux官方推荐在使用RTK(Redux Tool Kit)时,创建一个features目录,并把不同的功能模块放在这个目录下,每一个功能模块包含自己的组件、reducer和其他相关文件(如与后端通信的代码),所以我在features目录下创建了game文件夹,并在其下创建了components目录和game-slice.js

组件结构

我规划的组件包括:

|-- components/
 |-- Board.js
 |-- Control.js
 |-- MessagePopup.js
 |-- NextBlock.js
 |-- ScoreBoard.js
 |-- Square.js

fe1e443c7befa38bcde341ef5d894997.jpeg

组件展示
  • Board.js是主游戏区

  • Control.js是按钮控制面板

  • MessagePopup.js是游戏结束时弹出的消息框

  • NextBlock.js是下一个形状提示框

  • ScoreBoard.js显示当前分数和历史最高分数

  • Square.js是组成主游戏区和提示框的小方块

核心UI组件搭建的逻辑

游戏区域的实现

主游戏区框架的实现

73aa7c9429974c7704d19aa151bde79b.png

将主游戏区看作一个10*18的网格,可以通过单项数据为0的二维数组实现:

//src/utils/index.js

const boardDefault = () => {
    const rows = 18; //共18行
 const cols = 10; //共10列 
 const array = Array.from(Array(rows), () => Array(cols).fill(0));
 return array;
};

注:在示意图中,我标注了x和y象限,因为之后会使用x和y的值来判断形状在主游戏区的位置。

形状的实现

既然主游戏区可以通过数组来实现,那么不同形状的方块也可以通过数组来实现:

设定每一种形状都用一个4*4的网格作为容器:

[

 [0, 0, 0, 0],
 [0, 0, 0, 0],
 [0, 0, 0, 0],
 [0, 0, 0, 0],

],

在俄罗斯方块游戏中,不同的形状的颜色不同,所以可以用整数1,2,3,4...来区分它们。

使用整数的另一个好处是可以和样式绑定,先提前设置好每个颜色对应的编号:

:root {
 --color-0: #282c34;
 --color-1: #ff6600;
 --color-2: #eec900;
 ...
 --color-7: #ff0000;
}

.color-0 {
 background-color: var(--color-0);
}

.color-1 {
 background-color: var(--color-1);
}

...
  
.color-7 {
 background-color: var(--color-7);
}

然后就可以通过数字1,2,3,4...在前文的4*4网格数组中的排列来表达不同的形状了,需要注意的是每个形状还有旋转后的样式也要考虑进去。

c9f684390500f929bb8ed482eb2068a3.png

长条形状

比方说一个长条形状及它旋转后可以表示为:

//I

[
 [
  [0, 0, 0, 0],
  [1, 1, 1, 1],
  [0, 0, 0, 0],
  [0, 0, 0, 0],
 ],

 [
  [0, 1, 0, 0],
  [0, 1, 0, 0],
  [0, 1, 0, 0],
  [0, 1, 0, 0],
 ],
],
  • 把单个形状及其旋转后的样式放在同一个数组中,引用时就可以通过索引号来获得形状的旋转效果。

  • 再把所有形状的数组汇总到一个数组中,这样通过索引号就把表示颜色的数字和形状挂钩了。

形状到主游戏区的映射

我们完成了主游戏区和形状分别的实现,那么当游戏进行时,如何把形状映射到主游戏区呢?

在React中,每一次渲染都相当于一次快照,那么就可以把游戏进行时,形状在主游戏区域的每一次移动都看作独立的一次形状数组到主游戏区数组的映射

325a7b5593d0b1ac69be6e0152be2eed.png

bc1b1f43a3560f708a18d8e5203f6eea.jpeg

具像化我们映射过程就是从以上第一幅图到第二幅图。

代码实现如下:

//src/features/game/components/Board.js

//这里的board变量是10*18的由0组成的二位数组
const boardSquare = board.map((rowArray, row) => {
 return rowArray.map((square, col) => {
 //col(0-9)row(0-17)
 const blockX = col - x;
 const blockY = row - y;
 //square为0
 let color = square;
 //生成移动的block的颜色
 if (
  blockX >= 0 &&
  blockX < block.length &&
  blockY >= 0 &&
  blockY < block.length
 ) {
    //这里的block变量引用了从函数外部取得的表示形状的数组
  color = block[blockY][blockX] === 0 ? color : blockColor;
 }
 //生成key
 const k = row * board[0].length + col;
 return <Square key={k} color={color} />;
 });
});

让我们看看上面代码发生了什么:

  • x,y是形状起始的横轴(行)和纵轴(列)的坐标,我设定的初始值为x=4, y=-5,这样每一个形状的初始位置就位于主游戏区差不多正上方,随着形状的移动,这两个值也会发生变化;

  • boardSquare函数实际上做了两件事:将我们用数组表达的10*8主游戏区展示到UI;根据主游戏区纵坐标(0-17)、横坐标(0-9)与xy的差值来定位,将形状数组映射到主游戏区;

  • 首先通过一组嵌套结构的Array.map()的方法,遍历主游戏区所有网格,并将所有网格的color设置为0

  • 在遍历主游戏区的同时,通过blockX和blockY坐标来锁定位于形状数组内部的方块,如果它的值不为0的话,就返回对应数字的颜色;

  • 因为React要求遍历的组件包含一个独一无二的key,所以通过row * board[0].length + col生成key。

判断边界的逻辑

移动范围边界

形状在主游戏区的移动遵守一定的规则:

  1. 左右下边都不可越界

  2. 如果在主游戏区碰到其他的形状,也不得移动。

我们只用检查形状内部每一个方块是否符合以上规则,就可以判断形状是否可以移动了。

此处还需要注意的是,每一形状都被放置在4*4的网格中,所以要排除方块值为0情况:

//src/utils/index.js

const canMoveTo = (shape, board, x, y, rotation) => {
 const currentShape = shapes[shape][rotation];
 for (let row = 0; row < currentShape.length; row++) {
  for (let col = 0; col < currentShape[row].length; col++) {
   if (currentShape[row][col] !== 0) {
    const proposedX = col + x;
    const proposedY = row + y;
   if (proposedY < 0) {
    continue;
    }
   const possibleRow = board[proposedY];
   if (possibleRow) {
    if (
     possibleRow[proposedX] === undefined ||
     possibleRow[proposedX] !== 0
     ) {
      return false;
     }
    } else {
    //超越底线
      return false;
     }
    }
   }
  }
 //默认可以移动
 return true;
};
  • 使用canMoveTo函数来判断是否可以移动,函数接受shape(形状数组)、board(主游戏区数组)、x,y(形状定位坐标)、rotation(旋转索引号)作为参数,返回布尔值。

  • 首先默认可以移动;

  • 利用两个嵌套Array.map()遍历形状数组内部的方块,如果方块的值为非0,就根据xy的值来判断方块位于游戏主区的位置,即proposedXproposedY

  • 因为形状的初始情况是位于游戏主区上方,不在主区内,所以存在proposedY小于0的情况,这个时候继续遍历就好;

  • 根据propsedY可以推断出方块位于主游戏区的行数possibleRow,如果不存在possibleRow的话,则说明方块已经超越了主游戏区的底边,返回false

  • possibleRow[proposedX]来定位形状方块位于主游戏区该行的哪一个方块,如果值不为0或者为undefined的话,说明主游戏区上已经有其他形状的方块或者超越主游戏区左右边界,返回false

消除条件

当主游戏区一整行填满形状方块就会消除,放在数组的框架下思考,就是检查一行是否全不为0。如果符合条件就删除一整行,然后在数组最开始重新添加一行:

//src/utils/index.js

const checkRows = (board) => {
 for (let row = 0; row < board.length; row++) {
 //检查是否一整行都不为'0'
  if (board[row].indexOf(0) === -1) {
  //如果是,删除这一行
    board.splice(row, 1); 
    //同时在数组最开始添加一行新的数组
    board.unshift(Array(10).fill(0));
   }
 }
}

状态管理

使用Redux Tool Kit

在这个应用中,我使用Redux Tool Kit(后文简称RTK)来实现状态管理。

RTK对比传统的Redux来说的优势在于:

  1. 更少的样板代码

  2. RTK通过Immer库来实现不可变数据,提高应用程序的性能。

创建Slice

在RTK中,reducer和action集合到slice中,可以通过引用createSlice来实现:

//src/features/game/game-slice.js

import {createSlice} from '@reduxjs/toolkit'

const gameSlice = createSlice({
 name:'game',
 initialState: {...},
 reducer: {
  rotate:(state, action) =>{
   ...
   },
  moveRight: (state, action) =>{
   ...
   },
   ...
  },
})

export const {rotate, moveRight} = gameSlice.actions
export default gameSlice.reducer;

注:在传统方式中若要修改不可变数据,需要手动复制原始数据,并在副本上进行修改,由于immer库的存在,在编写reducer的时候,可以直接修改state,详细代码可以查看github仓库。

创建store

//src/store.js

import { configureStore } from '@reduxjs/toolkit';
import gameReducer from './features/game/game-slice';

export const store = configureStore({ reducer: { game: gameReducer}})

将数据提供给应用

//src/index.js

import { Provider } from 'react-redux';
import { store } from './store';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
 <React.StrictMode>
  <Provider store={store}>
   <App />
  </Provider>
 </React.StrictMode>
);

使用数据

//src/features/game/components/Control.js

import { useSelector, useDispatch } from 'react-redux';
import { rotate } from '../game-slice';

export default function Controls() {
 const dispatch = useDispatch();
 const isRunning = useSelector((state) => state.game.isRunning);
    const gameOver = useSelector((state) => state.game.gameOver);

 const handleRotate = () => {
  if (!isRunning || gameOver) return;
  dispatch(rotate());
 };

 return (
  <div className="controls">
  {/* rotation */}
   <button
    disabled={!isRunning || gameOver}
    className="control-button"
    onClick={handleRotate}
   >      
    Rotate
   </button>

下落动画的实现

Delta Time

Delta time(Δt)是一个计算两个时间点之间经过的时间差的量。在游戏开发中,delta time 指的是每一帧之间的时间间隔。

实现方块的下落动画,会运用到delta time

这里我们需要计算两次调用requestAnimationFrame的时间差的值,如果累计差值比我们设定的游戏速度要大,就触发moveDownaction。

形状的下落实际上就是在一定时间间隔下更新画面(触发moveDown这个action),不就是定时器嘛!

使用requestAnimationFrameuseRefuseEffect实现定时器的原因

  • requestAnimationFrame

一般想到定时器会想到setIntervalsetTimeout,但实际上使用requestAnimationFrame的性能更好。

这是因为requestAnimationFrame更符合屏幕的刷新率,通常屏幕的刷新率是60Hz,也就意味着浏览器每间隔16.7毫秒(1000毫秒/60)就会更新一次页面。requestAnimationFrame的回调函数会在浏览器准备好下一帧时调用,从而确保每一帧都能够在屏幕刷新之前完成绘制。

setTimeoutsetInterval的时间并不精准,可能会出现卡顿、延迟等性能问题。

  • useRef

React中的函数组件就像一个快照,每次渲染完成后值就会丢失。所以如果需要让函数“记住”值的话,就需要使用额外的钩子。

在俄罗斯方块游戏中,我们需要对形状下落的帧进行记录,这个值不受组件的生命周期影响,因此需要useRef

  • useEffect

游戏中只需要在特定时间调用控制下落的函数,并且这个过程是Board.js组件的一个副作用,因此需要使用useEffect

//src/utils/useTimer.js

import { useEffect, useRef } from 'react';
import { useDispatch } from 'react-redux';

export function useTimer(flagVal, benchmark, func) {
 const requestRef = useRef();
 const lastUpdateTimeRef = useRef(0);
 const progressTimeRef = useRef(0);
 
 const dispatch = useDispatch();

 const update = (time) => {
  requestRef.current = requestAnimationFrame(update);
  if (!flagVal) return;
  if (!lastUpdateTimeRef.current) {
   lastUpdateTimeRef.current = time;
  }
 
  const deltaTime = time - lastUpdateTimeRef.current;
  progressTimeRef.current += deltaTime;
 
  if (progressTimeRef.current > benchmark) {
  dispatch(func());
  progressTimeRef.current = 0;
  }
  lastUpdateTimeRef.current = time;
 };

 useEffect(() => {
  requestRef.current = requestAnimationFrame(update);
  return () => cancelAnimationFrame(requestRef.current);
 }, [flagVal]);
}
  • 使用useRef创建了三个引用变量requestReflastUpdateTimeRefprogressTimeRef,用于存储定时器的状态和进度。

  • requestRef用于存储当前动画帧的引用, lastUpdateTimeRef用于存储上一次更新的时间戳,progressTimeRef用于存储已经过去的时间。

  • update函数中实现定时器逻辑。

  • 首先使用requestAnimationFrame来获取下一帧的引用,并存储到requestRef.current中,以实现流畅的动画效果;

  • 然后通过flagVal的值,来决定是否退出函数;

  • 接着如果没有lastUpdateTimeRef.current的值,就将当前的时间戳(time)存储到里面。

  • 创建deltaTime,它的值为当前时间戳和lastUpdateTimeRef.current的时间差,将每次更新得到的deltaTime都添加到progressTimeRef.current中,记录累计过去的时间;

  • 如果这个累计值大于我们设定的时间间隔benchmark,就执行传入的函数,执行完毕后,将累计时间清零;

  • 最后将当前时间戳存储在lastUpdateTimeRef.current中,以便下一次调用时使用。

  • 在使用这个定时器时,我们传入应用的状态属性isRunning作为flagVal来判断是否需要退出函数,speed作为benchmark来和累积值做对比,控制形状的下移动画的速度,moveDown作为func来实现方块下移的动作。

总结

本项目通过数组实现了基本的游戏静态画面,动画部分使用了requestAnimationFrame,游戏复杂的状态管理借助了更简洁的RTK。

如果你仔细观察项目代码,会发现我还使用了第三方库Redux Persist来存储最高分数。

如果你想要动手实验一下我在文章中提到的逻辑和状态管理,我准备了starter仓库,它包含了基础的框架和样式,你可以在此基础上搭建属于你的俄罗斯方块,当然你也可以在我的项目基础上添加新的内容,如不同的关卡(下落速度不同),将按键和键盘绑定等。

希望阅读这篇文章让你有所收获,祝你实验愉快!

代码仓库

  • 完整项目

  • 仅UI部分

  • 试玩

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

小游戏开发:使用 React 和 Redux Tool Kit 实现俄罗斯方块 的相关文章

  • 了解设置 JQuery 变量

    了解设置 JQuery 变量 最近 我通过在 StackOverflow 上遇到的另一个问题寻找帮助 了解到如何设置 JQuery 变量 如下所示 您可以通过简单地调用变量来创建输入字段 并且锚变量似乎也定义了样式 var clicked
  • Android 设备上的 PhoneGap 蓝牙插件

    我一直在尝试让 PhoneGap 工作的蓝牙插件 但我似乎不知道哪里出了问题 首先 我的测试设备是 Galaxy S3 GT 19305T 应用程序是使用PhoneGap CLI http docs phonegap com en 3 0
  • jquery.find() 可以只选择直接子项吗?

    我应该向 jQuery find 提供什么参数来选择元素子元素而不选择其他元素 我不能用 gt 引导选择器 而用 将选择所有后代 而不仅仅是直接子代 我知道 jQuery children 但这是一个库 因此用户能够提供自己的选择器 并且我
  • 解析“流”JSON

    我在浏览器中有一个网格 我想通过 JSON 将数据行发送到网格 但浏览器应该在接收到 JSON 时不断解析它 并在解析时将行添加到网格中 换句话说 在接收到整个 JSON 对象后 不应将行全部添加到网格中 应该在接收到行时将其添加到网格中
  • 如何防止 Iframe 在与浏览器交互后弄乱浏览器的历史记录?

    因此 就我而言 我使用 Iframe 将 Grafana 附加到我的页面 这为我提供了漂亮且易于使用的图表 可以注意到 每次在图表上进行放大或缩小 使用鼠标单击 交互后 Grafana 的 Iframe 都会在我的 Angular 页面上触
  • Javascript正则表达式用于字母字符和空格? [关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 我需要一个
  • 除了更改标题之外,如何在 Firefox 中强制另存为对话框?

    有没有办法在 ff 中强制打开 www example com example pdf 的另存为对话框 我无法更改标题 如果您可以将文件以 Base64 格式输出到客户端 则可以使用 data uri 进行下载 location href
  • 如何将 Google Charts 与 Vue.js 库一起使用?

    我正在尝试使用 Vue js 库使用 Google Charts 制作图表 但我不知道如何添加到 div 这是我尝试做的 这是如何使用普通 javascript 添加图表 这是文档的代码示例 https developers google
  • React - 无法读取未定义的属性[重复]

    这个问题在这里已经有答案了 通常 当我单击子组件中的菜单项时 它会调用 this handlesort 这是一个本地函数 处理排序从我的父组件中获取 onReorder 属性 onReorder 调用名为 reOrder 的本地函数 它设置
  • 在 webpack 2.x 中使用 autoprefixer 和 postcss

    如何使用autoprefixer使用 webpack 2 x 以前 它曾经是这样的 module loaders test scss loader style css sass postcss postcss gt return autop
  • Firefox 书签探索未超过 Javascript 的第一级

    我已经编写了一些代码来探索我的 Firefox 书签 但我只获得了第一级书签 即我没有获得文件夹中的链接 e g 搜索引擎 雅虎网站 谷歌网站 在此示例中 我只能访问 Search engines 和 google com 不能访问 yah
  • 提交表单并重定向页面

    我在 SO 上看到了很多与此相关的其他问题 但没有一个对我有用 我正在尝试提交POST表单 然后将用户重定向到另一个页面 但我无法同时实现这两种情况 我可以获取重定向或帖子 但不能同时获取两者 这是我现在所拥有的
  • Grails 在 javascript 内的 GSP 站点中使用 grails var

    我有一个在 GSP 文件中的 javascript 代码中使用 grails 变量值的问题 例如 我有一个会话值session getAttribute selectedValue 我想在 javascript 代码部分使用这个值 我现在的
  • Angular 2+ 安全性;保护服务器上的延迟加载模块

    我有一个 Angular 2 应用程序 用户可以在其中输入个人数据 该数据在应用程序的另一部分进行分析 该部分仅适用于具有特定权限的人员 问题是我们不想让未经授权的人知道how我们正在分析这些数据 因此 如果他们能够在应用程序中查看模板 那
  • Laravel 中只向登录用户显示按钮

    如果我以 John 身份登录 如何才能只显示 John 的红色按钮而不显示 Susan 的红色按钮 测试系统环境 Win10 Laravel5 4 Mysql5 7 19 table class table table responsive
  • 如何在类似控制台的环境中运行 JavaScript?

    我正在尝试遵循这里的示例 http eloquentjavascript net chapter2 html http eloquentjavascript net chapter2 html and print blah 在浏览器中运行时
  • Javascript转换时区问题

    我在转换当前时区的日期时间时遇到问题 我从服务器收到此日期字符串 格式为 2015 10 09T08 00 00 这是中部时间 但是当我使用 GMT 5 中的 new Date strDate 转换此日期时间时 它返回给我的信息如下 这是不
  • 条件在反应本机生产中失败,但在开发中有效

    我创建了一个反应本机应用程序 我需要通过它进行比较 如果属实 就会执行死刑 问题是 该条件适用于 React Native 开发模式 而不适用于 React Native 生产版本 我使用 firebase 作为数据库 也使用 redux
  • Safari 支持 JavaScript window.onerror 吗?

    我有一个附加到 window onerror 的函数 window onerror function errorMsg url line window alert asdf 这在 firefox chrome 和 IE 中工作正常 但在 s
  • 如何获取浏览器视口中当前显示的内容

    如何获取当前正在显示长文档的哪一部分的指示 例如 如果我的 html 包含 1 000 行 1 2 3 9991000 并且用户位于显示第 500 行的中间附近 那么我想得到 500 n501 n502 或类似的内容 显然 大多数场景都会比

随机推荐

  • c++编写COM组件,并使用该组件

    在网上看了很多个介绍com组件的方法 对于一个新手来说看很久都看不懂 自己项目需要实现com 于是自己整理了一个文档和代码 先记录下来 以防以后用的上 步骤如下 1 新建ATL项目 你也可以是其他项目 只要是dll就行 可以支持MFC AT
  • [Excel VBA]如何拷贝数组?

    本文翻译至 http itpro nikkeibp co jp atcl column 15 090100207 090100143 ST system Variant型变量 数组 数组是可以保存多个值的 一种变量 变量是独幢楼房的话 数组
  • 详解java设计模式之-----观察者模式

    观察者模式 对象间的联动 什么是观察者模式呢 首先 看一个故事 红灯停 绿灯行 在日常生活中 交通信号灯装点着我们的城市 指挥着日益拥挤的城市交 通 当红灯亮起 来往的汽车将停止 而绿灯亮起 汽车可以继续前行 在这个过程中 交 通信号灯是汽
  • 串行接口的工作原理和实现

    串口的结构和工作原理 通用异步收发传输器 Universal Asynchronous Receiver Transmitter 通常称作UART 它将要传输的资料在串行通信与并行通信之间加以转换 作为把并行输入信号转成串行输出信号的芯片
  • postman接口测试的关联测试

    在接口测试中 很多时候需要依赖前一个请求的响应数据关联到后一个请求的请求数据中来 在postman的中有一个Pre request Script 板块 如示例接口为 https api weixin qq com cgi bin user
  • Java面向对象基础

    文章目录 面向对象 一 类和对象 1 类的介绍 2 类和对象的关系 3 类的组成 4 创建对象和使用对象的格式 二 对象内存图 1 单个对象内存图 2 两个对象内存图 3 两个引用指向相同内存图 三 成员变量和局部变量 四 this 关键字
  • 栈(Stack)——class Stack 和 class Stack T 实现

    对于Stack类的实现 跟之前链表实现也一样 只是封装成为面向对象的类了 PS 这里是线式存储的类和模板实现 链表式的实际上写法也是一样的 class Stack代码如下 mystack h include
  • 信奥一本通 贪心算法 回顾

    文章目录 写在前面 A 家庭作业 B 智力大冲浪 C 加工生产调度 D 喷水装置3 线段覆盖最少线段 E 活动安排 线段覆盖 覆盖最多段 F 种树 G 数列极差 H 数列分段 I 钓鱼 J 均分纸牌 K 糖果传递 写在前面 之前看到一篇非常
  • java中的双端队列deque使用以及部分原理

    转载自 https www cnblogs com denglh p 7911513 html package collections import java util Deque import java util LinkedList P
  • 对于金融机构而言,为什么选择私有化 IM 比企业微信、钉钉更好?

    一 金融机构数字化转型迈向规范有序 更成体系的新阶段 当前 新一轮信息技术革命浪潮拉开序幕 以人工智能 大数据 云计算等为代表的数字技术正在重构全球经济 不少企业也纷纷拥抱数字化浪潮 开展全方位的变革和升级 中国银保监会印发 关于银行业保险
  • char字符表

    图片来源于网上
  • 如何在 GitHub 上找到免费且实用的软件?

    GitHub 虽说是以程序员为主的社区 但是上面托管的项目类型却风格迥异 有认真科研型的 也有上班划水型的 有面向极客宅男的开发工具 也有给小白麻瓜使用的普通软件 本周写了几篇文章 大多都在介绍与技术相关的开发工具与技巧 今天稍微调整一下
  • 生成csv文件并下载

    在做项目中 我们做一个功能的时候 可能要把数据做导出或下载处理 下载成各种格式 下面提供了一种excel下载格式 csv 将得到的数据 经过处理生成csv文件 并激活下载到本地 代码如下
  • 云计算目前国内外发展现状是什么,云计算主要存在哪些问题?

    远在 云计算 的名次出现之前 我国相关科技人员便已对互联网的透明资源储备技术进行了多方面应用 而随着科技的不断进步 对于云计算的应用愈加频繁 政府部门对云计算的建设提供了经济基础与社会软环境的保障 并且在国家科研部门当中设立了专业的部门 直
  • Android 获取本地视频列表

    activity video list xml
  • 将二叉树转换成双向链表

    输入一棵二叉搜索树 将该二叉搜索树转换成一个排序的双向链表 要求不能创建任何新的节点 只能调整树中节点指针的指向 因为二叉搜索树每个节点都有两个指针 分别是指向其左子节点和右子节点 所以将该节点的左子节点变成双向链表中的左节点 将该节点的右
  • 【数据结构】详解栈的应用之表达式求值

    首先明白 前缀表达式 符号在前 如 3456 中缀表达式 符号在中间 如 3 4 5 6 后缀表达式 符号在最后 如34 5 6 后缀表达式不出现括号 中缀表达式转后缀表达式的方法 1 遇到数字 直接输出 添加到后缀表达式中 2 栈为空时
  • python 关联图谱_机器学习算法与Python实践 - 知识图谱

    机器学习 人工智能 知识图谱 可以为自己建立一个机器学习的知识图谱 并争取掌握每一个经典的机器学习理论和算法 简单地总结如下 1 回归算法 多元自适应回归样条 MultivariateAdaptive Regression Splines
  • gmtime与localtime的区别

    目录 gmtime函数 linux环境下 window环境下 localtime函数 gmtime函数 gmtime转换的时间是UTL时间 与北京时间相差了8个小时 如果你想要得到北京时间 不建议你将gmtime转换后的时间直接加上八个小时
  • 小游戏开发:使用 React 和 Redux Tool Kit 实现俄罗斯方块

    大家好 我是若川 我持续组织了近一年的源码共读活动 感兴趣的可以 点此扫码加我微信 lxchuan12 参与 每周大家一起学习200行左右的源码 共同进步 同时极力推荐订阅我写的 学习源码整体架构系列 包含20余篇源码文章 历史面试系列 另