ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

basic-computer-games 的 Calendar 游戏 Perl 移植版解析:用 POSIX 时间函数重写经典日历程序

basic-computer-games 的 Calendar 游戏 Perl 移植版解析:用 POSIX 时间函数重写经典日历程序 示例工程【免费下载链接】basic-computer-gamesAn updated version of the classic Basic Computer Games book, with well-written examples in a variety of common MEMORY SAFE, SCRIPTING programming languages. See https://coding-horror.github.io/basic-computer-games/项目地址https://gitcode.com/gh_mirrors/ba/basic-computer-games点击查看免费下载导读21_Calendar/perl/README.md 是本仓库中《Basic Computer Games》第 21 号游戏 Calendar 的 Perl 移植说明。与其他语言版本不同这个 Perl 移植并非逐行翻译 1978 年的 BASIC 源码而是借助 Perl 的 POSIX 时间函数strftime、gmtime、Time::Local做了一次完整重写默认打印当前年份的日历也可以通过在命令行传入年份参数生成任意年份的日历甚至能输出本地化语言locale的月份与星期名称。读完本文你将掌握该移植版的运行方式、命令行参数、底层时间计算原理以及它与原始 BASIC 程序和其他语言移植版的差异。一、从 Vintage Basic 到 Perl一版非典型的移植README 开篇即说明了来源原始 BASIC 源码下载自 Vintage Basic 网站本目录是对应 Perl 语言的转换。但作者特别强调Actually, this is not so much a port as a complete rewrite, making use of Perls Posix time functionality.也就是说这不是严格意义上的移植而是利用 Perl 的 POSIX 时间功能进行的一次完整重写。这与仓库中 21_Calendar/README.md 的 Porting Notes 形成鲜明对比——该文件指出大部分移植版刻意避免使用现代环境自带的时间/日期函数以贴近原始程序的手工计算方式in these ports we are choosing to do without them, as in the original program并选择询问用户一周起始日是星期几以及是否闰年来适配不同年份。Perl 版则反其道而行把日期计算完全交给系统时间库。二、运行方式与命令行参数Perl 版的核心用法在 README 中直接给出perl 21_Calendar/perl/calendar.pl 2001从源码 21_Calendar/perl/calendar.pl 第 26 行可以确认参数解析逻辑my $year ARGV ? $ARGV[0] : ( localtime )[5] 1900;即带参数运行perl calendar.pl 2001把2001作为$ARGV[0]传入打印 2001 年的日历不带参数运行从localtime取出当前年份(localtime)[5]返回当前年份减 1900因此要加回 1900打印当前年份的日历。这一点与原始 BASIC 完全不同BASIC 版在 calendar.bas 第 130 行硬编码D-1: REM 1979 STARTS ON MONDAY即只为 1979 年设计想换年份必须手工修改源码。Perl 版彻底消除了这个痛点。脚本本身是可执行文件#!/usr/bin/env perl因此也可以先chmod x后直接以./calendar.pl 2001方式运行注意本仓库为只读实际运行时请复制到本地目录再执行。三、依赖与运行环境要求源码头部21_Calendar/perl/calendar.pl 第 1-24 行揭示了运行环境要求use 5.010; # To get state and say use strict; # Require explicit declaration of variables use warnings; # Enable optional compiler warnings use English; # Use more friendly names for Perls magic variables use POSIX qw{ strftime }; use Term::ReadLine; # Prompt and return user input use Time::Local ();Perl 版本至少5.10因为脚本使用了sayuse 5.010同时开启该特性严格模式use strictuse warnings要求变量显式声明并启用编译期警告属于现代 Perl 的标配实践核心模块POSIX导出strftime、Time::Local提供timegm/timegm_modern、Term::ReadLine注释表明用于提示并读取用户输入但当前实现中并未实际调用以及English为 Perl 魔法变量提供友好别名字符编码第 24 行binmode STDOUT, :encoding(utf-8)将标准输出声明为 UTF-8这是支持非英文本地化输出的前提之一。此外脚本定义了版本号our $VERSION 0.000_01;以及两个常量use constant COLUMN_WIDTH 6; use constant SECONDS_PER_DAY 86400;COLUMN_WIDTH控制每列打印宽度星期名与日期数字均按 6 字符宽对齐SECONDS_PER_DAY是 1 天的秒数当前代码中虽未直接引用但语义上为时间计算提供常量。四、核心实现原理用 POSIX 时间函数生成日历4.1 闰年判断21_Calendar/perl/calendar.pl 第 76-82 行实现了标准公历闰年规则sub is_leap_year { my ( $year ) 1; return 0 if $year % 4; return 1 if $year % 100; return 0 if $year % 400; return 1; }能被 4 整除且不能被 100 整除或能被 400 整除的年份为闰年。这与原始 BASIC 中要求用户手动修改两处calendar.bas 第 360 行的REM 366-S; ON LEAP YEARS和第 620 行 DATA 中0,31,29,...,ON LEAP YEARS形成了自动化与手工的对照。闰年结果被用于第 27 行my $is_leap_year is_leap_year( $year );第 28 行my $year_len 365 $is_leap_year;计算全年天数第 37 行$mon_len[1] $is_leap_year;把 2 月天数从 28 调整为 29。4.2 逐月生成timegm 与 gmtime 的配合核心循环第 39-74 行对 0..11 共 12 个月依次处理。每个月月初的时间戳通过time_gm计算BEGIN { *time_gm Time::Local-can( timegm_modern ) || Time::Local-can( timegm ); }这里做了一个兼容性技巧优先使用Time::Local模块提供的timegm_modern新版 API若当前版本不存在则回退到经典timegm。timegm与timegm_modern的差异在于处理纪元前后时间的方式不同这里统一封装为time_gm供后续调用。每月月初的 UTC 时间戳为my $epoch time_gm( 0, 0, 0, 1, $month, $year ); my start_time gmtime( $epoch ); my ( $week_day, $year_day ) start_time[ 6, 7 ];time_gm( 0, 0, 0, 1, $month, $year )构造该月 1 日 00:00:00 UTC的时间戳注意Time::Local的月份索引从 0 开始恰好与 Perl 的gmtime一致gmtime返回的列表第 6 个元素是星期几0Sunday第 7 个元素是年内第几天这两个值分别决定了月份的头部信息、星期表头偏移和日期网格的起始列。4.3 月份标题与星期表头月份标题由strftime生成本地化名称my $label strftime( %B %Y, start_time ); $label . x ( ( 14 - length $label ) / 2 ); printf \n** %3d ****** %14s ****** %3d **\n, $year_day, $label, $year_len - $year_day;strftime(%B %Y, ...)输出完整的月份名与年份如March 2001其中%B会跟随当前 locale 设置而变化——这正是 README 所说mayeven produce output in languages other than English可能输出英语之外的其他语言的依据。标题行还像原始 BASIC 一样在左右两侧标出年内第几天$year_day和年内剩余天数$year_len - $year_day呼应 calendar.bas 第 200-360 行的**S*****月份*****365-S**版式。星期表头同样基于真实日期计算而非硬编码my $day 1 ( 7 - $week_day ) % 7; foreach my $wd ( 0 .. 6 ) { my $ep time_gm( 0, 0, 0, $day $wd, $month, $year ); printf %*s, COLUMN_WIDTH, strftime( %a, gmtime $ep ); }这里先算出日历网格第一格对应的日期号再对星期一到星期日逐一求时间戳并格式化缩写。关键点在于表头以周日Sunday开头。README 特意提醒即使在通常以周一开头的 locale 环境下本程序的最左列依然是 Sunday——因为这里依据的是gmtime返回的星期索引0Sunday并以此作为网格起点。4.4 日期数字的排版日期网格输出第 56-72 行先打印COLUMN_WIDTH * $week_day个空格来留出月初之前的空位然后按周填充日期print x ( COLUMN_WIDTH * $week_day ); my $month_day 1; while ( $week_day 7 ) { printf %*d, COLUMN_WIDTH, $month_day; $week_day; }后续行每满 7 个日期换行直到打印完$mon_len[$month]天。整体输出风格6 字符列宽、*分隔线say * x ( COLUMN_WIDTH * 7 );与 BASIC 原版每列 8 字符、59 个*分隔线的视觉传统一脉相承但由真实日期计算驱动。五、与原始 BASIC 及其他移植版的对照维度原始 BASICcalendar.basPerl 版calendar.pl其他多数移植版如 python/calendar.py年份来源硬编码 1979第 130 行D-1注释1979 STARTS ON MONDAY命令行参数或系统当前时间运行时询问用户起始星期与是否闰年闰年处理手工修改第 360、620 行is_leap_year自动判断询问用户IS IT A LEAP YEAR?起始星期手工修改D值0SUN, -1MON, -2TUE…由gmtime自动得出询问用户INSERT THE STARTING DAY OF THE WEEK OF THE YEAR月份/星期名硬编码英文strftime本地化输出硬编码英文原始 BASIC 的星期编码约定0Sunday、-1Monday、-2Tuesday…在 21_Calendar/README.md 中有完整说明并提示可用仓库中的 WEEKDAY 程序95_Weekday确定某年起始日。Python 移植版的 calendar.py 第 30-38 行保留了这个负数编码映射sunday: 0, monday: -1, ...可作为理解该约定的参考。另外值得一提的是 Perl 版还保留了原始程序的标题头CALENDAR Creative Computing Morristown, New Jersey这是 1978 年《Basic Computer Games》书中所有程序的标志性出处标识在 calendar.bas 第 10-20 行以及各移植版如 Java 版 Calendar.java 的showIntro中均被保留。六、POD 文档与许可证信息21_Calendar/perl/calendar.pl 末尾__END__之后嵌入了完整的 PODPlain Old Documentation文档TITLEcalendar - Play the game Calendar from Basic Computer GamesSYNOPSIScalendar.pl不带参数即可运行DETAILS再次强调complete rewrite的性质与命令行年份参数用法PORTED BYThomas R. Wyant, IIIwyant at cpan dot org版权 2022COPYRIGHT AND LICENSE与 Perl 5.10.0 相同的授权条款Artistic License 1.0 与 GPL 1.0 双许可并声明不提供任何担保。可以通过perldoc 21_Calendar/perl/calendar.pl在本地查看这份内嵌文档这也是 Perl 社区常见的代码即文档实践。七、小结Calendar 的 Perl 移植版是仓库中一个重写式移植的代表它没有照搬 BASIC 的逐行逻辑而是用Time::LocalPOSIX::strftimegmtime把当年第几天、星期几、月份长度等日期要素全部交给系统计算从而让程序天然支持任意年份、自动闰年判断甚至可输出本地化语言。其命令行参数perl 21_Calendar/perl/calendar.pl [年份]简洁实用配合 README 中的说明与 POD 内嵌文档既是了解 POSIX 时间函数在文本排版场景下应用的绝佳示例也是对比1978 年手工计算式 BASIC与现代标准库自动化两种程序设计思路的生动教材。# 快速体验打印当前年份日历 perl 21_Calendar/perl/calendar.pl # 打印指定年份日历 perl 21_Calendar/perl/calendar.pl 2001 # 查看内嵌 POD 文档 perldoc 21_Calendar/perl/calendar.pl赞分享示例工程【免费下载链接】basic-computer-gamesAn updated version of the classic Basic Computer Games book, with well-written examples in a variety of common MEMORY SAFE, SCRIPTING programming languages. See https://coding-horror.github.io/basic-computer-games/项目地址https://gitcode.com/gh_mirrors/ba/basic-computer-games点击查看免费下载相关推荐Angular Material MatSelect 组件公开 API 全解析基于 angular/material_select API 报告与源码实战指南Angular Material MatSelect 组件公开 API 全解析基于 angular/material_select API 报告与源码实战指示例工程用 Lua 移植经典《Basic Computer Games》之 Chief四则运算心算游戏的实现解析用 Lua 移植经典《Basic Computer Games》之 Chief四则运算心算游戏的实现解析 导读 Chief 是 1978 年经典书籍《Basi示例工程Basic Computer Games 之 Tower从汉诺塔传说看 1978 年经典 BASIC 游戏的多语言移植Basic Computer Games 之 Tower从汉诺塔传说看 1978 年经典 BASIC 游戏的多语言移植 导读 本文围绕经典书籍 Basic C示例工程上一篇【亲测免费】 CatchAdmin 项目安装与使用教程下一篇【亲测免费】 Mall 开源项目安装与使用指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表