在 Phase1 中,我是通过调用
system(("ps auxc > " + LogFile).c_str());
的方式获取进程信息。
在助教老师的建议下,修改为了手动读取 /proc
进行相应计算的方式:
// process.cpp
int CalcuProcTime(const Tokens& tok) {
return StrToInt(tok.GetArgs(13)) + // user mode jiffies
StrToInt(tok.GetArgs(14)); // kernel mode jiffies
}
void Process::CalculateRatio(int DurTotal, int FullSiz) {
Tokens line;
std::ifstream ProcInput(InputFile);
ProcInput >> line;
int CurProcTime = CalcuProcTime(line);
RatioCPU = std::floor(1000.0 * (CurProcTime - PreProcTime) / DurTotal) / 10.0;
RatioMEM = std::floor(1000.0 * StrToInt(line.GetArgs(22)) / 8192 / FullSiz) / 10.0;
PreProcTime = CurProcTime;
}
Process::Process(int var) {
Pid = var;
std::ostringstream oss;
oss << "/proc/" << Pid << "/stat";
InputFile = oss.str();
Tokens line;
std::ifstream ProcInput(oss.str());
ProcInput >> line;
CommandStr = line.GetArgs(1);
PreProcTime = CalcuProcTime(line);
}
// process_list.cpp
void ProcessList::UpdateList(bool LogFlag) {
/* using a few functions provided by the kernel
to read the dirent */
struct dirent* ent;
DIR* ProcDir = opendir("/proc");
std::vector<int> PidList;
if (ProcDir == NULL) {
std::cerr << red << "Error opening /proc" << std::endl;
return;
}
while (ent = readdir(ProcDir)) {
if (isdigit(*ent->d_name)) {
PidList.push_back(strtol(ent->d_name, NULL, 10));
}
}
closedir(ProcDir);
for (auto pid: PidList) {
if (!FindProcess(pid, false)) {
lst.push_back(Process(pid));
}
}
for (auto iteproc = lst.begin(); iteproc != lst.end(); ) {
auto itepid = std::lower_bound(
PidList.begin(), PidList.end(), iteproc->GetPid());
if (*itepid != iteproc->GetPid()) {
if (iteproc + 1 != lst.end()) {
*iteproc = lst.back();
}
lst.pop_back();
} else iteproc++;
}
std::this_thread::sleep_for(
std::chrono::microseconds(20000));
int CurTotalTime = ReadCPUTotal(),
DurTotalTime = CurTotalTime - PreTotalTime;
for (auto&& proc: lst) {
proc.CalculateRatio(DurTotalTime, MemTotal);
}
PreTotalTime = CurTotalTime;
SortCol(DefaultCol, Defaultinc);
if (!LogFlag) return;
auto CurTime = time(nullptr);
std::ostringstream oss;
oss << "Logs/" << std::put_time(gmtime(&CurTime), "%F-%T") << ".log";
std::ofstream LogOut(oss.str());
LogOut << "PID" << tab << "%CPU" << tab
<< "%MEM" << tab << "COMMAND" << std::endl;
for (auto proc: lst) {
LogOut << proc << std::endl;
}
}
其中,计算进程的内存占用则读取相应信息计算比例即可。
而对于进程的 CPU 占用时,我是通过考察一个时间段内每个进程占用的机时、总机时,计算比例得到的。
可以发现,通过这种方式进行进程 CPU 占用计算,时间段相对较长时,结果就会相对准确。
具体地,这里选取了 Time_lastupdate
\(\sim\) Time_thisupdate + std::chrono::micorseconds(20000)
进行计算,在运行结果上比较接近于 ps
。实现上,这里利用了之前实现的 Tokens
类来管理 /proc/[pid]/stat
的相应信息。解析 stat
文件中的信息时,我参考了内核的文档。
对代码的结构进行了一些小调整:
其中额外添加了四个文件,Item/movingobj.h
与 Runtime/birdgame.h
在 movingobj.h
中,主要管理游戏中的各种对象实体。birdgame.h
中实现游戏运行时的相应功能。
// movingobj.h
#pragma once
#include <set>
#include <string>
#include <utility>
#include <functional>
typedef std::pair<int, int> Node;
class MovingObj {
protected:
std::set<Node> Obj;
void ObjModify(std::function<Node(Node)> func);
public:
bool FindCoo(Node x, bool DeleteFlag = false);
bool CheckBoardCrash(int H, int W, bool FlagAll = 0);
bool CheckObjCrash(MovingObj& rhs, bool DeleteFlag = false);
// virtual void AutoMove() = 0;
virtual void PrintUnit() = 0;
Node GetCorner(bool FindXMax);
void AddNode(Node x);
};
class Bird: public MovingObj {
public:
Bird() {}
Bird(Node x);
void AutoMove();
void PrintUnit();
void ControlMove(int delta = 2);
};
class Bullet: public Bird {
public:
Bullet(Node x);
void PrintUnit();
void AutoMove();
};
class Pipe: public MovingObj {
protected:
const int Width = 5;
bool PipeType;
public:
Pipe() {};
Pipe(int x, int y, int h, bool type);
void PrintUnit();
void AutoMove(int H);
void BackMove();
bool GetType();
};
class FallPipe: public Pipe {
public:
FallPipe(Pipe& pip, Node x);
void FixDown(Pipe& pip);
void AutoMove();
};
// movingobj.cpp
#include <map>
#include <utility>
#include <iostream>
#include "movingobj.h"
#include "../Utils/iostaff.h"
void MovingObj::AddNode(Node x) {
Obj.insert(x);
}
void MovingObj::ObjModify(std::function<Node(Node)> func) {
std::set<Node> NewObj;
for (auto itm: Obj) {
NewObj.insert(func(itm));
}
Obj = NewObj;
}
// set DeleteFlag = true to delete what have found
bool MovingObj::FindCoo(Node x, bool DeleteFlag) {
if (DeleteFlag) {
if (Obj.count(x)) {
return Obj.erase(x), true;
}
return false;
}
return Obj.count(x);
}
// FlagAll=1 check forall, FlagAll=0 check exsiting
bool MovingObj::CheckBoardCrash(int H, int W, bool FlagAll) {
for (auto itm: Obj) {
if (0 <= itm.first && itm.first <= W && 0 <= itm.second && itm.second <= H) {
if (FlagAll) return false;
} else {
if (!FlagAll) return true;
}
}
return FlagAll;
}
/* FindXMax=true to find the Node with maximum x-coo, minimum y-coo
FindXMax=false to find the Node with minimum x-coo, minimum y-coo
*/
Node MovingObj::GetCorner(bool FindXMax) {
auto func = [&](Node lhs, Node rhs) {
if (lhs.first == rhs.first) {
return (lhs.second < rhs.second)? lhs: rhs;
}
return ((lhs.first < rhs.first) ^ FindXMax)? lhs: rhs;
};
Node res = *Obj.begin();
for (auto itm: Obj) {
res = func(res, itm);
}
return res;
}
/* DeleteFlag=true delete the crashed Node when checking and return true when hit through
DeleteFlag=false return true when crashing checked.
*/
bool MovingObj::CheckObjCrash(MovingObj& rhs, bool DeleteFlag) {
if (DeleteFlag) {
bool ret = false;
for (auto itm: Obj) {
if (rhs.Obj.count(itm)) {
rhs.Obj.erase(itm);
ret |= !(rhs.Obj.count(std::make_pair(itm.first + 1, itm.second)));
}
}
return ret;
}
for (auto itm: Obj) {
if (rhs.Obj.count(itm)) {
return true;
}
}
return false;
}
Bird::Bird(Node x) {
AddNode(x);
AddNode(std::make_pair(x.first + 1, x.second));
}
void Bird::AutoMove() {
ObjModify([](Node x) {
return Node(x.first, x.second - 1);
});
}
void Bird::PrintUnit() {
std::cout << "??";
}
void Bullet::PrintUnit() {
std::cout << "??";
}
void Pipe::PrintUnit() {
std::cout << backg << " " << endo;
}
void Bird::ControlMove(int delta) {
ObjModify([&](Node x) {
return Node(x.first, x.second + delta);
});
}
Bullet::Bullet(Node x): Bird::Bird(x) {}
void Bullet::AutoMove() {
ObjModify([](Node x) {
return Node(x.first + 1, x.second);
});
}
void Pipe::AutoMove(int H) {
const static int tx[] = {1, 0, -1, 0},
ty[] = {0, 1, 0, -1};
int ColorCnt = 0, SpecifyCode = 0;
std::map<Node, int> HashCheck;
std::function<void(Node)> DfsConnected = [&](Node cur) {
if (cur.second < 0 || cur.second > 23) {
SpecifyCode = ColorCnt;
}
HashCheck[cur] = ColorCnt;
for (int i = 0; i != 4; ++i) {
Node nex = std::make_pair(cur.first + tx[i], cur.second + ty[i]);
if (HashCheck[nex]) continue;
if (FindCoo(nex, false)) {
DfsConnected(nex);
}
}
};
for (auto unit: Obj) {
if (HashCheck[unit]) continue;
ColorCnt++;
DfsConnected(unit);
}
ObjModify([&](Node x) {
return Node(x.first - 1,
x.second - (HashCheck[x] != SpecifyCode));
});
}
void FallPipe::AutoMove() {
ObjModify([](Node x) {
return Node(x.first - 1, x.second - 2);
});
}
Pipe::Pipe(int x, int y, int h, bool type) {
PipeType = type;
for (int i = 0; i != h; ++i) {
for (int j = 0; j != Width; ++j) {
AddNode(std::make_pair(x + j, y + i));
}
}
}
bool Pipe::GetType() {
return PipeType;
}
// To cut a part of [Pipe& pip] from the last hitted [Node x]
FallPipe::FallPipe(Pipe& pip, Node Corner) {
const int delta = pip.GetType()? -1: +1;
int CooY = Corner.second + delta;
while (true) {
bool FoundNull = true;
for (int i = -1; i <= Width; ++i) {
Node CurCoo = std::make_pair(Corner.first - i, CooY);
if (pip.FindCoo(CurCoo, true)) {
AddNode(CurCoo);
FoundNull = false;
}
}
if (FoundNull) break;
CooY += delta;
}
}
void FallPipe::FixDown(Pipe& pip) {
for (auto itm: Obj) {
pip.AddNode(itm);
}
}
每个对象都使用了一个 std::set<std::pair<int,int> >
维护它所包含的点的坐标信息。
小鸟与子弹的形象使用了 unicode 的字符画,管道的形象使用了一个绿色的空格(基于已经实现的 Form
类),使用虚函数 PrintUnit
管理。
每个对象都有一个同名的 AutoMove()
方法,在运行时刻不断变换坐标位置。为了便于实现,使用了一个高阶函数抽象 ObjModify
,可以按照传入的变化函数更改整个 set
的值。
其中 Pipe
类的 AutoMove()
比较复杂。我使用了一个线性复杂度的搜索算法寻找 Pipe
类的联通块,并使孤立的联通快下落。这一功能是为了弥补在多个子弹击中同一个 FallPipe
时导致的 bug 。稍后我们将在 Demo 中看见。
游戏要求实现一个实时按键反馈的运行机制,因此这里我额外实现了一个 Keyboard
类,实现类似与 Windows 下 kbhit()
与 getch()
的功能。
// iostaff.h
#include <unistd.h>
class Keyboard {
public:
Keyboard();
~Keyboard();
bool kbhit();
char getch();
private:
struct termios initial_settings, new_settings;
int peek_character;
};
// iostaff.cpp
Keyboard::Keyboard() {
tcgetattr(0, &initial_settings);
new_settings = initial_settings;
new_settings.c_lflag &= ~ICANON;
new_settings.c_lflag &= ~ECHO;
new_settings.c_lflag &= ~ISIG;
new_settings.c_cc[VMIN] = 1;
new_settings.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &new_settings);
peek_character = -1;
}
Keyboard::~Keyboard() {
tcsetattr(0, TCSANOW, &initial_settings);
}
bool Keyboard::kbhit() {
unsigned char ch;
int nread;
if (peek_character != -1) return true;
new_settings.c_cc[VMIN] = 0;
tcsetattr(0, TCSANOW, &new_settings);
nread = read(0, &ch, 1);
new_settings.c_cc[VMIN] = 1;
tcsetattr(0, TCSANOW, &new_settings);
if (nread == 1) {
peek_character = ch;
return true;
}
return false;
}
char Keyboard::getch(){
char ch;
if (peek_character != -1) {
ch = peek_character;
peek_character = -1;
} else {
read(0, &ch, 1);
}
return ch;
}
这里也是使用了一些内核提供的现有函数实现的。
接下来考虑实现 Board
类:
// birdgame.h
#pragma once
#include <list>
#include "../Item/movingobj.h"
#include "../Item/process_list.h"
class Board {
private:
const int BoardHeight, BoardWidth, PipeNum;
bool GameOverFlag;
int GameScore, Magazine;
ProcessList& pst;
std::list<Pipe*> PipeList;
std::list<FallPipe*> FallList;
std::list<Bullet*> BulletList;
Bird* brd;
int RatiobaseRand(double ratio);
void PrintGameBoard();
void UpdatePosition();
void CheckCrash();
void UpdatePipe();
public:
Board(ProcessList& prl);
~Board();
void GameLoop();
};
// birdgame.cpp
#include <thread>
#include <cstdlib>
#include <condition_variable>
#include "birdgame.h"
#include "../Item/process.h"
#include "../Utils/iostaff.h"
#include "../Item/process_list.h"
Board::Board(ProcessList& prl):
BoardHeight(23), BoardWidth(100), PipeNum(18), pst(prl) {
Magazine = 5;
GameScore = GameOverFlag = false;
brd = new Bird(std::make_pair(2, 10));
UpdatePipe();
}
Board::~Board() {
for (auto blt: BulletList) delete blt;
for (auto pip: PipeList) delete pip;
for (auto pip: FallList) delete pip;
delete brd;
}
int Board::RatiobaseRand(double ratio) {
static int res = 0;
int Modu = BoardHeight * 0.65;
(res += ratio * 100) %= Modu;
for (int i = 0; i != 20; ++i) {
(res = res * 277 + 13) %= Modu;
}
return int(res + BoardHeight * 0.05);
}
void Board::UpdatePipe() {
while (PipeList.size() < PipeNum) {
pst.UpdateList(false);
auto tmprocess = pst.GetProcess(0);
int randHeight = RatiobaseRand(tmprocess.GetRatioCPU()),
UpperHeight = BoardHeight * 0.3 + randHeight,
currx = (PipeList.empty())? 30: (PipeList.back()->GetCorner(true)).first + 14;
/* set -1 here to remain a root underground
in case the bullet hit through CooY=0, the problem caused by the falling pipe
same for BoardHeight - UpperHeight + 2 here */
PipeList.push_back(new Pipe(currx, -1, randHeight + 1, false));
PipeList.push_back(new Pipe(currx, UpperHeight, BoardHeight - UpperHeight + 2, true));
}
while (PipeList.front()->CheckBoardCrash(BoardHeight, BoardWidth, true)) {
delete PipeList.front();
PipeList.pop_front();
Magazine += ((++GameScore) % 10 == 0);
}
}
void Board::UpdatePosition() {
brd->AutoMove();
for (auto pip: PipeList) pip->AutoMove(BoardHeight);
for (auto pip: FallList) pip->AutoMove();
for (auto blt: BulletList) blt->AutoMove();
}
void Board::CheckCrash() {
if (brd->CheckBoardCrash(BoardHeight, BoardWidth)) {
GameOverFlag = true;
return;
}
auto PipeCheck = [&](Pipe* pip, bool FlagFalling) {
if (GameOverFlag) return;
if (brd->CheckObjCrash(*pip)) {
GameOverFlag = true;
return;
}
if (!FlagFalling) {
for (auto ite = FallList.begin(); ite != FallList.end(); ) {
auto TmpCoo = (*ite)->GetCorner(true);
if (pip->FindCoo(std::make_pair(TmpCoo.first, TmpCoo.second - 1))) {
(*ite)->FixDown(*pip);
delete (*ite);
ite = FallList.erase(ite);
} else ite++;
}
}
for (auto blt: BulletList) {
if (blt->CheckObjCrash(*pip, true)) {
FallList.push_back(new FallPipe(*pip, blt->GetCorner(true)));
}
}
};
for (auto pip: PipeList) PipeCheck(pip, false);
for (auto fall_pip: FallList) PipeCheck(fall_pip, true);
for (auto ite = BulletList.begin(); ite != BulletList.end(); ) {
if ((*ite)->CheckBoardCrash(BoardHeight, BoardWidth)) {
delete (*ite);
ite = BulletList.erase(ite);
} else ite++;
}
}
void Board::PrintGameBoard() {
system("clear");
PrintTableline(BoardWidth);
for (int y = BoardHeight - 1; y >= 0; --y) {
for (int x = 0; x < BoardWidth; ++x) {
bool FoundFlag = false;
auto CurCoo = std::make_pair(x, y);
auto PrintObj = [&](MovingObj* itm, bool FlagBird = false) {
if (FoundFlag) return;
if (itm->FindCoo(CurCoo)) {
itm->PrintUnit();
FoundFlag = true;
if (FlagBird) ++x;
}
};
PrintObj(brd, true);
for (auto pip: PipeList) PrintObj(pip);
for (auto pip: FallList) PrintObj(pip);
for (auto blt: BulletList) PrintObj(blt, true);
if (!FoundFlag) std::cout << " ";
}
std::cout << std::endl;
}
PrintTableline(BoardWidth);
std::cout << green << bold << "Score" << endo << ": " << GameScore / 2 << tab
<< green << bold << "Magazine" << endo << ": " << Magazine << std::endl;
PrintTableline(BoardWidth);
}
void Board::GameLoop() {
/* create an auto-refreshing thread to got a flat display interface
using a condition_variable to
avoid the interruption of auto-refreshing thread,
(caused by user input)
which can result in garbled print
*/
std::condition_variable cv;
std::mutex ThreadMute;
bool ReadyInput = true;
auto InputThread = std::thread([&] {
Keyboard kbd;
while (true) {
if (GameOverFlag) break;
std::unique_lock<std::mutex> LockThread(ThreadMute);
cv.wait(LockThread, [&]{
return ReadyInput;
});
if (kbd.kbhit()) {
char cht = toupper(kbd.getch());
if (cht == ‘W‘) brd->ControlMove(2);
else if (cht == ‘E‘) brd->ControlMove(4);
else if (cht == ‘Q‘) GameOverFlag = true;
else if (cht == ‘L‘ && Magazine > 0) {
BulletList.push_back(new Bullet(brd->GetCorner(true)));
BulletList.back()->AutoMove();
Magazine--;
}
}
LockThread.unlock();
}
kbd.~Keyboard();
});
while (true) {
ReadyInput = false;
if (GameOverFlag) {
cv.notify_one();
break;
}
UpdatePipe();
UpdatePosition();
CheckCrash();
PrintGameBoard();
ReadyInput = true;
cv.notify_one();
std::this_thread::sleep_for(
std::chrono::milliseconds(100));
}
InputThread.join();
}
这里也是用到了一个多线程的技术,分别实现用户输入与界面打印的功能。特别地还用到了 condition_variable
,避免了用户输入中断界面打印时出现的乱码情况。213 行的 std::this_thread::sleep_for(std::chrono::milliseconds(100));
则保证了游戏运行时能有一个非常稳定的帧率。
在打印游戏界面时,用了一个多态抽象,调用各对象动态绑定的 PrintUnit()
方法来简化实现。
此外,考虑到在刚刚实现的计算 CPU 占用率方法。在游戏运行时刻,两次更新时间间隔较长。这导致运算结果会相对稳定,影响游戏体验。因此使用了线性迭代的方法提高了随机结果的方差,即 RatiobaseRand()
的内容。


Task Manager for Linux: Phase 2
原文:https://www.cnblogs.com/Shimarin/p/14722542.html