-
2026年美加墨世界杯,足球滚大小技巧《₩₩₩典𝔧𝚜㊇㊇㍚ℂ𝒞》亚足联共有8.5个参赛席位。世预赛亚洲区从第一阶段开始,共22队参赛,晋级的11支球队与亚足联旗下FIFA排名前25的25支球队,参加第二阶段的36强赛。36强赛一共分为9个小组,每组4支球队,采取主客场双循环赛制,总计踢6轮比赛,最终只有小组前两名才能出线,晋级第三阶段18强赛。小组第三和第四名则直接出局,提前宣告无缘2026年世界杯决赛圈。中国男足已经进入了36强赛,与韩国、泰国、新加坡分在同一个小组,目标当然就是拿到小组前两名,顺利进入18强赛。首轮,11月16日晚20点30分,国足做客挑战泰国,可以说“首战即决战”,至少要带走1分。第二轮,11月21日晚20点,国足主场迎战韩国。韩国拥有孙兴慜、金玟哉、李刚仁等欧洲豪门球星,不出意外的话将获小组第一。所以国足若能在深圳拿到1分,就已完成目标;若能爆冷赢球,那更是巨大惊喜,出线不成问题。第三轮和第四轮,国足将在明年3月21日和24日连战新加坡,而且是先客后主。新加坡是本组最弱对手,所以国足的目标只有一个,就是全取6分,少拿2分都是巨大损失。如果前4轮战罢,国足能先赢泰国、再平韩国、双杀新加坡,3胜1平积10分,那么很有可能提前两轮出线!因为泰国要两战韩国,基本上会全败,4轮1胜3负只有3分——当然,这是最理想的情况。第五轮,国足将在6月6日主场迎战泰国。如果不胜,很有可能无法出线。因为第六轮,国足将在6月11日做客挑战韩国,对手恐已提前晋级,但大概率不会放水。
-
我这里用的是Centos7.9====================================================================下载steamcmdyum install -y wget libxcb glibc.i686 libcurl.i686 #下载相关依赖工具sudo useradd -m steam #这里要给steam单独创建一个用户,直接root做的话后面运行帕鲁服务器的时候会报错,提示不能在root用户下执行sudo -u steam -s #切换到steam用户cd /home/steam #进入主文件夹wget https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz #下载steamcmdtar -zxvf steamcmd_linux.tar.gz #解压steamcmd./steamcmd #打开steamcmd#如果运行steamcmd报错/lib/ld-linux.so.2: bad ELF interpreter: No such file or directory,那么就是上面的glibc.i686没有安装,那么你需要执行下面命令进行安装yum install glibc.i686 (glibc是GNU发布的libc库,即c运行库。glibc是linux系统中最底层的api,几乎其它任何运行库都会依赖于glibc。glibc除了封装linux操作系统所提供的系统服务外,它本身也提供了许多其它一些必要功能服务的实现。由于 glibc 囊括了几乎所有的 UNIX 通行的标准,可以想见其内容包罗万象。而就像其他的 UNIX 系统一样,其内含的档案群分散于系统的树状目录结构中,像一个支架一般撑起整个操作系统。)=======================================================================================下载幻兽帕鲁服务器login anonymous #在刚刚运行的steamcmd里面输入该命令,然后回车app_update 2394010 validate #下载/更新幻兽帕鲁服务器,文件大概2.5G大小exit #下载好后退出,steamcmdcd /home/steam/Steam/steamapps/common/PalServer #切换到刚刚下载的幻兽帕鲁目录./PalServer.sh #运行幻兽帕鲁服务器延迟还不错===========================================================================修改世界参数(具体参数含义,我的其他帖子有可以参考下)cd /home/steam/Steam/steamapps/common/PalServer #在该路径下vi /DefaultPalWorldSettings.ini #这是默认配置模板,复制里面的全部内容,推荐用ssh工具连接到服务器,一般鼠标选中内容,鼠标右键有复制的选项。按住键盘shift+: 输入q 然后回车 #退出vi编辑器cd /home/steam/Steam/steamapps/common/PalServer/Pal/Saved/Config/LinuxServer #拿着刚才复制的内容来到该路径下vi PalWorldSettings.ini #编辑文件键盘按下 i 进入编辑模式,然后把刚刚复制的内容,粘贴进来键盘按下ESC退出编辑模式,然后键盘同时按shift+: 输入wq! 保存并退出cd /home/steam/Steam/steamapps/common/PalServer./PalServer.sh #重启帕鲁服务器,就生效了
-
学习目标:目录DEvOps的五要素DEvOps的生命周期
qingqingjiayuan6
发表于2022-08-08 17:12:25
2022-08-08 17:12:25
最后回复
吃完就睡,快乐加倍
2022-08-09 11:13:53
242 11 -
题目给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。示例 1:输入:nums = [2,3,1,1,4]输出:true解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。示例 2:输入:nums = [3,2,1,0,4]输出:false解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。方法一:正向遍历,计算每个位置开始能到达的最高处class Solution {public: bool canJump(vector& nums) { int reach=0;//记录当前能到达的最远处 //i遍历数组,i要始终处于能到达的位置,如果reach已经大于nums边界已经可以退出循环 for(int i=0;i<=reach&&reach<nums.size();i++) { reach=max(reach,i+nums[i]);//当前位置的最远边界为当前位置能加最大步数和最远边界,二者取较大 } return reach+1>=nums.size();//边界是数组下标,从0开始,所以要加1 }};时间复杂度O(n)空间复杂度O(1)思路对于数组中的任意一个位置y,只要当前位置x加上x处可走的最大步数,二者之和大于y,则可以到达y一次遍历数组,维护当前的最远边界最后比较最远边界是否可以到达数组大小,如果是则返回true法二:逆向遍历数组,从末尾看能不能下降到0class Solution {public: bool canJump(vector& nums) { int left=nums.size()-1;//左边界 //从后往前遍历数组,如果i所指的位置加上当前数组值大于lef,则当前的左边界为i for(int i=nums.size()-2;i>=0;i--) { if(nums[i]+i>=left) left=i; } //最后判断左边界能否小于0,即从终点能否回到起点 return left<=0; }};法三:动态规划class Solution {public: bool canJump(vector& nums) { vector f(nums.size(),0); f[0]=nums[0]-1;//初始位置可以到达的最远距离就是0加上当前可以走的步数 //依次确定经过每一个位置的最远距离 for(int i=1;i<nums.size();i++) { f[i]=max(f[i-1],i+nums[i-1]); if(f[i]==i) return false;//表示不能往后走了,返回false } return true; }};时间复杂度O(n)空间复杂度O(n)思路f[i]表示当前位置可以到达的最远位置状态转移方程:f[i]=max(f[i-1],i+nums[i-1])当前位置可以到达的最远位置有两种情况,如果不到达当前位置则是前一个位置可以到达的最远位置即f[i-1],如果从当前位置出发则是i+nums[i],二者取最大值每次比较当前位置可以到达的最远位置是否与当前位置相同,如果相同则表示无法继续向后走,返回false如果for循环正常退出则表示可以走到末尾,返回true————————————————版权声明:本文为CSDN博主「Mirevas」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/weixin_45781228/article/details/126202831
-
目录目标效果设计开始步骤一步骤二步骤三步骤四步骤五今天我们来动手实现一款2048小游戏。这款游戏的精髓就玩家能够在于通过滑动屏幕合并相同数字,直到不能再合并为止。玩法可以说是非常的简单,但挑战性也是十足的。话不多说,让我们从0开始实现!目标效果大致要实现的效果如下:设计开始首先简单分析一下游戏的逻辑:输入移动方向,游戏内所有方块都朝指定方向移动同方向移动的方块,数字相同则合并,然后生成一个合并的方块合并后生成新的方块,无法生成新方块时游戏结束用一系列的颜色来区分不同分数的方块(可有可无,纯粹是为了美观)ok,游戏内再逻辑已经很清晰了。现在开始实现:步骤一新建一个文件夹用来放需要的游戏素材步骤二新建一个python程序,可以命名为2048,放在素材目录的同级文件夹下步骤三导入需要的依赖库:123import pygame as pyimport sys, random, time, redis, os,mathimport numpy as np依赖库中的redis是一个额外的数据库,用来存取游戏历史数据,需要的可以考虑安装,不需要的用excel表代替也可以。首先需要思考的是,游戏内的方块的移动本质上是坐标的变换,并且方块的坐标是固定的,也就是说,每次输入一个方向就按照一个移动函数将所有方块的坐标进行对应的转换。那么,如此以来,就需要建立一个坐标系用以标记方块的坐标。因为是4x4的游戏,那么就按照(1,1),(1,2),(1,3),...,(4,4)建立游戏坐标,然而相比直接移动坐标还是比较麻烦,一个简单的想法是,每个方块给一个唯一的标记,如我们需要实现4x4的游戏,就需要16个记号。而每一个标记就对应了唯一且固定的坐标。给出如下代码:123456789# 预加载移动逻辑def pre_move(): numberPos = {} for num in range(1, 17): row1, row2 = divmod(num, 4) row = row1 + np.sign(row2) column = [row2 if row2 != 0 else 4][0] numberPos['{}'.format([row, column])] = num return numberPos这里的numberPos实际上就是{‘{1,1}’:1,’{1,2}‘:2......}。当然如果想设计5x5或者6x6的只需要把循环里面的17和4改成25和5或36和6就行。ok,有了坐标接下来的问题好解决了。步骤四在新建的素材文件夹内放入一些图片方块(正方形)用来表示每个不同分数的方块。如下图所示:当然,不使用图片加载游戏也是可以的,如使用py.draw.rect()也能绘制图像,不过每次加载都绘制图像会占用游戏大量运算内存,并且使用图片可以自定义自己的游戏风格,修改上也非常便利。设置完成之后,定义一个游戏的初始化模块:12345678910111213141516171819# 主程序def game_start(): global screen, rate py.init() clock = py.time.Clock() screen_x = 500 # 请调到合适的大小 screen_y = math.ceil(screen_x * rate / rate2) screen = py.display.set_mode((screen_x, screen_y), depth=32) py.display.set_caption("终极2048") BackGround = [251, 248, 239] # 灰色 Icon = py.image.load('./素材/icon.png').convert_alpha() py.display.set_icon(Icon) screen.fill(color=BackGround) # 主界面下设计 width = math.floor(screen_x * rate) bgSecond = py.image.load('./素材/BG_02.png').convert_alpha() bgSecond = py.transform.smoothscale(bgSecond, (width, width)) bgSecondRect = bgSecond.get_rect() bgSecondRect.topleft = math.floor(screen_x * (1 - rate) / 2), math.floor(screen_y * (1 - rate2))游戏界面的大小请调节到合适的尺寸。接下来加载分数图片,以便游戏循环时随时可以调用。123456789101112131415161718192021222324252627282930313233343536373839404142# 预加载分数图def pre_load_image(background): imageList = {} imagePath = './素材/分数/' image_filenames = [i for i in os.listdir(imagePath)] width = math.floor(background.width * (1 - internalWidth) / 4) for name in image_filenames: image = py.transform.smoothscale(py.image.load(imagePath + name).convert_alpha(), (width, width)) imageList[name.replace('.png', '')] = image return imageList# 加载分数图像def draw_image(score_list, image_list, pos_list): for pos_num in score_list: score = score_list[pos_num] scoreSurf = BasicFont01.render('{}'.format(score), True, (0, 0, 0)) scoreRect = scoreSurf.get_rect() if score <= 4096: image = image_list['{}'.format(score)] else: image = image_list['4096'] imageRect = image.get_rect() imageRect.topleft = pos_list['{}'.format(pos_num)] scoreRect.center = imageRect.center screen.blit(image, imageRect) if score > 0: screen.blit(scoreSurf, scoreRect)# 图像位置列表,表示为(x,y)# 用于确定加载的分数图像的显示点位def image_pos_list(background): pre_x = background.topleft[0] pre_y = background.topleft[-1] internalLong = math.ceil(internalWidth / 5 * background.width) imageLong = math.floor((1 - internalWidth) / 4 * background.width) posList = dict(zip(list(range(1, 17)), [''] * 16)) for num in range(1, 17): row1, row2 = divmod(num, 4) row = row1 + np.sign(row2) column = [row2 if row2 != 0 else 4][0] image_x = pre_x + internalLong * column + imageLong * (column - 1) image_y = pre_y + internalLong * row + imageLong * (row - 1) posList['{}'.format(num)] = (image_x, image_y) return posList这里用了三个函数来加载游戏图片,分表表示:提取图片名保存到列表中,绘制游戏中的2,4,8等等数字在分数图片上。最后一个函数用于确定每个坐标在游戏界面的显示位置,并将其一一绑定。加载完成图像之后,就需要完成关键的移动逻辑,先上代码:12345678910111213141516171819202122232425262728293031323334353637383940414243# 移动逻辑def number_move(number_pos, move_input, score_list): values = list(number_pos.values()) keys = list(number_pos.keys()) numberPosReverse = dict(zip(values, keys)) newScoreList = score_list.copy() oldScoreList = {} while newScoreList != oldScoreList: oldScoreList = newScoreList.copy() for num in range(1, 17): pos = eval(numberPosReverse[num]) x, y = pos[0] + move_input[0], pos[1] + move_input[1] pos[0] = [x if 1 <= x <= 4 else pos[0]][0] pos[1] = [y if 1 <= y <= 4 else pos[1]][0] number = number_pos['{}'.format(pos)] oldNumberScore = newScoreList[num] nextNumberScore = newScoreList[number] syn = list(map(lambda x, y: abs(x) * abs(y), move_input, pos)) # 0值移动 if nextNumberScore == 0: newScoreList[number] = oldNumberScore newScoreList[num] = 0 # 无法移动 elif num == number: pass # 合并移动 elif oldNumberScore == nextNumberScore and num != number: newScoreList[number] = 2 * oldNumberScore newScoreList[num] = 0 # 边界移动 elif oldNumberScore != nextNumberScore and 1 in syn or 4 not in syn: pass # 非边界移动 elif oldNumberScore != nextNumberScore and 1 not in syn and 4 not in syn: x, y = pos[0] + move_input[0], pos[1] + move_input[1] next2NumberScore = newScoreList[number_pos['{}'.format([x, y])]] if next2NumberScore != nextNumberScore: pass elif next2NumberScore == nextNumberScore: newScoreList[number_pos['{}'.format([x, y])]] = 2 * next2NumberScore newScoreList[number] = oldNumberScore newScoreList[num] = 0 return newScoreList首先导入预先确定好的坐标,移动变量。根据前面分析的游戏逻辑,每次输入移动向量后游戏内的所有方块都需要移动,相同分数的方块需要一次性合并到一起,并且不能留空。详细分析一下就是:输入一个移动向量(x,y),如(+1,0)表示方块向右移动一格。对所有的原坐标进行计算并保留为移动后坐标,提取前后两次坐标对应的分数从1号标记开始循环判断:0值移动:如果移动后的分数为0,用旧坐标分数替代新坐标的分数,并删除旧坐标的分数无法移动:移动后的坐标与移动前的坐标相同,那么不做改变合并移动:新旧坐标对应的分数相同,那么新坐标分数x2,旧坐标分数删除边界移动:方块已经处于移动的边界,无法移动,不做修改非边界移动:新旧坐标对应的分数不同,且新坐标的下一个坐标对应的分数也不同,不做修改;新旧坐标对应的分数不同,且新坐标的下一个坐标对应的分数相同,修改循环整个逻辑,直到所有坐标对应的分数不再发生改变通过上述分析,移动逻辑函数实现了输入一个方向游戏内的分数动态发生变化。最后我们还需要一个游戏结束的函数:12345678910111213141516171819# 游戏结束def game_over(score,bg): ip = '127.0.0.1' password = None r = redis.Redis(host=ip, password=password, port=6379, db=2, decode_responses=True) r.hset('2048','{}'.format(time.localtime()),score) py.draw.rect(screen,bg,[0,0,screen.get_width(),screen.get_height()],0) BasicFont02 = py.font.SysFont('/素材/simkai.ttf', 40) overSurf = BasicFont01.render('Game Over', True, (0, 0, 0)) overRect = overSurf.get_rect() overRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() / 2)) scoreSurf = BasicFont02.render('最终得分:', True, (0, 0, 0)) scoreRect = scoreSurf.get_rect() scoreRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() * 0.6)) numberSurf = BasicFont02.render('{}'.format(score), True, (0, 0, 0)) numberRect = numberSurf.get_rect() numberRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() * 0.7)) time.sleep(3) sys.exit()一个键盘控制代码,实现键盘控制游戏:123456789101112# 键盘控制函数def keyboard_ctrl(event): move_output = [0, 0] if event.key == py.K_UP: move_output = [-1, 0] elif event.key == py.K_DOWN: move_output = [1, 0] elif event.key == py.K_RIGHT: move_output = [0, 1] elif event.key == py.K_LEFT: move_output = [0, -1] return move_output一个新方块生成器,实现每次合并之后能在空白方块处随机生成2或4中的一个新分数,生成概率按照当前游戏中的2和4的数量为基础。123456789101112# 随机得分生成def random_score(score_list): values = list(score_list.values()) pro = [2] * (2 + values.count(2)) + [4] * (1 + values.count(4)) # 以当前分数图中2或4出现的频率为概率 blank = [[i if score_list[i] == 0 else 0][0] for i in range(1, 17)] blank = list(set(blank)) blank.remove(0) if not blank: return 'GameOver' # 游戏结束 else: score_list[random.choice(blank)] = random.choice(pro) return score_list一个得分统计器,每次游戏运行是统计当前得分和历史最高得分:123456789101112131415161718192021222324252627# 统计并记录当前得分def record_score(score_list, background): totalScore = 0 values = list(score_list.values()) for i in values: totalScore += i scoreSurf = BasicFont01.render('得分:{}'.format(totalScore), True, (0, 0, 0)) scoreRect = scoreSurf.get_rect() scoreRect.topleft = (math.floor(0.1 * screen.get_width()), math.floor(0.05 * screen.get_height())) scoreRect.width = math.floor((rate - 0.15) / 2 * screen.get_width()) scoreRect.height = math.floor((1 - rate2) / 3 * 2 * screen.get_height()) py.draw.rect(screen, background, [scoreRect.topleft[0], scoreRect.topleft[1], scoreRect.width, scoreRect.height], 0) screen.blit(scoreSurf, scoreRect) return totalScore# 绘制历史最高得分def draw_best(background): ip = '127.0.0.1' password = None r = redis.Redis(host=ip, password=password, port=6379, db=2, decode_responses=True) scores=[eval(i) for i in list(r.hgetall('2048').values())] best_scores=max(scores) scoreSurf=BasicFont01.render('最高得分:{}'.format(best_scores),True,(0,0,0)) scoreRect=scoreSurf.get_rect() scoreRect.width = math.floor((rate - 0.15) / 2 * screen.get_width()) scoreRect.height = math.floor((1 - rate2) / 3 * 2 * screen.get_height()) scoreRect.topright = (math.floor(0.9 * screen.get_width()), math.floor(0.05 * screen.get_height())) py.draw.rect(screen, background, [scoreRect.topleft[0], scoreRect.topleft[1], scoreRect.width, scoreRect.height], 0) screen.blit(scoreSurf, scoreRect)最后补充完整的游戏启动器:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455# 主程序def game_start(): global screen, rate py.init() clock = py.time.Clock() screen_x = 500 # 请调到合适的大小 screen_y = math.ceil(screen_x * rate / rate2) screen = py.display.set_mode((screen_x, screen_y), depth=32) py.display.set_caption("终极2048") BackGround = [251, 248, 239] # 灰色 Icon = py.image.load('./素材/icon.png').convert_alpha() py.display.set_icon(Icon) screen.fill(color=BackGround) # 主界面下设计 width = math.floor(screen_x * rate) bgSecond = py.image.load('./素材/BG_02.png').convert_alpha() bgSecond = py.transform.smoothscale(bgSecond, (width, width)) bgSecondRect = bgSecond.get_rect() bgSecondRect.topleft = math.floor(screen_x * (1 - rate) / 2), math.floor(screen_y * (1 - rate2)) # 主界面上部分设计 # 预加载数据 draw_best(BackGround) posList = image_pos_list(bgSecondRect) imageList = pre_load_image(bgSecondRect) scoreList = dict(zip(list(range(1, 17)), [0] * 15 + [2])) # 分数表 numberPos = pre_move() scoreList = random_score(scoreList) totalScore=0 # 主循环 while True: screen.blit(bgSecond, bgSecondRect) # 刷新屏幕 if scoreList == 'GameOver': game_over(totalScore,BackGround) draw_image(scoreList, imageList, posList) # 绘制得分 totalScore = record_score(scoreList, BackGround) key = py.key.get_pressed() if key[py.K_ESCAPE]: exit() for event in py.event.get(): if event.type == py.QUIT: sys.exit() elif event.type == py.KEYDOWN: move_input = keyboard_ctrl(event) # 按下按键 scoreList = number_move(numberPos, move_input, scoreList) # 移动数字 scoreList = random_score(scoreList) # 在按下按键后生成新的数字 py.display.update() clock.tick(FPS)if __name__ == '__main__': py.font.init() BasicFont01 = py.font.Font('./素材/simkai.ttf', 30) screen = py.display.set_mode((500, 500)) rate = 0.95 # 游戏主界面下的宽度占整个游戏界面宽度的比例 rate2 = 0.7 # 游戏主界面下的高度占整个游戏界面高度的比例 internalWidth = 0.1 # 间隙比例 FPS = 50 # 游戏帧率 game_start()步骤五启动游戏运行之前别忘了启动redis服务器。运行效果图:(游戏界面设计的不够好。。。。,本来打算再加入一些小道具比如说:撤销,全屏合并等功能)
-
目录目标效果设计开始步骤一步骤二步骤三步骤四步骤五今天我们来动手实现一款2048小游戏。这款游戏的精髓就玩家能够在于通过滑动屏幕合并相同数字,直到不能再合并为止。玩法可以说是非常的简单,但挑战性也是十足的。话不多说,让我们从0开始实现!目标效果大致要实现的效果如下:设计开始首先简单分析一下游戏的逻辑:输入移动方向,游戏内所有方块都朝指定方向移动同方向移动的方块,数字相同则合并,然后生成一个合并的方块合并后生成新的方块,无法生成新方块时游戏结束用一系列的颜色来区分不同分数的方块(可有可无,纯粹是为了美观)ok,游戏内再逻辑已经很清晰了。现在开始实现:步骤一新建一个文件夹用来放需要的游戏素材步骤二新建一个python程序,可以命名为2048,放在素材目录的同级文件夹下步骤三导入需要的依赖库:123import pygame as pyimport sys, random, time, redis, os,mathimport numpy as np依赖库中的redis是一个额外的数据库,用来存取游戏历史数据,需要的可以考虑安装,不需要的用excel表代替也可以。首先需要思考的是,游戏内的方块的移动本质上是坐标的变换,并且方块的坐标是固定的,也就是说,每次输入一个方向就按照一个移动函数将所有方块的坐标进行对应的转换。那么,如此以来,就需要建立一个坐标系用以标记方块的坐标。因为是4x4的游戏,那么就按照(1,1),(1,2),(1,3),...,(4,4)建立游戏坐标,然而相比直接移动坐标还是比较麻烦,一个简单的想法是,每个方块给一个唯一的标记,如我们需要实现4x4的游戏,就需要16个记号。而每一个标记就对应了唯一且固定的坐标。给出如下代码:123456789# 预加载移动逻辑def pre_move(): numberPos = {} for num in range(1, 17): row1, row2 = divmod(num, 4) row = row1 + np.sign(row2) column = [row2 if row2 != 0 else 4][0] numberPos['{}'.format([row, column])] = num return numberPos这里的numberPos实际上就是{‘{1,1}’:1,’{1,2}‘:2......}。当然如果想设计5x5或者6x6的只需要把循环里面的17和4改成25和5或36和6就行。ok,有了坐标接下来的问题好解决了。步骤四在新建的素材文件夹内放入一些图片方块(正方形)用来表示每个不同分数的方块。如下图所示:当然,不使用图片加载游戏也是可以的,如使用py.draw.rect()也能绘制图像,不过每次加载都绘制图像会占用游戏大量运算内存,并且使用图片可以自定义自己的游戏风格,修改上也非常便利。设置完成之后,定义一个游戏的初始化模块:12345678910111213141516171819# 主程序def game_start(): global screen, rate py.init() clock = py.time.Clock() screen_x = 500 # 请调到合适的大小 screen_y = math.ceil(screen_x * rate / rate2) screen = py.display.set_mode((screen_x, screen_y), depth=32) py.display.set_caption("终极2048") BackGround = [251, 248, 239] # 灰色 Icon = py.image.load('./素材/icon.png').convert_alpha() py.display.set_icon(Icon) screen.fill(color=BackGround) # 主界面下设计 width = math.floor(screen_x * rate) bgSecond = py.image.load('./素材/BG_02.png').convert_alpha() bgSecond = py.transform.smoothscale(bgSecond, (width, width)) bgSecondRect = bgSecond.get_rect() bgSecondRect.topleft = math.floor(screen_x * (1 - rate) / 2), math.floor(screen_y * (1 - rate2))游戏界面的大小请调节到合适的尺寸。接下来加载分数图片,以便游戏循环时随时可以调用。123456789101112131415161718192021222324252627282930313233343536373839404142# 预加载分数图def pre_load_image(background): imageList = {} imagePath = './素材/分数/' image_filenames = [i for i in os.listdir(imagePath)] width = math.floor(background.width * (1 - internalWidth) / 4) for name in image_filenames: image = py.transform.smoothscale(py.image.load(imagePath + name).convert_alpha(), (width, width)) imageList[name.replace('.png', '')] = image return imageList# 加载分数图像def draw_image(score_list, image_list, pos_list): for pos_num in score_list: score = score_list[pos_num] scoreSurf = BasicFont01.render('{}'.format(score), True, (0, 0, 0)) scoreRect = scoreSurf.get_rect() if score <= 4096: image = image_list['{}'.format(score)] else: image = image_list['4096'] imageRect = image.get_rect() imageRect.topleft = pos_list['{}'.format(pos_num)] scoreRect.center = imageRect.center screen.blit(image, imageRect) if score > 0: screen.blit(scoreSurf, scoreRect)# 图像位置列表,表示为(x,y)# 用于确定加载的分数图像的显示点位def image_pos_list(background): pre_x = background.topleft[0] pre_y = background.topleft[-1] internalLong = math.ceil(internalWidth / 5 * background.width) imageLong = math.floor((1 - internalWidth) / 4 * background.width) posList = dict(zip(list(range(1, 17)), [''] * 16)) for num in range(1, 17): row1, row2 = divmod(num, 4) row = row1 + np.sign(row2) column = [row2 if row2 != 0 else 4][0] image_x = pre_x + internalLong * column + imageLong * (column - 1) image_y = pre_y + internalLong * row + imageLong * (row - 1) posList['{}'.format(num)] = (image_x, image_y) return posList这里用了三个函数来加载游戏图片,分表表示:提取图片名保存到列表中,绘制游戏中的2,4,8等等数字在分数图片上。最后一个函数用于确定每个坐标在游戏界面的显示位置,并将其一一绑定。加载完成图像之后,就需要完成关键的移动逻辑,先上代码:12345678910111213141516171819202122232425262728293031323334353637383940414243# 移动逻辑def number_move(number_pos, move_input, score_list): values = list(number_pos.values()) keys = list(number_pos.keys()) numberPosReverse = dict(zip(values, keys)) newScoreList = score_list.copy() oldScoreList = {} while newScoreList != oldScoreList: oldScoreList = newScoreList.copy() for num in range(1, 17): pos = eval(numberPosReverse[num]) x, y = pos[0] + move_input[0], pos[1] + move_input[1] pos[0] = [x if 1 <= x <= 4 else pos[0]][0] pos[1] = [y if 1 <= y <= 4 else pos[1]][0] number = number_pos['{}'.format(pos)] oldNumberScore = newScoreList[num] nextNumberScore = newScoreList[number] syn = list(map(lambda x, y: abs(x) * abs(y), move_input, pos)) # 0值移动 if nextNumberScore == 0: newScoreList[number] = oldNumberScore newScoreList[num] = 0 # 无法移动 elif num == number: pass # 合并移动 elif oldNumberScore == nextNumberScore and num != number: newScoreList[number] = 2 * oldNumberScore newScoreList[num] = 0 # 边界移动 elif oldNumberScore != nextNumberScore and 1 in syn or 4 not in syn: pass # 非边界移动 elif oldNumberScore != nextNumberScore and 1 not in syn and 4 not in syn: x, y = pos[0] + move_input[0], pos[1] + move_input[1] next2NumberScore = newScoreList[number_pos['{}'.format([x, y])]] if next2NumberScore != nextNumberScore: pass elif next2NumberScore == nextNumberScore: newScoreList[number_pos['{}'.format([x, y])]] = 2 * next2NumberScore newScoreList[number] = oldNumberScore newScoreList[num] = 0 return newScoreList首先导入预先确定好的坐标,移动变量。根据前面分析的游戏逻辑,每次输入移动向量后游戏内的所有方块都需要移动,相同分数的方块需要一次性合并到一起,并且不能留空。详细分析一下就是:输入一个移动向量(x,y),如(+1,0)表示方块向右移动一格。对所有的原坐标进行计算并保留为移动后坐标,提取前后两次坐标对应的分数从1号标记开始循环判断:0值移动:如果移动后的分数为0,用旧坐标分数替代新坐标的分数,并删除旧坐标的分数无法移动:移动后的坐标与移动前的坐标相同,那么不做改变合并移动:新旧坐标对应的分数相同,那么新坐标分数x2,旧坐标分数删除边界移动:方块已经处于移动的边界,无法移动,不做修改非边界移动:新旧坐标对应的分数不同,且新坐标的下一个坐标对应的分数也不同,不做修改;新旧坐标对应的分数不同,且新坐标的下一个坐标对应的分数相同,修改循环整个逻辑,直到所有坐标对应的分数不再发生改变通过上述分析,移动逻辑函数实现了输入一个方向游戏内的分数动态发生变化。最后我们还需要一个游戏结束的函数:12345678910111213141516171819# 游戏结束def game_over(score,bg): ip = '127.0.0.1' password = None r = redis.Redis(host=ip, password=password, port=6379, db=2, decode_responses=True) r.hset('2048','{}'.format(time.localtime()),score) py.draw.rect(screen,bg,[0,0,screen.get_width(),screen.get_height()],0) BasicFont02 = py.font.SysFont('/素材/simkai.ttf', 40) overSurf = BasicFont01.render('Game Over', True, (0, 0, 0)) overRect = overSurf.get_rect() overRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() / 2)) scoreSurf = BasicFont02.render('最终得分:', True, (0, 0, 0)) scoreRect = scoreSurf.get_rect() scoreRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() * 0.6)) numberSurf = BasicFont02.render('{}'.format(score), True, (0, 0, 0)) numberRect = numberSurf.get_rect() numberRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() * 0.7)) time.sleep(3) sys.exit()一个键盘控制代码,实现键盘控制游戏:123456789101112# 键盘控制函数def keyboard_ctrl(event): move_output = [0, 0] if event.key == py.K_UP: move_output = [-1, 0] elif event.key == py.K_DOWN: move_output = [1, 0] elif event.key == py.K_RIGHT: move_output = [0, 1] elif event.key == py.K_LEFT: move_output = [0, -1] return move_output一个新方块生成器,实现每次合并之后能在空白方块处随机生成2或4中的一个新分数,生成概率按照当前游戏中的2和4的数量为基础。123456789101112# 随机得分生成def random_score(score_list): values = list(score_list.values()) pro = [2] * (2 + values.count(2)) + [4] * (1 + values.count(4)) # 以当前分数图中2或4出现的频率为概率 blank = [[i if score_list[i] == 0 else 0][0] for i in range(1, 17)] blank = list(set(blank)) blank.remove(0) if not blank: return 'GameOver' # 游戏结束 else: score_list[random.choice(blank)] = random.choice(pro) return score_list一个得分统计器,每次游戏运行是统计当前得分和历史最高得分:123456789101112131415161718192021222324252627# 统计并记录当前得分def record_score(score_list, background): totalScore = 0 values = list(score_list.values()) for i in values: totalScore += i scoreSurf = BasicFont01.render('得分:{}'.format(totalScore), True, (0, 0, 0)) scoreRect = scoreSurf.get_rect() scoreRect.topleft = (math.floor(0.1 * screen.get_width()), math.floor(0.05 * screen.get_height())) scoreRect.width = math.floor((rate - 0.15) / 2 * screen.get_width()) scoreRect.height = math.floor((1 - rate2) / 3 * 2 * screen.get_height()) py.draw.rect(screen, background, [scoreRect.topleft[0], scoreRect.topleft[1], scoreRect.width, scoreRect.height], 0) screen.blit(scoreSurf, scoreRect) return totalScore# 绘制历史最高得分def draw_best(background): ip = '127.0.0.1' password = None r = redis.Redis(host=ip, password=password, port=6379, db=2, decode_responses=True) scores=[eval(i) for i in list(r.hgetall('2048').values())] best_scores=max(scores) scoreSurf=BasicFont01.render('最高得分:{}'.format(best_scores),True,(0,0,0)) scoreRect=scoreSurf.get_rect() scoreRect.width = math.floor((rate - 0.15) / 2 * screen.get_width()) scoreRect.height = math.floor((1 - rate2) / 3 * 2 * screen.get_height()) scoreRect.topright = (math.floor(0.9 * screen.get_width()), math.floor(0.05 * screen.get_height())) py.draw.rect(screen, background, [scoreRect.topleft[0], scoreRect.topleft[1], scoreRect.width, scoreRect.height], 0) screen.blit(scoreSurf, scoreRect)最后补充完整的游戏启动器:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455# 主程序def game_start(): global screen, rate py.init() clock = py.time.Clock() screen_x = 500 # 请调到合适的大小 screen_y = math.ceil(screen_x * rate / rate2) screen = py.display.set_mode((screen_x, screen_y), depth=32) py.display.set_caption("终极2048") BackGround = [251, 248, 239] # 灰色 Icon = py.image.load('./素材/icon.png').convert_alpha() py.display.set_icon(Icon) screen.fill(color=BackGround) # 主界面下设计 width = math.floor(screen_x * rate) bgSecond = py.image.load('./素材/BG_02.png').convert_alpha() bgSecond = py.transform.smoothscale(bgSecond, (width, width)) bgSecondRect = bgSecond.get_rect() bgSecondRect.topleft = math.floor(screen_x * (1 - rate) / 2), math.floor(screen_y * (1 - rate2)) # 主界面上部分设计 # 预加载数据 draw_best(BackGround) posList = image_pos_list(bgSecondRect) imageList = pre_load_image(bgSecondRect) scoreList = dict(zip(list(range(1, 17)), [0] * 15 + [2])) # 分数表 numberPos = pre_move() scoreList = random_score(scoreList) totalScore=0 # 主循环 while True: screen.blit(bgSecond, bgSecondRect) # 刷新屏幕 if scoreList == 'GameOver': game_over(totalScore,BackGround) draw_image(scoreList, imageList, posList) # 绘制得分 totalScore = record_score(scoreList, BackGround) key = py.key.get_pressed() if key[py.K_ESCAPE]: exit() for event in py.event.get(): if event.type == py.QUIT: sys.exit() elif event.type == py.KEYDOWN: move_input = keyboard_ctrl(event) # 按下按键 scoreList = number_move(numberPos, move_input, scoreList) # 移动数字 scoreList = random_score(scoreList) # 在按下按键后生成新的数字 py.display.update() clock.tick(FPS)if __name__ == '__main__': py.font.init() BasicFont01 = py.font.Font('./素材/simkai.ttf', 30) screen = py.display.set_mode((500, 500)) rate = 0.95 # 游戏主界面下的宽度占整个游戏界面宽度的比例 rate2 = 0.7 # 游戏主界面下的高度占整个游戏界面高度的比例 internalWidth = 0.1 # 间隙比例 FPS = 50 # 游戏帧率 game_start()步骤五启动游戏运行之前别忘了启动redis服务器。运行效果图:(游戏界面设计的不够好。。。。,本来打算再加入一些小道具比如说:撤销,全屏合并等功能)
-
一个成功的设计应该是以用户为出发点,始终在考虑“用户需要什么”,最不可取的做法就是以个人喜好来决定设计方向,业内失败的例子比比皆是。 游戏策划并不是典型的游戏用户,我们不是玩家,我们制作的游戏是给广泛的玩家玩的,而不是自娱自乐用的。一味从自我考虑,做自己喜欢的游戏,最后会发现做出来的游戏连你自己都不喜欢玩。 以用户为中心比都什么都重要。
-
人工智能规划决策系统的发展,一度是以棋类游戏为载体的。最早在18世纪的时候,就出现一台能下棋的机器,击败了当时几乎所有的人类棋手,包括拿破仑和富兰克林等。不过最终被发现**机器里藏着一个人类高手**,通过复杂的机器结构以混淆观众的视线,只是一场骗局而已。 真正基于人工智能的规划决策系统出现在电子计算机诞生之后,1962年时,Arthur Samuel制作的**西洋跳棋**程序Checkers经过屡次改进后,终于战胜了州冠军。当时的程序虽然还算不上智能,但已经具备了初步的自我学习能力,这场胜利在当时引起了巨大的轰动,毕竟是机器首次在智力的角逐中战胜人类。 这也让人们发出了乐观的预言:“机器将在十年内战胜人类象棋冠军”。但人工智能所面临的困难比人们想象得要大很多,跳棋程序在此之后也败给了国家冠军,未能更上一层楼。而与跳棋相比,国际象棋要复杂得多,在当时的计算能力下,机器若想通过暴力计算战胜人类象棋棋手,每步棋的平均计算时长是以年为单位的。人们也意识到,只有尽可能减少计算复杂度,才可能与人类一决高下。 于是,“剪枝法”被应用到了估值函数中,通过剔除掉低可能性的走法,优化最终的估值函数计算。在“剪枝法”的作用下,西北大学开发的象棋程序Chess4.5在1976年首次击败了顶尖人类棋手。进人80年代,随着算法上的不断优化,机器象棋程序在关键胜负手上的判断能力和计算速度上大幅提升,已经能够击败几乎所有的顶尖人类棋手。到了90年代,硬件性能、算法能力等都得到了大幅提升,在1997年那场著名的人机大战中,IBM研发的深蓝(Deep Blue)战胜国际象棋大师卡斯帕罗夫,人们意识到在象游戏中人类已经很难战胜机器了。 到了2016年,硬件层面出现了基于GPU、TPU的并行计算,算法层面出现了蒙特卡洛决策树与深度神经网络的结合。4:1战胜李世石;在野狐围棋对战顶尖棋手60连胜;3:0战胜世界排名第一的围棋选手柯洁,随着棋类游戏最后的堡垒--围棋也被AlphaGo所攻克,人类在完美信息博弈的游戏中已彻底输给机器,只能在**不完美信息的德州扑克和麻将中**苟延残喘。 人们从棋类游戏中积累的知识和经验,也被应用在更广泛的需要决策规划的领域,包括机器人控制、无人车等等。棋类游戏完成了它的历史使命,带领人工智能到达了一个新的历史起点。
-
作者 | 宋林飞 责编 | 何苗受访嘉宾 | 卫剑钒出品 | CSDN(ID:CSDNnews)关注开源的人一定对《大教堂与集市》非常熟悉,随着这本书的中文版在国内发行,译者卫剑钒逐渐被更多的人熟知,而今,开源圈的朋友们亲切地称他卫Sir。书中,原作Eric S. Raymond用许多抽象概念为大家讲述了开源运动的理论与实际应用。这些“晦涩难懂”的英文也给卫剑钒的翻译工作带来诸多挑战,但他一直信奉的一句格言“麻烦的事中藏着珍宝”。历经无数个周末斟酌字句“较真”地翻译,他终于将《大教堂与集市》中文版完整、准确地展现给读者,使许多中国热爱开源的人跨越语言隔阂,领略到这本被称为开源运动“圣经”的风采。作为网络安全专家的卫剑钒因为工作性质并没有机会过多参与到开源项目中,但他化身为“卫Sir”在个人的博客和公众号上为大家用“人话”来解读诸多开源中抽象、“不明觉厉”的元素,例如开源理念,开源许可证,热点开源事件等等… 宛若一位世外高人。许多读者在看完他的文章后对开源有了更接地气的了解。古人云:“当局者迷,旁观者清”, 本期《开源访谈录》邀请到《大教堂与集市》译者、信息安全专家卫剑钒来分享他从一位“开源观察者”的角度对开源协议、开源安全性以及开源发展的见解。如何破解开源程序员的普遍困境:用开源获得收益与影响力?“麻烦的事中藏着珍宝”CSDN:你曾将开源著作《大教堂与集市》翻译成中文,也时常将晦涩难懂的开源协议解读给大家听,这两件事不是易事,从你个人的成长经历来看,为什么会坚持做这类事情?卫Sir:这两件事有它的相通之处,都有翻译的性质,而且都有难度。如果大家看过原版的《大教堂与集市》,就会知道它阅读和理解起来是比较困难的。开源许可证也有这个特点,如果对开源不了解、对IT没有了解、英文不好,直接阅读会觉得非常吃力。而我正好从事IT领域工作,对开源有一定了解,掌握的英文也足以用来翻译,因此决定做这件事。有一句格言我很喜欢,“麻烦的事中藏着珍宝”。一些简单的事情,可能大家已经有很多途径获得解决方法。但比较难、比较有价值的知识,就需要有一个通俗的媒介,使得人们可以相对容易地去接触、了解、掌握。如果我一个人费点功夫,可以让更多人受益,这是我理解的一种开源精神。CSDN:为什么会鼓励青少年和年轻的开发者去尝试开源?卫Sir:我主要基于两个判断。第一,这个世界终将走向数字化,大家应该能感受到这些年数字化趋势越来越明显,发展速度越来越快。这两年大火的元宇宙,就是人类对于未来生活在数字化世界中的一种设想。另外一方面,我认为未来的软件终将被开源吞噬,大多数都走向了开源。如果闭源软件和开源软件在功能相差无几,那么开源软件竞争力一定更强,因为大家会更倾向于源码开放的软件。因此年轻人参与编程,参与开源项目,也是尽早为进入未来社会做准备。这方面能力越强,在未来就越有竞争力。开源协议是一纸合同,也是创作者与使用者的默认契约CSDN:开源圈中大家都知道你擅长解读开源协议,为什么理解开源协议对我们如此重要?卫Sir:开源协议本质上是一纸合约,是开源项目的作者很认真、很郑重其事地把他对使用者的要求以书面形式展现出来,为了提醒使用者要注要按照他的要求来使用。比如要保留作者版权信息,使用导致的问题不要找作者等等。如果使用者不重视这些明文要求,在使用过程中违背了开源许可证,可能会给自己带来麻烦。轻则引发作者本人的愤怒,产生一些纠纷,重则惹上诉讼、官司,因此作为开源使用者一定要重视开源协议中的要求。CSDN:能否为开源新手介绍一下常用的几种开源协议和它们的特点,区别是什么?假如要开源一个项目,该如何在其中进行选择?卫Sir:最常用的开源协议包括BSD、MIT、Apache、GPL。简单地说,BSD和MIT是最简单、最宽松的协议,对使用者要求也是最少的。Apache许可证不像MIT和BSD那么简单,它包含了一条关于专利的描述,这是它比较创新的地方,风格也相对严谨,考虑的因素会更加全面。 GPL最主要的特点是它具有“传染性”,使用附带GPL协议的开源代码,其项目也必须要开源。使用者需要注意不同开源协议的特点,结合自己的情况挑选适合的协议。如果是自己写写玩票性质的小软件,用MIT、BSD就可以,不用那么讲究。如果是个有一定规模和考虑商业化的开源软件,可以考虑使用Apache协议,会比较周全,它会考虑专利的授权问题;如果比较崇尚自由软件的精神和理念,则可以用GPL系列的协议。我个人比较喜欢宽松的协议,比如MIT简单、随意,既然都选择了开源,不如开放得更彻底一些,有些东西放到公共空间(Public Domain)也很不错。CSDN:目前国内还没有以开源软件为主题的法律,开源软件的协议是否具有法律效应?它怎么约束开源作者和使用者?卫Sir:国内虽然没有直接和开源相关的法律,但开源协议可以看作是个合同,受《合同法》的保护。虽然创造者与使用者双方并没有在协议上面签字,但是作者在软件发布时是附带着“合同”一并发布的。开发者一旦使用作者发布的软件就默认接受了合同的规定,如果不接受就不要使用。此外,开源软件本身是有版权的,所以它也受《著作权法》保护。国内去年就有两起与GPL协议相关的著名案件,经法院审判后的判决书我们也能看到,我就此写了两篇文章,大家有兴趣可以看看。CSDN:最近地缘政治冲突已经蔓延到了开源世界,甚至像GitHub这种开源平台和一些开源社区,在美国政府要求或呼吁下已经向俄罗斯停止部分服务,这个举措是否意味着法律政策凌驾于一切开源协议之上?开源是否存在国界?卫Sir:开源协议只是一个合同,显然法律的效力大于开源协议,尤其是当开源协议与法律所规定的内容冲突时,法律的效力更高。GitHub的这次限制,是这么说的“在我们努力确保所有国家的开发者都能使用GitHub的同时,我们也在继续确保所有人都能获得免费的开源服务,包括俄罗斯的开发者。我们的法律团队对此类权限进行了彻底审查,而且我们正在遵守不断发展的出口管制和贸易法规。这包括实施严格的新出口管制,旨在严格限制俄罗斯获得其维持侵略性军事能力所需的技术和其他物品。”我们需要认识到的一点,GitHub是一家被微软收购的公司,它的所有行为仅仅代表这个美国公司的行为,而美国公司是受美国法律管控的。因此,我们可以说,开源程序、开源精神、开源社区是无国界的,但每一个开源开发者,每一家开源公司,都是有所属国家的。维护开源安全需要多方共同努力CSDN:一般企业使用开源软件会存在哪些风险?卫Sir:企业使用开源主要存在两大类风险。第一大类风险是能否用好开源。要想用好开源其实不太容易,一个企业的代码可能是自己开发的,或是从供应商购买的软件和代码。对企业来说,如果技术能力不强,即便成功部署了开源软件,出现安全漏洞时,可能还得等待社区出了补丁才能把漏洞补上。但如果社区短期内无法给与支持,企业自身是没有能力修复的(除非是一家有很强技术实力的IT企业),这对企业来说是一个风险。第二类是合规性问题,主要存在于在那些有输出能力的企业中。输出产品、代码都需要检查其是否违背开源许可证中要求的内容。假设代码中使用了含有GPL协议的开源代码,企业是否做到了将源码开放出去?如果没有就会出问题,这就是合规风险。CSDN:开源技术,因为它开放和不设门槛的特点,很容易受到黑客攻击。之前发生过多起开源技术漏洞导致的安全事故,你从网络安全专家的角度如何看待开源安全性?卫Sir:开源的安全性和任何软件的安全性没有本质区别,只要是人写的软件,都会有安全问题,和它是否开源关系不大。业界有个广泛流传的说法:“只要眼球多,bug容易捉”。由于开源天然的协作特性,开源的安全性应该更高,但事实上,很多安全漏洞隐藏得很深,不是眼球多就能看出来的,想要发现这种漏洞,独特的安全技术和安全专业能力是必不可少的。而且找功能bug和找安全bug的思路是完全不同的。开发人员的思维和安全人员的思维完全是反着来的。开发人员更在意实现功能和提升性能,而安全人员考虑的是如何用各种意想不到的手段获取数据和权限。如何理解这两种思维的差异?可以从最简单的用户名和密码的功能为例。传统测试只会测试不同长度、不同类型的字符输入是否会导致程序崩溃和异常;而安全测试人员会构造精巧的数据作为输入,比如构造SQL注入语句,或者导致缓冲区溢出执行恶意代码的数据,这是传统测试不会考虑的。安全人员更多是要用一种在开发人员开起来匪夷所思的角度和路径去找到那些隐藏很深的安全bug,这些不是常规的测试,这是安全性测试。当然,我们建议开发人员应当具备一定的安全思维和安全能力,但看上去困难重重,开发人员对安全往往是不太关心的。专业的培训会有一定帮助,再有就是在整个软件生命周期嵌入安全流程,比如DevSecOps等,让人们做出更安全的软件。要想防范甚至消灭开源软件的安全漏洞,除了提升开发者安全能力和加强流程管控外,还可以在更多技术领域发力,从CPU、硬件体系架构、操作系统、编程语言、安全工具等方面,都可以有更多的安全创新和改进。总的来说,经过20多年的发展,不论是安全法规、安全理念、安全意识,还是安全技术、安全产品、安全生态,整个环境比我刚接触这个领域时(2000年左右)要好得太多了。我想看到开源吞噬世界的那一天CSDN:如今国内的一些开源产品和开源文化的发展情况如何?是否存在瓶颈?卫Sir:我所了解到的国内比较出名的开源产品目前都发展得很好,但有更多无法脱颖而出的开源项目没有进入大家的视野。谈到开源文化,我能感受到如今大家对开源的态度比原来要友好很多。10年前左右,很多人对开源甚至会有一些错误的认识,认为开源是“坏”东西,是“别有用心”的,这在现在看来简直不可思议。现在大家逐渐以一种实用主义的眼光来看待开源,发现开源软件不仅可以解决自己的问题,还能获取源代码、甚至商业化。我认为只要不违反开源许可证,这些想法都是自然的、正常的,是理性思考的结果。另一进步是如今大家普遍意识到了开源可能带来的风险,和开源的合规问题。有经济实力的公司会购买商业版本的开源软件,并且会购买开源相关服务。一些IT企业也开始贡献上游,开源内部软件,促进开源生态发展,这些都值得赞赏。总的来说,我认为国内开源目前没有瓶颈,也无需突破。大家自由发展、自由竞争、自由使用,只要合规就好。CSDN:你曾专门撰文探讨 faker.js 和 colors.js 的项目作者毁库跑路的故事,这里着重探讨了开源程序员在“如何用开源获得收益”的困境,除此之外,您认为开源开发者还面临着哪些困境?卫Sir:在很长一段时间内(对于个人项目,现在我也这么认为),我都认为开源是有钱有闲的人玩的游戏,你可以将它作为一种兴趣或者乐趣。如果你还面临温饱问题,我建议还是先务实一点,找一份被雇佣、可以拿薪水的工作,不要试图快速从开源之中变现。玩开源可以为了乐趣,为了提高能力、获得名声,为了满足存在感,但不管怎样,现阶段个人玩开源,不要指望能直接赚到钱。开源本身更多是一种奉献精神,对个人来说,这是开源最主要的困境。此外,很多人搞创作还是希望得到他人的认同,发挥自己的价值,给社会带来一些有益的影响。但你开源的代码可能未必有人关注,也未必有人下载、使用它,也未必产生你预期的效果和影响力。创作者的普遍困境就是,当你的作品并没有人关注,没有人用的,一定会感到很失落,也很容易放弃。所以你做的东西,必须足够优秀,解决足够有趣、有价值的问题,你必须拥有足够的能力,甚至包括程序员所稀缺的营销能力,才能获得更多的关注和认可。CSDN:你曾经说“免费”、“搭便车”等话题已经失去了讨论的意义,那么开源圈内还有哪些问题是您长期比较最关心的?卫Sir:我比较关心的是,什么时候每个人都能用上开源的桌面操作系统、开源的office软件,甚至于人们使用的软件全都是开源的?也即开源何时能够真正地吞噬世界?这大概需要很长时间,甚至在有的领域永远不会实现,比如可以直接盈利的消费型软件一般都不会开源。不过,以后的世界真的很难想象,也许开源会以一种现在无法想象的方式,吞噬世界。此外,前面谈到一个程序员因为无法获得收入愤然删库跑路的例子,我们是否可以找到一种方式,让程序员进入一种自由编码的状态,不再需要考虑生计问题,且不用被雇佣,仅仅通过写代码,参与开源项目或是自创项目,就可以轻松获得回报?这也是我长期关心的问题。开源开发者因为赚不到钱而删库跑路不是个例,他们的代码使用量很大,但因为是免费的,没有任何经济回报的机制,开发者就无法因此致富。假如能有一种方式,只要代码被人使用了,产生价值了,就能得到收益,这对开源开发者而言就是非常理想的状态。目前看来web3似乎是一条比较可行的道路,但也有人觉得Web3并不靠谱。一切尚不明朗,不过我很期待。《开源访谈录》是一档聚焦开源发展的访谈类栏目,每周将邀请1位具代表性及影响力的开源专家,探讨大家广泛关注的开源话题,从不同角度还原开源圈真实面貌。————————————————版权声明:本文为CSDN博主「开源头条」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/csdnopensource/article/details/124726837链接:https://blog.csdn.net/csdnopensource/article/details/124726837?spm=1000.2115.3001.5927
-
各位开发者小伙伴:经过线上答辩激烈角逐机器人7天训练营毕业设计活动共有9名杰出同学(含2位得分并列选手)凭借对RPA技术的深入理解和自动化场景痛点挖掘获得最终奖项具体获奖名单如下,公示期3天(6月20-6月22日)3天后7个工作日进行奖品发放恭喜以下同学!如有异议请与小助手01微信沟通哈~序号论坛昵称作品名称最终排名1頭文字C监控截图自动化数字机器人TOP12etudou自动收集业务信息,提升教师工作效率TOP23yd_48903315项目执行报告自动化案例TOP34yd_213290046商城自动补货机器人TOP45yuebao股票中签是否缴费小助手TOP56suifeng1324采购物品电商价格收集最有价值机器人奖7君临天下夜未央社群早读消息制作与发送RPA机器人最具创新机器人奖8hw16621059502物流订单自动获取导入极具潜力机器人奖9yd_234739558变更文档自动化检查案例极具潜力机器人奖为巩固华为数字机器人7天训练营知识点学习引导理论归于实践,同时发掘优秀RPA实践激励更多机器人在业务场景的应用7天训练营学员可报名参加本活动提交毕业设计作品,并参与评选赢取Switch游戏机、GT手表、智能机器人等多重好礼!【活动时间】5月1日-5月30日【参赛对象】所有已报名参加“华为数字机器人7天训练营“学习,且至少完成3次打卡的学员。【赛程与评选规则】作品初审:组织专家评委针对投稿作品初步评审,选择10-15个作品进入答辩环节线上答辩:开发者/团队通过线上答辩的方式进行,由评委综合打分评定优秀毕业设计作品打分项主要包括如下内容:内容评审(50%):• 完整性• 背景及流程痛点等(清晰与否)• 流程优化(前后流程对比)• 实施效果(是否痛点已解决)• 总结RPA价值可视(被量化)价值评审(45%)• 量化价值(提升效率、节约成本)• 借鉴性(有推广价值)表达评审点(5%)• 语言流畅• 可读性强• 逻辑清晰【提交作品要求】1. 作品主题可结合身边实际场景设计并开发,也可参考附件场景2. 按照模板提交案例(见文末附件),可采用word或PPT形式3. 案例包含可执行的xml文件(要求将用户名和密码等脱敏)4. 可选,作品项目流程完整运行录播视频(MP4格式,5min以内,内容脱敏)5. 作品必须为本人原创,严禁抄袭直接在本帖以附件形式回复相关作品并按照以下要求回帖即可。回复格式:华为云账号+微信昵称+参赛作品附件【奖励说明】本次共设立八个奖项第一名:任天堂便携掌上游戏机第二名: HUAWEI WATCH GT 2 运动款第三名: HUAWEI FreeBuds Pro 真无线耳机第四名: HUAWEI FreeLace Pro 无线耳机第五名:WeeeBot mini 酷跑侠最有价值机器人奖:富士INSTAX 一次成像相机 mini7最具创新机器人奖:富士INSTAX 一次成像相机 mini7极具潜力机器人奖:富士INSTAX 一次成像相机 mini7毕业设计奖励【可参考场景】No.自动化应用场景说明1HR场景招聘(51、智联招聘、Boos直聘、猎聘、拉勾等)、面试安排、新员工社保自动转入、五险一金操作等2电商场景竞品竞店日报(生意参谋、赤兔名品、淘宝等)、价格发现3供应链场景快递物流跟踪、货运托书订舱、报关及检疫录入等4营销辅助场景微信营销推广、推广链接自动检查等5其它场景企业舆情采集、微博数据分析、大学图书系统自动化查询和统计、股票自动看盘助手等;其他事宜请参考【活动页面规则】 及【华为云社区常规活动规则】 。【华为数字机器人7天训练营】课程活动直通车:【数字机器人结业考试】华为数字机器人7天训练营结业考试【华为数字机器人7天训练营】活动页点击查看
-
最火的编程语言python多次上榜 今天给大家分析几个python小程序1:Python小游戏实际分析:猜数游戏:猜数字是一个古老的密码破译的益智类小游戏 通常又俩人及以上人去参与,一个人设置一个数字一个人或多人猜 当猜数字的人说出一个数字时 由出数字的人告知是否猜中;若猜大了应该给予相应的提示比如:猜小了,若猜小了应该给予相应的提示比如:猜大了。猜的数字等于电脑的随机数字应提示:恭喜您 猜数成功代码:import randomprint("猜数字游戏,输入一个1-100以内的数字")random_num = random.randint(1, 100)# print(random_num) # 打开注释可查看生成的随机数for frequency in range(1,6): number = input("请输入一个数字:") if number.isdigit() is False: print('请输入一个正确的数字') elif int(number) < 0 or int(number) > 100: print("请输入1-100范围的数字") elif random_num == int(number): print("恭喜你用了%d次猜对了" % frequency) break elif random_num > int(number): print("很遗憾,你猜小了") else: print("很遗憾,你猜大了") if frequency == 5: print("很遗憾,%d次机会已用尽,游戏结束,答案为%d" % (frequency, random_num))2:叫⑦ 当代年轻人最喜欢的游戏叫⑦游戏规则:每次到啊7或7的倍数的数字就需要拍手如果数了就需要又相应的惩罚:思考:我们需要不断的从1-101进行遍历读取每一个数,判断条件是否包含7是不是7的的倍数这四种可能代码:for i in range(1, 101): # 把i转成字符串,使用find方法(字符串中不包含时,返回-1) include = str(i).find("7") # 判断条件:既不包含7,也不是7的倍数 if include == -1 and int(i) % 7 != 0: # 输出,去掉了换行符,加了、 print(i, end="、") # 如果包含7 输出* elif include != -1 or int(i) % 7 == 0: print("*", end='、')3:数据加密有没有小伙伴很好奇当年他们用电台去传电报是怎么实现翻译的又衍生了摩斯密码其实每一个代码都对应一个解码比如我们现在输入在电脑的内容都是二进制编程的生活中也有比如 12进制,10进制都需要转换成对应的我们所需要的进制进行翻译才能成功破解思考:ASCLL是什么对ASCLL理解代码:raw_data = input('请输入密码:') num_asc = 0 # ASCII累加值 str_pwd = '' # ASCII拼接值 for i in raw_data: ascii_val = ord(i) # 1.获取每个元素的ASCII值 num_asc = ascii_val + num_asc # 2.对遍历的ASCII值进行累加操作 str_pwd += str(ascii_val) # 3.拼接操作 reversal_num = str_pwd[::-1] # 4.将拼接的ASCII值倒序排列 encryption_num = int(reversal_num) + num_asc # 5.进行运算操作 print(f"加密后的密码为:{encryption_num}")
-
没装软件的先安装一下软件,没装模块的安装一下pygame模块。pip install pygame导入模块import pygame,sys,time,randomfrom pygame.locals import *定义颜色变量redColour = pygame.Color(255,0,0)blackColour = pygame.Color(0,0,0)whiteColour = pygame.Color(255,255,255)greenColour = pygame.Color(0,255,0)headColour = pygame.Color(0,119,255)在所有后续的除法中,为预防pygame输出出现偏差,必须取除数(//)而不是单纯除法(/)程序界面第0行,HEIGHT行,第0列,WIDTH列为围墙,所以实际大小是13*13IGHT = 15WIDTH = 15FIELD_SIZE = HEIGHT * WIDTH# 蛇头位于snake数组的第一个元素HEAD = 0用数字代表不同的对象,因为运动时矩阵上每个格子会处理成到达食物的路径长度,因此这三个变量间需要有足够大的间隔(>HEIGHT*WIDTH)来互相区分,小写一般是坐标,大写代表常量。FOOD = 0UNDEFINED = (HEIGHT + 1) * (WIDTH + 1)SNAKE = 2 * UNDEFINEDsnake是一维数组,对应元素直接加上以下值就表示向四个方向移动。LEFT = -1RIGHT = 1UP = -WIDTH # 一维数组,所以需要整个宽度都加上才能表示上下移动。DOWN = WIDTH 错误码ERR = -2333用一维数组来表示二维的东西,board表示蛇运动的矩形场地,初始化蛇头在(1,1)的地方,初始蛇长度为1。board = [0] * FIELD_SIZE #[0,0,0,……]snake = [0] * (FIELD_SIZE+1)snake[HEAD] = 1*WIDTH+1snake_size = 1与上面变量对应的临时变量,蛇试探性地移动时使用。tmpboard = [0] * FIELD_SIZEtmpsnake = [0] * (FIELD_SIZE+1)tmpsnake[HEAD] = 1*WIDTH+1tmpsnake_size = 1food:食物位置初始在(4, 7),best_move: 运动方向。food = 4 * WIDTH + 7best_move = ERR运动方向数组,游戏分数(蛇长)mov = [LEFT, RIGHT, UP, DOWN] score = 1 检查一个cell有没有被蛇身覆盖,没有覆盖则为free,返回true 。def is_cell_free(idx, psize, psnake): return not (idx in psnake[:psize]) 检查某个位置idx是否可向move方向运动def is_move_possible(idx, move): flag = False if move == LEFT: #因为实际范围是13*13,[1,13]*[1,13],所以idx为1时不能往左跑,此时取余为1所以>1 flag = True if idx%WIDTH > 1 else False elif move == RIGHT: #这里的<WIDTH-2跟上面是一样的道理 flag = True if idx%WIDTH < (WIDTH-2) else False elif move == UP: #这里向上的判断画图很好理解,因为在[1,13]*[1,13]的实际运动范围外,还有个 #大框是围墙,就是之前说的那几个行列,下面判断向下运动的条件也是类似的 flag = True if idx > (2*WIDTH-1) else False elif move == DOWN: flag = True if idx < (FIELD_SIZE-2*WIDTH) else False return flag重置boardboard_BFS后,UNDEFINED值都变为了到达食物的路径长度。如需要还原,则要重置它。def board_reset(psnake, psize, pboard): for i in range(FIELD_SIZE): if i == food: pboard[i] = FOOD elif is_cell_free(i, psize, psnake): # 该位置为空 pboard[i] = UNDEFINED else: # 该位置为蛇身 pboard[i] = SNAKE广度优先搜索遍历整个board,计算出board中每个非SNAKE元素到达食物的路径长度。def board_BFS(pfood, psnake, pboard): queue = [] queue.append(pfood) inqueue = [0] * FIELD_SIZE found = False # while循环结束后,除了蛇的身体, # 其它每个方格中的数字为从它到食物的曼哈顿间距 while len(queue)!=0: idx = queue.pop(0)#初始时idx是食物的坐标 if inqueue[idx] == 1: continue inqueue[idx] = 1 for i in range(4):#左右上下 if is_move_possible(idx, mov[i]): if idx + mov[i] == psnake[HEAD]: found = True if pboard[idx+mov[i]] < SNAKE: # 如果该点不是蛇的身体 if pboard[idx+mov[i]] > pboard[idx]+1:#小于的时候不管,不然会覆盖已有的路径数据。 pboard[idx+mov[i]] = pboard[idx] + 1 if inqueue[idx+mov[i]] == 0: queue.append(idx+mov[i]) return found从蛇头开始,根据board中元素值,从蛇头周围4个领域点中选择最短路径。def choose_shortest_safe_move(psnake, pboard): best_move = ERR min = SNAKE for i in range(4): if is_move_possible(psnake[HEAD], mov[i]) and pboard[psnake[HEAD]+mov[i]]<min: #这里判断最小和下面的函数判断最大,都是先赋值,再循环互相比较 min = pboard[psnake[HEAD]+mov[i]] best_move = mov[i] return best_move检查是否可以追着蛇尾运动,即蛇头和蛇尾间是有路径的,为的是避免蛇头陷入死路。虚拟操作,在tmpboard,tmpsnake中进行。def is_tail_inside(): global tmpboard, tmpsnake, food, tmpsnake_size tmpboard[tmpsnake[tmpsnake_size-1]] = 0 # 虚拟地将蛇尾变为食物(因为是虚拟的,所以在tmpsnake,tmpboard中进行) tmpboard[food] = SNAKE # 放置食物的地方,看成蛇身 result = board_BFS(tmpsnake[tmpsnake_size-1], tmpsnake, tmpboard) # 求得每个位置到蛇尾的路径长度 for i in range(4): # 如果蛇头和蛇尾紧挨着,则返回False。即不能follow_tail,追着蛇尾运动了 if is_move_possible(tmpsnake[HEAD], mov[i]) and tmpsnake[HEAD]+mov[i]==tmpsnake[tmpsnake_size-1] and tmpsnake_size>3: result = False return result让蛇头朝着蛇尾运行一步,不管蛇身阻挡,朝蛇尾方向运行。def follow_tail(): global tmpboard, tmpsnake, food, tmpsnake_size tmpsnake_size = snake_size tmpsnake = snake[:] board_reset(tmpsnake, tmpsnake_size, tmpboard) # 重置虚拟board tmpboard[tmpsnake[tmpsnake_size-1]] = FOOD # 让蛇尾成为食物 tmpboard[food] = SNAKE # 让食物的地方变成蛇身 board_BFS(tmpsnake[tmpsnake_size-1], tmpsnake, tmpboard) # 求得各个位置到达蛇尾的路径长度 tmpboard[tmpsnake[tmpsnake_size-1]] = SNAKE # 还原蛇尾 return choose_longest_safe_move(tmpsnake, tmpboard) # 返回运行方向(让蛇头运动1步)在各种方案都不行时,随便找一个可行的方向来走(1步)def any_possible_move(): global food , snake, snake_size, board best_move = ERR board_reset(snake, snake_size, board) board_BFS(food, snake, board) min = SNAKE for i in range(4): if is_move_possible(snake[HEAD], mov[i]) and board[snake[HEAD]+mov[i]]<min: min = board[snake[HEAD]+mov[i]] best_move = mov[i] return best_move转换数组函数def shift_array(arr, size): for i in range(size, 0, -1): arr[i] = arr[i-1]def new_food():#随机函数生成新的食物 global food, snake_size cell_free = False while not cell_free: w = random.randint(1, WIDTH-2) h = random.randint(1, HEIGHT-2) food = WIDTH*h + w cell_free = is_cell_free(food, snake_size, snake) pygame.draw.rect(playSurface,redColour,Rect(18*(food//WIDTH), 18*(food%WIDTH),18,18))真正的蛇在这个函数中,朝pbest_move走1步。def make_move(pbest_move): global snake, board, snake_size, score shift_array(snake, snake_size) snake[HEAD] += pbest_move p = snake[HEAD] for body in snake:#画蛇,身体,头,尾 pygame.draw.rect(playSurface,whiteColour,Rect(18*(body//WIDTH), 18*(body%WIDTH),18,18)) pygame.draw.rect(playSurface,greenColour,Rect(18*(snake[snake_size-1]//WIDTH),18*(snake[snake_size-1]%WIDTH),18,18)) pygame.draw.rect(playSurface,headColour,Rect(18*(p//WIDTH), 18*(p%WIDTH),18,18)) #下面一行是把初始情况会出现的第一个白块bug填掉 pygame.draw.rect(playSurface,(255,255,0),Rect(0,0,18,18)) # 刷新pygame显示层 pygame.display.flip() # 如果新加入的蛇头就是食物的位置 # 蛇长加1,产生新的食物,重置board(因为原来那些路径长度已经用不上了) if snake[HEAD] == food: board[snake[HEAD]] = SNAKE # 新的蛇头 snake_size += 1 score += 1 if snake_size < FIELD_SIZE: new_food() else: # 如果新加入的蛇头不是食物的位置 board[snake[HEAD]] = SNAKE # 新的蛇头 board[snake[snake_size]] = UNDEFINED # 蛇尾变为UNDEFINED,黑色 pygame.draw.rect(playSurface,blackColour,Rect(18*(snake[snake_size]//WIDTH),18*(snake[snake_size]%WIDTH),18,18)) # 刷新pygame显示层 pygame.display.flip() 如果蛇与食物间有路径,则调用本函数。def find_safe_way(): global snake, board safe_move = ERR # 虚拟地运行一次,因为已经确保蛇与食物间有路径,所以执行有效 # 运行后得到虚拟下蛇在board中的位置,即tmpboard virtual_shortest_move() # 该函数唯一调用处 if is_tail_inside(): # 如果虚拟运行后,蛇头蛇尾间有通路,则选最短路运行(1步) return choose_shortest_safe_move(snake, board) safe_move = follow_tail() # 否则虚拟地follow_tail 1步,如果可以做到,返回true return safe_move初始化pygame 模块pygame.init()定义一个变量用来控制游戏速度fpsClock = pygame.time.Clock()创建pygame显示层playSurface = pygame.display.set_mode((270,270))pygame.display.set_caption('贪吃蛇')绘制pygame显示层playSurface.fill(blackColour)初始化食物pygame.draw.rect(playSurface,redColour,Rect(18*(food//WIDTH), 18*(food%WIDTH),18,18))while True: for event in pygame.event.get():#循环监听键盘和退出事件 if event.type == QUIT:#如果点了关闭 print(score)#游戏结束后打印分数 pygame.quit() sys.exit() elif event.type == KEYDOWN:#如果esc键被按下 if event.key==K_ESCAPE: print(score)#游戏结束后打印分数 pygame.quit() sys.exit() # 刷新pygame显示层 pygame.display.flip() #画围墙,255,255,0是黄色,边框是36是因为,pygame矩形是以边为初始,向四周填充边框 pygame.draw.rect(playSurface,(255,255,0),Rect(0,0,270,270),36) # 重置距离 board_reset(snake, snake_size, board) # 如果蛇可以吃到食物,board_BFS返回true # 并且board中除了蛇身(=SNAKE),其它的元素值表示从该点运动到食物的最短路径长 if board_BFS(food, snake, board): best_move = find_safe_way() # find_safe_way的唯一调用处 else: best_move = follow_tail() if best_move == ERR: best_move = any_possible_move() # 上面一次思考,只得出一个方向,运行一步 if best_move != ERR: make_move(best_move) else: print(score)#游戏结束后打印分数 break # 控制游戏速度 fpsClock.tick(20)#20看上去速度正好
-
今天是 2022 年 4 月 4 日,清明节。扫墓祭祖与踏青郊游是清明节的两大主题,而埋葬总陪伴着新生,在 1896 年的今天,首届现代奥运会在雅典举行。 现代奥运会创始人为法国的顾拜旦,1894 年他在国际业余体育组织上提出召开奥运会,并由此成立了国际奥委会(IOC);后经英皇和希腊女王的支持,奥委会于 1896 年的今天在希腊雅典召开第一届现代奥运会,以后每隔 4 年举行一次。回首过去,在计算机历史的这一天都发生过哪些关键事件呢?1954 年 4 月 4 日:Apple I 工程师 Daniel Kottke 出生丹尼尔·科特克(Daniel Kottke)于 1954 年 4 月 4 日出生在纽约,他是史蒂夫·乔布斯的大学同窗,也是苹果公司的首批员工之一;1972 年,科特克第一次见到史蒂夫·乔布斯,当时他们都还是里德学院的本科生。1974 年,科特克和乔布斯前往印度朝圣,拜访了当地知名的静修所,但静修所当时的主持已于 1973 年 9 月去世;然后,他们沿着干涸的河床长途跋涉,前赴另一处精修所朝圣。从印度回来后,科特克于 1975 年转到哥伦比亚大学,主修文学和音乐;他与乔布斯仍保持联系,他们还一起参加过里德学院的同学会。1976 年,科特克在哥伦比亚大学三年级时,乔布斯要求科特克和他一起参与一个业余计算机项目;科特克在夏天组装并测试了电路板,正是在那个夏天,科特克意识到了自己对计算机的兴趣。科特克组装和测试的这些电路板随即在史蒂夫·沃兹尼亚克的手中进一步变成了 Apple I 计算机,这台计算机也成为了苹果公司一切的开始。1977 年,从哥伦比亚大学毕业后,科特克作为全职员工加入了苹果公司;就在那时,乔布斯、科特克和乔布斯的女友克里斯安·布伦南在库比蒂诺的苹果办公室附近合租了一所房子。“我是唯一一个在车库工作过的人……沃兹尼亚克每周会来一次,带着他最新完成的代码来。从那个意义上说,乔布斯没有亲自操刀过代码。 ——丹尼尔·科特克科特克在 Apple 工作了八年,期间他调试 Apple II 印刷电路板,构建 Apple III 和 Macintosh 原型,并致力于 Macintosh 键盘的设计。Apple II 获得了相当大的人气,最终成为 1970 年代和 1980 年代初期最畅销的个人电脑之一;科特克也是 Macintosh 开发团队的原始成员之一,他的签名可以在早期生产的 Macintosh 计算机的内墙上找到。然而,乔布斯拒绝授予科特克任何创业元老级员工应有的股权,反而是沃兹尼亚克后来将把他自己在苹果公司的一些股份交给了丹尼尔·科特克。此外,在 1982 年《时代》杂志刊登了乔布斯的那些不讨人喜欢的形象时,他公开斥责科特克向时代杂志提供了消息,其中部分消息证实了他有一个女儿丽莎·布伦南的事实。科特克在后来回顾往事时,曾这样谈及股份的事情:“乔布斯不再和我交谈。跟我合作的所有人都成了百万富翁,而我还在打工,每小时挣 12 美元。不过,20 年前我就原谅他了,可我能想象到他心里有愧,不想面对这件事。”1975 年 4 月 4 日:科技巨头微软公司诞生微软(Microsoft)是美国的一家跨国电脑科技公司,公司于 1975 年 4 月 4 日由比尔·盖茨与保罗·艾伦创立,以研发、制造、授权及提供广泛的电脑软件服务为主;公司总部位于美国华盛顿州的雷德蒙德,最为著名及畅销的产品是 Microsoft Windows 操作系统及 Microsoft Office 办公室软件,以及 Xbox 游戏业务与动视暴雪。微软初期主要为 Altair 8800 发展和销售 BASIC 解释器,在 1980 年代中期凭借 MS-DOS 在家用电脑操作系统市场上获取长足进步,后来出现的 Windows 使得微软逐渐统治了家用桌面电脑操作系统市场。同时微软也开始扩张业务,进军其他行业和市场,创建了 MSN 网站,在电脑硬件市场上,微软商标及 Xbox 游戏机、Zune 和 MSN TV 家庭娱乐设备也在不同的年份出现在市场上。微软于 1986 年首次公开募股,此后不断走高的股价为微软缔造了四位亿万富翁和一万两千名百万富翁。比尔·盖茨和保罗·艾伦是小时候认识的朋友及高中同学,他们对电脑编程充满激情,并利用自己的演讲技能,意图开辟一条财路。1975 年 1 月,MITS 公司发售了 Altair 8800 大众化微电脑和遥测系统,这令他们注意到,可以编写一个市场上缺少的 BASIC 解释器赚钱;他们立即致电给 Altair 8800 的发明者(MITS),提出在该系统中执行 BASIC。MITS 公司对此深感兴趣,并要求艾伦和盖茨进行示范。之后 MITS 公司聘请艾伦为 Altair 进行模拟器的开发工作,而盖茨则开发解释器。他们的工作十分出色,1975 年 3 月,MITS 公司同意出售 Altair BASIC,盖茨和艾伦也顺利赚了第一桶金;于是盖茨离开哈佛大学,并搬到 MITS 在新墨西哥州阿布奎基的总部。1975 年 4 月 4 日,微软正式成立,盖茨为微软首席执行官。他在 1995 年《财富》杂志的一篇文章中回忆,原名“Micro-Soft”是艾伦想出来的,之后更改为“Microsoft”。微软在 1980 年开发了 UNIX 操作系统业务,叫做 Xenix,发表后受到欢迎,并巩固了公司的霸主地位。1980 年 11 月,微软获得了 IBM 公司的合同,这笔交易中,微软要提供 CP/M 操作系统给即将到来的 IBM 个人电脑。从此在非 IBM 硬件上便可运行微软更改后的 MS-DOS 系统,从而完全改变了当时的电脑业的规则和生态。微软在 1983 年扩大业务,发布微软鼠标,并成立微软出版社进入新的市场。但不幸的是,保罗·艾伦患上霍奇金病,于同年二月向微软辞职。1984 年,微软和 IBM 开始合作开发一款不同以往的操作系统 OS/2;1985 年 11 月 20 日,微软推出了首款 Microsoft Windows 的零售版,最初作为其 MS-DOS 系统的图形拓展版本。由于与 IBM 的合作伙伴关系,导致 1990 年联邦贸易委员会开始对微软的监控;标志着微软与美国超过十年的法律冲突的开始。伴随公司的强大,微软也受到越来越多的批评和指责,并且数十年来从未间断。拒绝交易和捆绑销售等做法招致垄断和不正当竞争的诉讼。美国司法部和欧盟委员会根据反托拉斯法均对微软作出过不利裁定。截至 2015 年,尽管在 Android 和 iOS 的竞争下,微软已经失去了绝大部分移动设备系统占比,但在 IBM PC 兼容操作系统市场和办公软件套装市场仍占据主导地位。2018 年,微软曾一度超越苹果公司,成为全球最有价值的上市公司。在 2019 年 4 月,微软达到了 1 兆美元的市值,成为仅次于苹果公司第二家股价市值超过 1 兆美元的美国上市公司。1994 年 4 月 4 日:第一家真正意义上的互联网公司——网景成立1994 年 1 月 27 日,硅图公司(Silicon Graphics)的联合创始人吉姆·克拉克(Jim Clark)离开公司,与马克·安德森(Marc Andreessen)一起创办了 Mosaic 公司。Mosaic 后来改名为网景通信公司(Netscape),该公司的第一个产品是 Mosaic Netscape 网络浏览器,随后因版权问题更名为 Netscape Navigator。网景的用户份额在 1990 年代中期达到顶峰,但在和微软的浏览器战争中惨败,到 2006 年底仅剩不足 1%。网景是第一家尝试利用万维网的公司,它开发了第一个被广泛使用的浏览器,与此同时,主导开发了如今市面上广受欢迎的编程语言 JavaScript。网景发明的其他重要技术还包括 Cookie、SSL,并在推送技术、跨平台等方向作出了奠基性贡献。在 1994 年 4 月 4 日成立时的名字为马赛克通信公司(Mosaic Communications Corporation),吉姆·克拉克和马克·安德森当时得到了知名风险投资公司凯鹏华盈的投资;两人的第一次会议所讨论的并非讨论有关软件或服务,而是更多关于一种类似任天堂游戏机的产品。因为克拉克刚出走硅图公司,所以他从 SGI 和 NCSA Mosaic 招募了团队的早期成员;两人利用这份资源最初为任天堂的 N64 游戏机创建了一个 20 页的在线游戏雏形,但这一交易最终没有达成。马克·安德森后来回忆道:“如果这事提早一年,我们后来可能就不会做 Netscape 了。”该公司实际上的第一个产品是网页浏览器──Mosaic Netscape 0.9,发行于 1994 年 10 月 13 日。在发布的四个月后,它已经占据了四分之三的浏览器市场份额。为了避免和 NCSA 的商标拥有权产生问题,这套程序后来改名为 Netscape Navigator,公司也于 1994 年更名为“网景”。1995 年 8 月 9 日,网景首次公开募股获得巨大成功,网景的浏览器市场占有率此时达到 70%;网景的成功亦使马克·安德森于 1996 年成为时代杂志的封面人物。网景的一个目标,是为所有操作系统的用户提供跨平台一致的互联网使用体验。Netscape Navigator 的用户界面在多个平台上始终如一。网景更多次尝试开发一种能够让用户透过浏览器来访问和修改他们的文件的网络应用系统。这引起微软注意,因为这种概念跨越了操作系统的权限,所以微软视为对 Microsoft Windows 的直接威胁。有人声称,数个微软的执行人员在 1995 年 6 月曾参访网景总部,提议分拆市场,即容许微软开发 Windows 浏览器,而网景则负责在其他操作系统上开发产品,网景当时拒绝了。随后,微软的 Internet Explorer 1.0 诞生,微软很快地推出了数个 Internet Explorer 的连续版本,这些版本捆绑于 Windows 操作系统,用户可免费使用——区别于网景浏览器的收费;微软更以自己公司的其他部门的收入,调配于开发 Internet Explorer 的资金中。这段时期,现在被称之为“浏览器大战”。当时,网景和微软为了超越彼此,在它们的浏览器增加许多功能;网景无法对抗微软的“免费程序对上收费程序”策略。同时,网景面对越来越多对其产品程序错误的批评。批评者认为,网景过度偏向“增加功能”,忽视运作稳定,特别是在 1997 年第 4 季和 1998 年 1 月的大规模裁员后,大众对网景的印象渐趋负面。1998 年网景的浏览器市场占有率还超过 50%,但在次年微软浏览器的市占率就超过了网景。1998 年 1 月,网景开始展开 Mozilla 开放源代码项目。在 Internet Explorer 成为浏览器市场的首位后,网景寄望透过开放 Netscape Communicator 4.0 的源代码,使其成为有成就的开源计划。它以 Netscape 公共许可协议(Netscape Public License)发布源代码,这一协议简称 NPL,对标我们所熟悉的 GNU 通用公共许可协议(GNU General Public License,简称 GPL);之后,网景继续开发 Netscape Communicator 4.5,并专注改善电子邮件和企业的功能。没过多久,开源社区终于发觉,他们很难在当时源代码的基础上继续开发 Netscape Communicator。他们决定放弃旧有的源代码,重新编写下一代浏览器。利用新开发的 Gecko 排版引擎,浏览器拥有一个更加模块化的结构,并容许大量开发人员一同工作。新浏览器亦包含一个基于 XML 的用户界面标记语言——XUL。在 XUL 的帮助下,一次性的“用户界面开发”便能在 Windows、Mac OS 和 Unix 中稳定运作。1998 年 5 月,美国司法部控告微软,并提出反垄断诉讼。网景在该案件中不是原告,因此收益有限。1998 年 11 月 24 日,美国在线以 42 亿美元、免税换股的方式收购网景。该次合并受到很多人数落,认为两家公司的企业文化差距很大、不能兼容;美国在线是传统的大型企业,等级制度分明,分工明确;而网景则是互联网企业模式的开创者,惠普时代的硅谷更接近于 20 世纪中期的“白衬衫与黑领带”的工作文化,而网景带来的则是直到 28 年后的现在还在流行的“披萨、咖啡与 T 恤”的企业精神。证实微软因滥用垄断能力而败讼后,美国在线向微软提出索偿诉讼。在 2003 年 5 月,微软和美国在线达成和解协议,愿意赔偿网景 7.5 亿美元,并同意提供美国在线 7 年无限制的使用和散布 Internet Explorer 的权利。这被认为是“网景结束的信号”。2003 年 7 月 15 ,并购之后的超级企业——美国在线时代华纳(详见 1 月 10 日专栏:史上最失败的世纪并购)解散了网景,网景大部分的程序员被解雇,网景的标志建筑物也跟着消失匿迹。2004 年 10 月 12 日,美国在线接着关闭了一个受欢迎的开发者网站 Netscape DevEdge。DevEdge 曾是一个重要的、有关互联网技术的数据源,它维护了网景浏览器、和诸如 HTML 和 JavaScript 技术的帮助文档、以及由业界和技术领导者(如丹尼·古德曼)所编写的文章。部分 DevEdge 的内容已重新发布到 Mozilla Developer Center 网站。在 20 世纪的大部分时间里,孩子们都渴望成为摇滚明星、运动员、股票经纪人或宇航员,但在网景和互联网时代到来之前,创业在美国很长一段时间都不是一件“很酷的事情”;将创业这一概念变为时代精神需要一系列事件的推动,而网景诞生便是这一系列事件中的第一起事件。1994 年时,克拉克对网景的初始投资为 400 万美元;到后来 1999 年网景被 AOL 收购时,他以 12 亿美元退出。克拉克和安德森开创了互联网时代的一个里程碑,用浏览器彻底改变了我们今天的互联网生活。原文链接:https://blog.csdn.net/Byeweiyang/article/details/123950652
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签