本项目,主要目的是练习模块划分能力,我将整个程序划分为多个模块
显示要求:
打单词
下落方式
显示多个单词
通过正确输入消除单词
对输入正确性统计
能够随着熟练度改变速度
有软件版权信息
好看点,更有吸引力
隐式要求:
单词来源(外部文件或是字符串)
原始单词存储问题(多个同类型元素,采用数组的方式,每个数组元素为字符串单词的地址)char *word[300]
显示单词的选择,显示个数的确定
单词显示区域
单词输入时的回显问题,输入的单词需要被看见
输入正确性验证(验证查表的位置选择,是数据源还是其他存储区域)
模块划分:
dictionary数据源模块,负责数据源的获取的,和单词分割
emitter发射器模块,负责从数据源中获取需要显示的单词,并保存
view显示模块,负责显示单词和其余操作
针对接口编程而非针对实现编程
深拷贝和浅拷贝
浅拷贝:
struct foo
{
int *p;
}
struct foo f1;
f1.p=malloc(20);
struct foo f2=f1;
free(f1.p);
int a=*(f2.p)
上面的程序会出现错误,原因是浅拷贝中f1.p被释放后,f2.p变为失效指针
所以需要做到深拷贝的话,需要在拷贝时为f2.p重新开辟空间后赋值
特殊的:
struct foo
{
int ar[3];
}
struct foo f1={1,2,3};
struct foo f2=f1;
这种情况也为深拷贝,f2的ar改变不会影响f1的ar的值,说明两的结构体变量在拷贝后的空间是不同的。
两种拷贝都具有各自的意义和作用。
int a[3]={1,2,3}
int c=(a+=1)//这种情况无法编译通过,因为数组名是只读的无法被赋值
但是int c=(a+1)//由于数组名a没有被赋值,只是移动了地址位置
.c和.cpp被称为编译单元
头文件保护符
.h文件中使用确保每个头文件只被包含一次
#pragma once
或者
#define 指令把一个名字设定为预处理变量
#ifndef 当且仅当变量未定义时为真
#ifdef 当且仅当变量定义时为真
#endif 一旦检查结果为怎则执行后续操作,直到遇到endif位置
所以先展示模块的划分情况
一共四个模块
下面一一放出各个模块的完成详细过程
1.file_division.h
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include"data_source.h"
//函数指针
typedef void(*fun)(char *word);
//解析函数
void parse(char *file_name,fun fun_name);
file_division.c
#include"file_division.h"
#define start 0
#define in_word 1
#define out_word 2
void parse(char *file_name, fun fun_name)
{
FILE *fp = fopen(file_name, "r");
int i = 0;
if (fp != NULL)
{
char buffer[32] = { 0 };
char *p = buffer;
int state = start;//正常单词
while (!feof(fp))//判断文件 是否读到末尾,如果未读到末尾,则返回假,然后求反
{
char ch = fgetc(fp);
switch (state)
{
case start:
if (isalpha(ch))
{
*p = ch;
state = in_word;
}
else
{
state = out_word;
p--;
}
break;
case in_word:
if (!isalpha(ch))//else keeping in_word
{
if (ch == ' ')
{
state = start;
*p = '\0';
if (fun_name)
fun_name(buffer);
i++;
p = buffer;
p--;
}
else
{
state = out_word;
p = buffer;
p--;
}
}
else *p = ch;
break;
case out_word:
if (isalpha(ch) && *(p - 1) == ' ')
{
*p = ch;
state = in_word;
}//else keeping out_word
else p--;
break;
}
p++;
}
}
fclose(fp);
}
2 data_source.h
#pragma once
#include"file_division.h"
struct dictionary;//结构体声明,而不是定义,是为了数据隐藏不希望用户看见内部数据表示
struct dictionary *download(const char *file_name);//下载数据源
void release();//释放数据源
const char *provide(int index);//提供某个下标的单词
const int words_num();//提供单词总数
data_source.c
#include"data_source.h"
#define MAX_WORDS 256
struct dictionary
{
char *words[MAX_WORDS];
char *file_name;
int num;
}g_dictionary;//结构体变量为全局变量,为了私有不让其他模块看见
//由于结构体变量为全局变量,所以所有元素为0值
int is_same_word(const char *word)
{
int j = 0;
for (; j <g_dictionary.num; j++)
{
if (!stricmp(g_dictionary.words[j], word))//出现相同,则放弃这个单词
{
return -1;
}
}
return j;
}
void add(const char *word)
{
int n = is_same_word(word);
if (n!=-1)
{
g_dictionary.words[n] = _strdup(word);
g_dictionary.num++;
}
}
struct dictionary *download(const char *file_name)//下载数据源,并分割单词
{ release();
parse(file_name,add);
g_dictionary.file_name = _strdup(file_name);//深拷贝的原因是不知道文件名是否由局部变量得来
return &g_dictionary;
}
void release()//释放下载的数据源
{
if (g_dictionary.num == 0)
{
return;
}
for (int i = 0; i < g_dictionary.num; i++)
{
if (g_dictionary.words[i] != NULL)
{
free(g_dictionary.words[i]);
g_dictionary.words[i] = NULL;//赋值为空,方便二次下载数据源正常
}
}
g_dictionary.num = 0;
free(g_dictionary.file_name);//可能有人会认为在file_name未初始化时free不安全,但是结构体为全局变量未初始化时默认为0值
g_dictionary.file_name = NULL;
}
const char *provide(int index)//提供下标对应单词
{
return g_dictionary.words[index];
}
const int words_num()//提供单词总数
{
return g_dictionary.num;
}
3 Launcher.h
#pragma once
#include
struct Word
{
const char *word;
int color;
int killAnim;
int x, y;//列是x,行是y
};
void emit_init(int winWidth, int winHeight);
void emit_release();
void emit_words();
bool emit_hittest(const char *word);
int emit_update();
int emit_word_count();
struct Word* emit_get_word(int index);
Launcher.c
#include"data_source.h"
#include"Launcher.h"
#define MAX_NUM 5
static int times = 0;
struct windows//窗口结构
{
struct Word word[MAX_NUM];
int wide;
int high;
}g_windows;
struct windows sd;
void emit_init(int winWidth, int winHeight)//初始化窗口范围
{
g_windows.wide = winWidth;
g_windows.high = winHeight;
}
void emit_release()//重置当前窗口
{
return;
}
void emit_words()//提取设定数目的随机单词
{
for (int i = 0; i < MAX_NUM; i++)
{
if (g_windows.word[i].word == NULL)
{
g_windows.word[i].color = rand() % 9 + 1;
g_windows.word[i].killAnim = 0;
g_windows.word[i].word = provide(rand() % words_num());
g_windows.word[i].x = rand() % g_windows.wide;
int row = 0;
row = g_windows.word[i].x + strlen(g_windows.word[i].word);//判断单词所在位置加上单词长度的和是否越界显示区域
if (row > g_windows.wide)
{
g_windows.word[i].x -= (row - g_windows.wide);
}
if (times == 0)
{
g_windows.word[i].y = i;
}
else g_windows.word[i].y = 0;
}
}
times += 1;//static变量,使后面的单词都初始化在第一行
}
bool emit_hittest(const char *word)//判断输入的单词是否匹配
{
for (int i = 0; i < MAX_NUM; i++)
{
struct Word *wd = &g_windows.word[i].word;
if (wd->word != NULL&&strcmp(wd->word, word) == 0)
{
wd->killAnim =13;
return true;
}
}
return false;
}
int emit_update()//单词下落跟新
{
int missed = 0;
for (int i = MAX_NUM-1; i >= 0; i--)
{
struct Word *wd = &g_windows.word[i];
if (wd->word != NULL&&wd->killAnim==0)
{
wd->y += 1;
if (wd->y>g_windows.high)
{
wd->word = NULL;
missed += 1;
}
}
}
return missed;
}
int emit_word_count()//返回单词总数
{
return MAX_NUM;
}
struct Word* emit_get_word(int index)//返回某下标的单词
{
return &g_windows.word[index];
}
4.display.h
#pragma once
void view_init();
void view_release();
void view_show_appinfo();
void view_show_score(int killed, int missed);
void view_show_level(int level, int speed);
void view_show_popwords();
void view_show_input_prompt();
void view_show_input_word(const char *word);
int view_emitter_winid();
void view_clear_emitwnd();
void view_do_killanim();
display.c
#include
#include
#include
#include "glConsole.h"
#include "display.h"
#include"Launcher.h"
int about_window = -1;
int score_window = -1;
int emitter_window = -1;
int input_window = -1;
void view_init()
{
int main_window;
int bottom_window1, bottom_window2;
main_window = glmxConsole_CreateWindow(45, 30, "word-pop");
glmxConsole_SplitWindow(main_window, 4, WND_SPLIT_HORIZONTAL, '-', 7, &about_window, &bottom_window1);
glmxConsole_SplitWindow(bottom_window1, 2, WND_SPLIT_HORIZONTAL, '-', 6, &score_window, &bottom_window2);
glmxConsole_SplitWindow(bottom_window2, 19, WND_SPLIT_HORIZONTAL, '-', 5, &emitter_window, &input_window);
}
void view_release()
{
return;
}
void view_show_appinfo()
{
glmxConsole_WndDrawFormatText(about_window, 0, 1, 12, "WORD-POP", TEXT_ALIGN_CENTER);
glmxConsole_WndDrawFormatText(about_window, 0, 2, 12, "> GLIMIX STUDIO <", TEXT_ALIGN_CENTER);
glmxConsole_WndDrawFormatText(about_window, 0, 2, 12, "ZYF", TEXT_ALIGN_RIGHT);
}
void view_show_score(int killed, int missed)
{
char buffer[64];
sprintf_s(buffer, 64, "KILLED: %d", killed);
glmxConsole_WndDrawTextEx(score_window, 0, 0, 13, buffer);
sprintf_s(buffer, 64, "MISSED: %d", missed);
glmxConsole_WndDrawTextEx(score_window, 0, 1, 13, buffer);
}
void view_show_level(int level, int speed)
{
char buffer[64];
sprintf_s(buffer, 64, "LEVEL: %d", level);
glmxConsole_WndDrawFormatText(score_window, 0, 0, 13, buffer, TEXT_ALIGN_RIGHT);
sprintf_s(buffer, 64, "SPEED: %d", speed);
glmxConsole_WndDrawFormatText(score_window, 0, 1, 13, buffer, TEXT_ALIGN_RIGHT);
}
void view_show_input_prompt()
{
glmxConsole_WndDrawTextEx(input_window, 0, 0, 9, ">>");
}
void view_show_input_word(const char *word)
{
glmxConsole_WndDrawTextEx(input_window, 2, 0, 9, word);
}
int view_emitter_winid()
{
return emitter_window;
}
void view_show_popwords()//显示发射器提供单词单词
{
for (int i = 0; i < emit_word_count(); i++)
{
struct Word*p = emit_get_word(i);
if (emit_get_word(i) != NULL&&p->word != NULL)
{
glmxConsole_WndDrawTextEx(emitter_window, p->x, p->y, p->color,p->word);
}
}
}
void view_clear_emitwnd()
{
for (int i = 0; i < emit_word_count(); i++)
{
struct Word*p = emit_get_word(i);
if (p != NULL&&p->word != NULL)
{
int len = strlen(p->word);
char space[32];//栈空间做法
memset(space, ' ', sizeof(char)*32);
space[len] = '\0';
glmxConsole_WndDrawTextEx(emitter_window, p->x, p->y, p->color, space);
/* int len = strlen(p->word); 堆空间做法,但是频繁的开辟小空间会产生内存碎片
char *space = (char*)malloc(len + 1);
for (int j = 0; j < len; j++)
{
space[j] = ' ';
}
space[len] = '\0';
glmxConsole_WndDrawTextEx(emitter_window, p->x, p->y, p->color, space);
free(space);*/
}
}
}
void view_do_killanim()
{
for (int i = 0; i < emit_word_count(); i++)
{
struct Word*p = emit_get_word(i);
if (p->killAnim != 0)
{
if (p->killAnim % 2 == 1)
{
glmxConsole_WndDrawTextEx(emitter_window, p->x, p->y, 7, p->word);
}
else
{
glmxConsole_WndDrawTextEx(emitter_window, p->x, p->y, p->color, p->word);
}
p->killAnim--;
if (p->killAnim == 0)
{
int len = strlen(p->word);
char space[32];
memset(space, ' ', sizeof(char) * 32);
space[len] = '\0';
glmxConsole_WndDrawTextEx(emitter_window, p->x, p->y, p->color, space);
p->word = NULL;
}
}
}
}
5 main.c
#include"data_source.h"
#include"display.h"
#include"Launcher.h"
#include "glConsole.h"
int main()
{
glmxConsole_Init();
view_init();
srand((unsigned)time(NULL));
struct dictionary *dict;
dict = download("D:\\text\\text.txt");
int w, h;
glmxConsole_GetWindowRect(view_emitter_winid(), NULL, NULL, &w, &h);
view_show_appinfo();
//输入缓冲
char word[50] = { 0 };
char *p = word;
char val;
//下落计时器
int time_count = 30;
//成功,失败,等级,速度
int killed=0, missed=0, level=1, speed=40;
while (1)
{
//发射单词
emit_init(w, h - 1);
emit_words(dict);
if (time_count-- == 0)
{
//获取成绩记录
view_show_score(killed, missed);
view_show_level(level, speed);
//下落显示
view_show_popwords();
view_show_input_prompt();
view_clear_emitwnd();
missed=missed+emit_update();
view_show_popwords();
time_count = 25;
}
while (kbhit())//kbhit按下返回非0,这里使用while循环调用getch,这样在多次输入错误答案情况下,可以在循环中快速排除
{
val = getch();
if (val == '\r')
{
if (emit_hittest(word))
{
killed += 1;
}
memset(word, ' ', 50);
view_show_input_word(word);
memset(word, 0, 50);
p = word;
}
else if (val == '\b')
{
p--;
*p = ' ';
view_show_input_word(word);
*p = '\0';
}
else
{
*p = val;
p++;
*p = '\0';
view_show_input_word(word);
}
}
if (killed >= level * 10)
{
level += 1;
speed -= 5;
}
Sleep(speed);
view_do_killanim();
}
return 0;
}
下载资源:
资源