我认为,最重要的是要意识到您的问题是关于一项(显然)曾经被设计为简单而有趣的练习。
但是,那是很久以前的事了,同时硬件和软件都在进步。访问物理内存不再容易。而且,至少对于初学者来说,这样做不再有趣。
因此,该练习的主要优点已被时间无情的熵作用所切断。
但是,对于绝对想要进行此练习的人来说,可以模拟曾经如此容易访问的硬件。透明地进行这样的模拟,以使学生的代码与真实的代码相同,这是相当困难的并且依赖于系统。但是,如果您(学生)只是同意显式调用一些“更新”例程来模拟硬件屏幕更新,那么它更接近于可管理 - 因此,旧练习可以重复使用!:-)
然后可以想象它main
会进行一些初始化,并调用一个doTheDancingDolls
带有参数的例程,该例程可以访问模拟的视频内存,以及前面提到的“更新”功能:
void doTheDancingDolls(
curses::Console& console,
b8000::DisplayMemorySim& memorySim
)
{
do
{
// Whatever - this is where you Do Things to the simulated video memory.
// Then:
memorySim.update( console );
} while( !console.keyboard().keypressIsAvailable() );
console.keyboard().getKey(); // Consume the keypress.
}
...memorySim
对象以某种方式提供了指向模拟视频内存的必要指针——一个与练习中的 B8000000 地址相对应的指针。
这就是主要思想,如何为解决这个为现在过时的设备设计的练习找到一个起点。
再充实一点,这是视频内存模拟的一种可能实现(请注意,这不是解决您在练习中打算做的事情的代码,它只是解决您从未打算做的事情,但可能必须在今天):
文件[b8000.h]
#ifndef B8000_H
#define B8000_H
// A simulation of the early PC's 25*80 color text mode video memory.
// The structure of the memory is INTENTIONALLY not modelled in the
// exposed interface. A student can learn from imposing such structure.
//
// Copyright (c) 2011 Alf P. Steinbach.
//------------------------------------------ Dependencies:
#include "curses_plus_plus.h" // curses::Console
#include <vector> // std::vector
//------------------------------------------ Interface:
namespace b8000 {
typedef unsigned char Byte;
class DisplayMemorySim
{
private:
std::vector< Byte > buf_;
public:
enum { nColumns = 80, nLines = 25 };
DisplayMemorySim(): buf_( 2*nLines*nColumns ) {}
void* bufferPointer() { return &buf_[0]; }
void const* bufferPointer() const { return &buf_[0]; }
void update( curses::Console& console ) const
{
assert( console.nLines() >= nLines );
assert( console.nColumns() >= nColumns );
assert( console.colors().nColors() >= 16 );
for( int y = 0; y < nLines; ++y )
{
for( int x = 0; x < nColumns; ++x )
{
int const iCell = 2*(y*nColumns + x);
Byte const charCode = buf_[iCell];
Byte const colors = buf_[iCell + 1];
Byte const fg = colors & 0xF;
Byte const bg = colors >> 4;
console.colors().setFgBg( fg, bg );
console.putAt( x, y, charCode );
}
}
console.updateScreen();
}
};
} // namespace b8000
#endif
并且curses::Console
该类可能是对curses 库的简单包装,例如……
文件[curses_plus_plus.h]
#ifndef CURSES_PLUS_PLUS_H
#define CURSES_PLUS_PLUS_H
// Sort of minimal C++ interface to the "curses" library.
// Copyright (c) 2011 Alf P. Steinbach.
//------------------------------------------ Dependencies:
#include "curses.h"
#if defined( _MSC_VER ) && defined( __PDCURSES__ )
# pragma comment( lib, "pdcurses.lib" )
#endif
#ifdef _MSC_VER
# pragma warning( disable: 4355 ) // 'this' used in member initializer
#endif
#include <assert.h> // assert
#include <stddef.h> // NULL
//------------------------------------------ Interface:
namespace curses {
class Console;
namespace detail {
template< class Number > inline Number square( Number x ) { return x*x; }
template< class Derived >
struct SingleInstance
{
static int& instanceCount()
{
static int n = 0;
return n;
}
SingleInstance( SingleInstance const& ); // No such.
SingleInstance& operator=( SingleInstance const& ); // No such.
SingleInstance()
{
++instanceCount();
assert(( "can only have one instance at a time", (instanceCount() == 1) ));
}
~SingleInstance() { --instanceCount(); }
};
} // namespace detail
namespace color {
#ifdef _WIN32 // Any Windows, 32-bit or 64-bit.
int const lightness = 0x08; // Windows only. 8
// The portable colors, expressed for Windows systems.
int const black = COLOR_BLACK; // 0
int const blue = COLOR_BLUE; // 1
int const green = COLOR_GREEN; // 2
int const cyan = COLOR_CYAN; // 3
int const red = COLOR_RED; // 4
int const magenta = COLOR_MAGENTA; // 5
int const yellow = COLOR_YELLOW | lightness; // 6 + 8
int const white = COLOR_WHITE | lightness; // 7 + 8
// Windows-specific colors.
int const gray = COLOR_BLACK | lightness;
int const lightBlue = COLOR_BLUE | lightness;
int const lightGreen = COLOR_GREEN | lightness;
int const lightCyan = COLOR_CYAN | lightness;
int const lightRed = COLOR_RED | lightness;
int const lightMagenta = COLOR_MAGENTA | lightness;
int const brown = COLOR_YELLOW & ~lightness; // A bit greenish.
int const lightGray = COLOR_WHITE & ~lightness;
#else
// The portable colors, expressed for non-Windows systems.
int const black = COLOR_BLACK;
int const blue = COLOR_BLUE;
int const green = COLOR_GREEN;
int const cyan = COLOR_CYAN;
int const red = COLOR_RED;
int const magenta = COLOR_MAGENTA;
int const yellow = COLOR_YELLOW;
int const white = COLOR_WHITE;
#endif
} // namespace color
class Colors
: private detail::SingleInstance< Colors >
{
private:
Colors( Colors const& ); // No such.
Colors& operator=( Colors const& ); // No such.
int n_;
int nPairs_;
int rawIndexOfPair0_;
int rawIndexFor( int fg, int bg ) const
{
return bg*n_ + fg;
}
public:
int nColors() const { return n_; }
int nColorPairs() const { return nPairs_; }
int indexFor( int fg, int bg ) const
{
int const rawIndex = rawIndexFor( fg, bg );
return 0?0
: (rawIndex == rawIndexOfPair0_)? 0
: (rawIndex == 0)? rawIndexOfPair0_
: rawIndex;
}
void setColorPair( int i )
{
::color_set( short( i ), NULL ); //attrset( COLOR_PAIR( i ) );
}
void setFgBg( int fg, int bg )
{
setColorPair( indexFor( fg, bg ) );
}
Colors( Console& )
{
::start_color(); // Initialize color support.
// Although these look like global constants, they're global variables
// that are initialized by the call to Curses' `start_color` (above).
n_ = ::COLORS; nPairs_ = ::COLOR_PAIRS;
assert( detail::square( n_ ) <= nPairs_ ); // Our requirement.
// Obtain curses' default colors, those are at color pair index 0.
{
short fg0 = -1;
short bg0 = -1;
::pair_content( 0, &fg0, &bg0 );
assert( fg0 != -1 );
assert( bg0 != -1 );
rawIndexOfPair0_ = rawIndexFor( fg0, bg0 );
}
// Initialize color pair table to support finding index for given colors.
// The color pair at index 0 can't be specified (according to docs),
// so trickery is required. Here like swapping index 0 to elsewhere.
for( int fg = 0; fg < n_; ++fg )
{
for( int bg = 0; bg < n_; ++bg )
{
int const i = indexFor( fg, bg );
if( i == 0 ) { continue; } // It's initialized already.
::init_pair( short( i ), short( fg ), short( bg ) );
}
}
}
};
class Keyboard
: private detail::SingleInstance< Keyboard >
{
private:
WINDOW* pCursesWin_;
bool isBlocking_;
int bufferedKeypress_;
bool hasBufferedKeypress_;
void setBlocking( bool desiredBlocking )
{
if( isBlocking_ != desiredBlocking )
{
::nodelay( pCursesWin_, !desiredBlocking );
isBlocking_ = desiredBlocking;
}
}
public:
int getKey()
{
if( hasBufferedKeypress_ )
{
hasBufferedKeypress_ = false;
return bufferedKeypress_;
}
setBlocking( true );
return ::getch();
}
bool keypressIsAvailable()
{
if( hasBufferedKeypress_ ) { return true; }
setBlocking( false );
int const key = ::getch();
if( key == ERR ) { return false; }
hasBufferedKeypress_ = true;
bufferedKeypress_ = key;
return true;
}
Keyboard( WINDOW& pCursesWin )
: pCursesWin_( &pCursesWin )
, isBlocking_( true )
, hasBufferedKeypress_( false )
{
::keypad( pCursesWin_, true ); // Assemble multi-character sequences into key symbols.
::nodelay( pCursesWin_, false );
assert( isBlocking_ == true );
}
~Keyboard()
{
setBlocking( true );
}
};
class Console
: private detail::SingleInstance< Console >
{
private:
::WINDOW* pCursesWin_;
Colors colors_;
Keyboard keyboard_;
Console( Console const& ); // No such.
Console& operator=( Console const& ); // No such.
static ::WINDOW* beginWin()
{
return ::initscr();
}
public:
// At least with pdcurses in Windows, these numbers are for the
// console window, i.e. not for its underlying text buffer.
int nColumns() const { return ::getmaxx( pCursesWin_ ); }
int nLines() const { return ::getmaxy( pCursesWin_ ); }
Colors& colors() { return colors_; }
Colors const& colors() const { return colors_; }
Keyboard& keyboard() { return keyboard_; }
Keyboard const& keyboard() const { return keyboard_; }
void putAt( int x, int y, char const s[] )
{
::mvaddstr( y, x, s );
}
void putAt( int x, int y, char c )
{
::mvaddch( y, x, c );
}
void updateScreen() { ::refresh(); }
Console()
: pCursesWin_( beginWin() )
, colors_( *this )
, keyboard_( *pCursesWin_ )
{
::cbreak(); // Immediate input (no line buffering).
::noecho(); // No input echo.
}
~Console()
{
::endwin();
}
};
} // namespace curses
#endif
为了驱动这一切,在主程序中,您将检查终端窗口(Windows“控制台窗口”)是否足够大,等等:
文件[main.cpp]
#include "string_util.h" // strUtil::S
#include "b8000.h" // b8000::DisplayMemorySim
#include "curses_plus_plus.h" // curses::Console
#include <algorithm> // std::max
#include <assert.h> // assert
#include <iostream> // std::cerr, std::endl
#include <stdexcept> // std::runtime_error, std::exception
#include <stdlib.h> // EXIT_SUCCESS, EXIT_FAILURE
void doTheDancingDolls(
curses::Console& console,
b8000::DisplayMemorySim& memorySim
)
{
do
{
// Whatever - this is where you Do Things to the simulated video memory.
// The following three lines are just to see that something's going on here.
using stringUtil::S;
static int x = 0;
console.putAt( 30, 5, S() << "I have now counted to " << ++x << "..." );
// Then:
//memorySim.update( console );
} while( !console.keyboard().keypressIsAvailable() );
console.keyboard().getKey(); // Consume the keypress.
}
bool throwX( std::string const& s ) { throw std::runtime_error( s ); }
void cppMain()
{
using std::max;
using stringUtil::S;
curses::Console console;
enum
{
w = b8000::DisplayMemorySim::nColumns,
h = b8000::DisplayMemorySim::nLines
};
(console.colors().nColors() >= 16)
|| throwX( "The console window doesn't support 16 colors." );
(console.nColumns() >= w)
|| throwX( S() << "The console window has less than " << w << " columns." );
(console.nLines() >= h)
|| throwX( S() << "The console window has less than " << h << " lines." );
namespace color = curses::color;
console.colors().setFgBg( color::lightRed, color::yellow );
console.putAt( 30, 0, " The Dancing Dolls! " );
console.putAt( 30, 1, S() << " In " << h << "x" << w << " color mode." );
console.putAt( 30, 3, S() << " Press ANY key to start... " );
console.keyboard().getKey();
console.putAt( 30, 3, S() << " Press ANY key to stop... " );
b8000::DisplayMemorySim displayMemorySim;
doTheDancingDolls( console, displayMemorySim );
}
int main()
{
using namespace std;
try
{
cppMain();
return EXIT_SUCCESS;
}
catch( exception const& x )
{
cout << "!" << x.what() << endl;
}
return EXIT_FAILURE;
}
这篇文章有点长,所以我没有为这S()
件事添加代码(代码不多,但仍然如此)。
无论如何,这应该为实验提供一个起点。
免责声明:我只是把它拼凑在一起。它没有经过广泛的测试。例如,我可能误解了 curses 库的键盘处理 -。我希望它也能在 *nix-land 中工作,但我不知道。
干杯&hth.,