精华内容
下载资源
问答
  • mybatisPlus怎样实现按天或按月查询数据??sql语句怎样写? mybatisPlus怎样实现按天或按月查询数据??sql语句怎样写
  • 怎样写好代码

    千次阅读 2018-08-08 14:28:28
    怎样写好代码 本文是《201 principles of software development》和《代码简介之道》关于如何写好代码部分的感悟。 一 让变量和注释清晰可读 好的变量命名与注释是保证代码简洁、具有可读性性的第一步。 能在...

    怎样写好代码

    本文是《201 principles of software development》和《代码简介之道》关于如何写好代码部分的感悟。


    一 让变量和注释清晰可读


    好的变量命名与注释是保证代码简洁、具有可读性性的第一步。
    能在变量命名和函数命名中清楚体现出代码的意义的话,就不必要写冗余的注释。在python这样的语言中,强调简介和良好的可阅读性,最好做到代码即文档。
    Simple is better than complex. Readability counts.
    有这样一个小例子:

    d = 0 #流逝的时间,按天计算

    这个变量命名貌似进行了详细的注释,但是不是一个好的命名。如果在另外的地方出现这个变量,读者可能早忘了它的注释意义。
    把这个变量命名为elapsedDays或者elapsedTimeInDays才符合代码即文档的思想。
    在变量命名是要清楚的表明这个变量的意义。注释应该阐述你的设计思想、有意义的说明、对读者的提示等,不要放啰嗦的法律申明。

    关于清晰的一点体会是尽量显示的用代码表达你的意思,建议少用匿名函数,下面可以做些对比,这是功能相同的代码:

    symbols = 'abd&^*'
    beyond_ascii = list(filter(lambda c: c > 50, map(ord, symbols)))
    beyond_ascii = [ord(s) for s in symbols if ord(s) > 50]

    >>> beyond_ascii
    [97, 98, 100, 94]
    显示的表达让代码更清晰。Explicit is better than implicit.

     

    二 简洁优美


    代码简洁优美我们追求的下一个层次。下面是一个简单的统计单词出现次数的字典。下面有2种写法,功能相同。
    但第二种写法更简洁优美,并且执行效率更高。

    if word in count_dict:
        num = count_dict[word]
        num += 1
        count_dict[word] = num
    else:
        count_dict[word] = 1
    count_dict[word]=1 + count_dict.setdefault(word,0)

     

    三 暴露bug,快速失败


    在程序设计的时候,不要让bug溜走,要快速失败fail fast。
    让程序快速崩溃,这听起来似乎违反直接。其实不然,
    让程序快速失败可以让开发者迅速的得到反馈,从而改进代码。如果开发者没有注意到一些潜在的问题,
    程序可能处于一种不正常的状态,而表面上看起来还流畅运行,这其实增加了程序的危险性,同时让bug更难调试。
    所以在写try except时,try的代码模块要尽量小,一般只有一行。
    不要try一大堆代码,这不符合尽量暴露bug的原则。

     

    四 代码中没有奇迹


    如果项目延后了,只剩下2天时间。这两天时间只够进行各程序模块开发,而没有时间联调测试。
    要不要赌一把?争取2天开发完成,联调没有bug,成功运行!
    代码开发中没有奇迹,错误难免且很多。充分的联调测试必不可少。
    期待奇迹赌一把,反而会在最后关头把事情搞砸。还不如提前通知相关方,老实告诉项目延后了。

    总结,个人体会如果要让代码更好,一定让变量和注释清晰可读,尽量让代码简洁优美。在代码开发测试中没有奇迹,一定要充分暴露bug,充分测试。

    展开全文
  • 怎样写模拟器

    千次阅读 2011-08-14 10:13:56
    怎样写模拟器 作者: Nikolas Gavalas Email: thundermahoney@hotmail.com 怎样写模拟器 简介 阶段 读入 运行 获得 ROM 的操作码 执行操作码 执行中断 读写内存 做循环任务 视频模拟 声音模
    怎样写模拟器
    作者: Nikolas Gavalas
    Email: thundermahoney@hotmail.com


    怎样写模拟器
    简介
    阶段
    读入
    运行
    获得 ROM 的操作码
    执行操作码
    执行中断
    读写内存
    做循环任务
    视频模拟
    声音模拟
    优化
    课程收获
    我的模拟器
    参考文献






    +------+
    | 简介 |
    +------+


    模拟你喜欢的系统来玩游戏比你想象的要简单 (简单程度依赖于你尝试的模拟的系统). 本文档旨在给你一个构建模拟器的概要. 为了帮知你更好的理解概念,我将给出一些 NES 模拟器的明确的例子 (这个模拟器是我这学期我在 Long Beach 的 California State University 写的).


    本文档假设读者对 C++ 和 汇编语言 (Assembly) 熟悉.




    +------+
    | 阶段 |
    +------+


    与模拟有关的阶段其实只有两个. 它们是读入游戏或程序 (ROM) 和运行 ROM. 你的大多数工作都是在运行阶段. 读入阶段非常直截了当.




    +------+
    | 读入 |
    +------+


    你要做的第一件事就是把 ROM 整个读入到内存中. 怎样读入 ROM 到内存中依赖于你所要模拟的系统. 我将把 NES 的读入阶段作为一个很好的例子来解释.


    在 NES 的 cartridge 上有两种内存. 程序内存的真正的指令是被 CPU 执行. 角色内存用来显示图形的是 8x8 马赛克块. 对于 NES,我将全部程序内存读入一个数组,将全部角色内存读入另外一个数组.


    NES 只有 32k 程序内存和 8k 角色内存. 这样就限制了你在如此有限的内存中能够制作出的游戏的类型和品质. 为了解决这个问题,一些 cartridges 拥有内存镜像 (mapper). 内存镜像被用来在 cartridge 中的内存和 NES 中的内存作交换. 镜像交换在数据被写入 cartridge 的一个寄存器 (register) 时发生. 这些寄存器告诉 cartridge 哪段内存被使用.


    一旦你将全部的 cartridge 读入内存,你就可以使交换非常快速. 比真的将内存拷入拷出其他数组要快,你只需要改变一个指针. 例如,如果你需要交换程序内存中的第一段,你只需要需要改变指向 cartridge 上使用中的内存段的指针的值.


         pPRGROMBank1 = &abyPRGROM[PRGROM_BANKSIZE*wBankNumber];


    现在,我们已经有了一些关于如何将 ROM 读入内存和如何处理内存镜像的背景知识了,下面我将要解释在 ROM 被读入后应当做些什么.


    你首先需要建立 PRG-ROM 内存段,方法类似通过内存镜像处理内存交换. NES 有两段程序内存,每一段 16k. 依赖于所使用的内存段,你需要对指针适当的赋值. 在下面的例子中,wBankNumber1 和 wBankNumber2 是使用的每段 PRG-ROM 区域的号码.


         pPRGROMBank1 = &abyPRGROM[PRGROM_BANKSIZE*wBankNumber1];
         pPRGROMBank1 = &abyPRGROM[PRGROM_BANKSIZE*wBankNumber2];


    下一件事情是你需要建立指向程序开始地址的 Instruction pointer (指令指针). 由于我对其他系统没有太多经验,我将解释 NES 是如何工作的. 在 PRG-ROM (0FFFEh) 的一个指定地址,有保存指令指针的初始值 (starting value) 的字. 你需要拷贝那个字到说明指针,然后执行指令指针指向的地方.




    +------+
    | 运行 |
    +------+


    运行 ROM 可以被分成几个部分. 为了运行 ROM 你必须:


         1. 获得 ROM 的操作码
         2. 执行操作码
         3. 执行 Interrupts (中断)
         4. 读/写内存
         5. 做循环任务




    +-------------------+
    | 获得 ROM 的操作码 |
    +-------------------+


    执行 ROM 的第一件事就是获得操作码. 你获得 CPU 的指令指针 (IP) 指向的字节. 这个字节就是你的操作码. 依赖于操作码,你也许要获得操作数 (operands) 或者你也许要为了执行部分来保存这部分. 对于一个8位处理器 (8-bit processer),代码可以像下面的.


         byOpcode = pPRGROMBank1[wInstructionPointer];


    CPU.pPRGROMBank1 指向的内存段就是在一开始被读入的,wInstructionPointer 就是说明指针. 在上述的执行后,byOpcode 将会以数字的方式 (numeric value) 保存你的操作码.




    +------------+
    | 执行操作码 |
    +------------+


    现在你已经有了操作码 byte,你需要对它解码和执行. 如果你要模拟的系统是8位的,解码就非常容易. 如果是16位系统,我想这也不是太难. 但是,为了使这个处理过程更简单,并且指出重点,就让我们继续坚持以8位处理器为例子.


    你的操作码 字只是一个指令. 例如,对于 6502 (6502是 NES 的 CPU -- 译注),值 29h 是一个 AND 指令的表格. 既然这样,它就立即执行 AND,这也就意味着它将在操作码 byte 之后累加器 (Accumulator register) 与 immediate value 作 and 运算. 由于这个,如果你写一个 6502 的模拟器,你需要在 AND操作码 byte 之后在累加器上执行一个 AND. 然后你需要增加 IP,还有加或者减去执行那个特别指令所花去的时钟周期的数目.


    下面是 AND 操作的代码. 其他的操作就像这个差不多.


    switch (byOpcode)
    {
          case 0x29:
                byOperand1 = pPRGROMBank1[wInstructionPointer+1];
                A &= byOperand1;
                break;
          .
          .
          .
    }
    wInstructionPointer += wNumBytesForOpcode;
    dwCPUCycles -= dwNumCyclesForOpcode;




    +----------+
    | 执行中断 |
    +----------+


    中断可以使硬件中断也可以是软件中断. 当某些硬件情况发生时,硬件中断发生. 例如,当 VBlank 发生时,一个中断就发生. 当程序中特定的指令执行时,软件中断就发生. 对于 NES,这种状况发生在 BRK 指令被执行.


    当中断发生时,NES 保存指令指针和标志寄存器 (Flag register),然后再特定的内存区域内查找一个新的值赋给指令指针. 每个终端都有它们被指派的地址,所以 NES 从那个位置获得值然后赋给指令指针. 然后程序从这个点继续执行.


    NES 有三种中断. 指令指针的新的值依赖于中断发生的内存地址.


         FFFAh = NMI (VBlank)
         FFFCh = RESET
         FFFEh = IRQ/BRK (software)


    所以当 VBlakn 发生时,CPU 将:


         1. 将指令指针当前值推入栈.
         2. 将标志寄存器当前值推入栈.
         3. 获得位于 FFFAh 字的值然后拷贝到指令指针.




    +----------+
    | 读写内存 |
    +----------+


    读写内存有两种方法. 得到操作码 和操作码 的操作数被认为是读内存的简单方法,并且在这个过程中没有什么模拟行为. 另一种方法是当指令执行的时候读写内存 (The other way is when an instruction has to go out and read/write from memory). 这是你必须关心的方法.


    为了模拟这种方法,你必须指出那条指令事实上需要读写内存. 对于 NES 的 CPU (6502),指令是 LDA (读)和 STA (写).


    依赖于读或写的地址,特定的时间必须发生. 特定的内存地址也许是一个寄存器,内存镜像,或者其他任何你的系统拥有的. 我将提供一个 NES 有详细地址的例子,但是我将会详细解释重点. 写内存和读内存用类似的方法.


         BYTE ReadMemory(WORD wAddress)
         {
               switch (wAddress)
               {
                     case 0x2002:
                     // Emulate the PPU status register.
                     break;
         
                     case 0x2004:
                     // Return a byte from sprite memory.
                     break;
         
                     .
                     .
                     .
         
                     default:
                     // Return a byte at the address passed in.
               }
         }




    +------------+
    | 做循环任务 |
    +------------+


    选择执行循环任务的时间和位置是依赖于你所模拟的系统的选择. 它可以在绘制一个扫描线之后,在绘制一整个屏幕之后,或者即便是任何指令之后. 循环任务越多,性能损失也就越多,但是准确性就会越高. 其他的方法也有同样效果. 如果你减少循环任务,你的模拟器的速度就会加快但是会损失一些准确性. It's up to you to desgin your emulator, so I leave the choice up to you.


    一旦你决定了执行循环任务的位置,你就必须决定循环任务是什么. 例如执行中断,增加被扫描的扫描线,或者其他任何你能想到的但必须有一定间隔.


    对于 NES 模拟器,我决定在每条扫描线绘制后执行循环任务. 我通过经过的 CPU 时钟周期数量来保持屏幕扫描线的绘制轨迹. 我通过下面的式子来计算每条扫描线需要的时钟周期:


         NumCyclesPerScanline = (CPUFrequency / RefreshRate) / NumScanlinePerFrame






    +----------+
    | 视频模拟 |
    +----------+


    模拟器图形处理是非常依赖系统的题目. 我将解释一些一般的模拟器图形处理的观点,但是这里的大多数的材料都特别对应于 NES. 如果你除 NES 外其他的系统,我希望这里的信息会有用; 如果不然,跳过本部分.


    电视机的图像都是通过一个电子枪显示. 如果你距离电视机很近地看,你可以看到单个的像素. 像素改变颜色通过电子枪从屏幕左边到右边的移动. 就这样一行一行的做,使得像素改变颜色. 这就叫做绘制一条扫描线. 如果电子枪完成一条扫描线,它将回到左边并向下移动到下一条扫描线. 电子枪回到屏幕左边的时间叫做 HBlank 时期. 一旦电子枪扫描完屏幕上的所有扫描线,对 NTSC 来说是240条,它就移动到屏幕的左上角. 电子枪移动到左上角的时间叫做 VBlank 时期.


    有了上面的信息后,我们就可以讨论一些显示图形的指导方针. 由于人们看到图形的原理,你可以绘制图形的每一条扫描线,或者你可以在 VBlank 时期等待和绘制任何东西. 我将讨论两种方法的不同点.


    通过绘制图形的每条扫描线来绘制图形非常精确但不是很快速. 用这种方法模拟,你就必须需要让 CPU 执行每条扫描线需要的时钟周期 (用上面说过的方法计算). 对于 NES,就是114条 (rounded up). 然后你就应当绘制这条线. 在 VBlank 时期,你应当继续模拟 CPU, 但是不要在 VBlank 结束前绘制扫描线.


    每次绘制整个的屏幕将会非常快,但是如果模拟的不正确,将会损失掉精确度. 用这种方法模拟,你需要让 CPU 执行240条扫描线,然后将所有扫描线一下子绘制. 如果你的模拟器在绘制屏幕的时候改变值 (卷轴值,调色板个数),你将会损失精确度.


    对于我的模拟器,我选择使用扫描线方法来获得最终的准确. 阅读 Yoshi 的 NES 文档以获得关于 NES 和图形系统更多的信息.


    http://www.zophar.net/tech/files/ndox200.zip




    +----------+
    | 声音模拟 |
    +----------+


    对我来说,声音是最难模拟的. 大致的原因就是 DirectSound Buffers 方面的设计. 我将会解释我所使用的方法,并且把后面的留给你,寄希望于你能够发现我的错误并且纠正.


    第一,你应当去学 DirectSound Buffers 流 (steaming DirectSound Buffers). Microsoft 在 MSDN 中有大量的相关信息. 既然它们已经有了丰富的信息和优秀的作者,那么如果我继续尝试解释就是浪费时间,呵呵.


    我的算法就是在每个框架的末尾用声音数据更新 DirectSount Buffer. 下面就是我用来模拟一个输出了期待的大小和频率的方波的声音频道的步骤:


         1. 计算波的波长. 这是在 CPU 周期里完成,由一些声音寄存器读出.
         2. 计算波的工作循环. 这就是应当重复波长多少次直至波结束. 同样,这也是从声音寄存器读出.
         3. 计算输出音量. 这是从声音寄存器读出. 你可以认为这是波的振幅.


    我只在框架开始做这件事. 然后我就沿着波向前移动并把数据写到 DirectSound Buffer.样本的比率决定了写入缓冲的字节数目. 我只是将 CPU 框架中同样数目的样本写入缓冲. 在下一个框架,我继续写入剩下的数据. 当我完成写入所有我计算的波的数据,我将重新计算步骤1-3,然后重复整个过程. 例如:


         NumBytesToLock=(((NumBitsPerSample/8)*NumChannels*NumSamplesPerSec)/30)


    下面是我模拟 NES 的一个频道的方波的代码,用它来阐明我所说的.


    //------------------------------------------------------------------------------
    // Name: APU_DoFrame()
    // Desc: Takes care of the sound for a frame.
    //------------------------------------------------------------------------------
    BOOL APU_DoFrame()
    {
           // Stuff necessary for working with streaming buffers.
           HRESULT hr;
           DWORD   dwWritePos;
           DWORD   dwBytesLocked1;
           DWORD   dwBytesLocked2;
           DWORD   dwByteNum;
           VOID*   pvData1;
           VOID*   pvData2;
           VOID*   pvDataSave;
     
           static DWORD dwLastEndWritePos = 0;
     
           // Stuff for nintendo sound files.
           WORD wTotalOutputVol; // Sum of all the sound channel's volumes.
     
     
           // If the buffer is invalid then return.
           if (lpdsbSoundBuf == NULL)
                  return FALSE;
     
           // Get the current write position within the buffer.
           if (FAILED(lpdsbSoundBuf->GetCurrentPosition(NULL, &dwWritePos)))
                  return FALSE;
     
           // If the play cursor has just reached the first or second half
           // of the buffer, it's time to stream data to the other half.
           LONG lTemp = dwLastEndWritePos - 1000;
           if (lTemp < 0)
                  lTemp = 0;
     
           //if (dwWritePos >= (dwLastEndWritePos))// || ())
           if (dwWritePos >= (DWORD)lTemp)// || ())
           {
                  // Lock the buffer so we can write to it.
                  hr = lpdsbSoundBuf->Lock(dwLastEndWritePos, OPTIONS_NUM_BYTESTOLOCK, 
                         &pvData1, &dwBytesLocked1, &pvData2, &dwBytesLocked2, 0);
     
                  if (SUCCEEDED(hr))
                  {
                         // For the first part of the loop calculate the sound values.
                         bCalculateSound = TRUE;
     
                         // Fill the whole locked portion of the sound buffer with
                         // out sound data from each of the Nintendo's sound channels.
                         // This involves two for loops since the buffer may wrap around.
                         
                         // Save a temp pointer to the first portions of the sound buffer.
                         pvDataSave = pvData1;
     
                         // First portion of the buffer.
                         for (dwByteNum = 0; dwByteNum < dwBytesLocked1; 
                               dwByteNum += OPTIONS_NUM_CHANNELS * (OPTIONS_NUM_BITSPERSAMPLE / 8))
                         {
                               // Clear the last volume outta there.
                               wTotalOutputVol = 0;
     
                               // Process the square channel 1.
                               wTotalOutputVol += (WORD)APU_DoSquare1();
     
                               // Write the data to the sound buffer and move the pointer 
                               // to the buffer to the next data position.
                               if (OPTIONS_NUM_BITSPERSAMPLE == 8)
                               {
                                      *((BYTE*)pvDataSave) = (BYTE)wTotalOutputVol;
                                      pvDataSave = (BYTE*)pvDataSave + (OPTIONS_NUM_CHANNELS * 1);
                               }
                               else
                               {
                                      *((WORD*)pvDataSave) = wTotalOutputVol;
                                      pvDataSave = (BYTE*)pvDataSave + (OPTIONS_NUM_CHANNELS * 2);
                               }
     
                               // Stop calculating the sound.
                               if (bCalculateSound == TRUE)
                                      bCalculateSound = FALSE;
                         }
     
                         // If the locked portion of the buffer wrapped around to the 
                         // beginning of the buffer then we need to write to it.
                         if (dwBytesLocked2 > 0)
                         {
                               // Save a temp pointer to the second portions of the sound buffer.
                               pvDataSave = pvData2;
     
                               // Second portion of the buffer.
                               for (dwByteNum = 0; dwByteNum < dwBytesLocked2; 
                                      dwByteNum += OPTIONS_NUM_CHANNELS * (OPTIONS_NUM_BITSPERSAMPLE / 8))
                               {
                                      // Clear the last volume outta there.
                                      wTotalOutputVol = 0;
     
                                      // Process the square channel 1.
                                      wTotalOutputVol += (WORD)APU_DoSquare1();
     
                                      // Write the data to the sound buffer and move the pointer 
                                      // to the buffer to the next data position.
                                      if (OPTIONS_NUM_BITSPERSAMPLE == 8)
                                      {
                                             *((BYTE*)pvDataSave) = (BYTE)wTotalOutputVol;
                                             pvDataSave = (BYTE*)pvDataSave + (OPTIONS_NUM_CHANNELS * 1);
                                      }
                                      else
                                      {
                                             *((WORD*)pvDataSave) = wTotalOutputVol;
                                             pvDataSave = (BYTE*)pvDataSave + (OPTIONS_NUM_CHANNELS * 2);
                                      }
                               }
                         }
     
                         // Unlock the buffer now that were done with it.
                         lpdsbSoundBuf->Unlock(pvData1, dwBytesLocked1, pvData2, dwBytesLocked2);
                  }
     
                  // Save the position of the last place we wrote to so 
                  // we can continue the next time this function is called.
                  dwLastEndWritePos = (DWORD)(dwLastEndWritePos+dwBytesLocked1+dwBytesLocked2);
     
                  if (dwLastEndWritePos >= (OPTIONS_NUM_BITSPERSAMPLE/8)*OPTIONS_NUM_CHANNELS*OPTIONS_NUM_SAMPLESPERSEC)*OPTIONS_NUM_SECONDSFORBUFFER)
                  {
                        dwLastEndWritePos -= (OPTIONS_NUM_BITSPERSAMPLE/8)*OPTIONS_NUM_CHANNELS*OPTIONS_NUM_SAMPLESPERSEC)*OPTIONS_NUM_SECONDSFORBUFFER;
                  }
           }
     
           // Victory is ours!!!!
           return TRUE;
    } // end APU_DoFrame()
     


    //------------------------------------------------------------------------------
    // Name: APU_DoSquare1()
    // Desc: Process the first square wave sound channel.
    //------------------------------------------------------------------------------
    WORD APU_DoSquare1()
    {
           static DWORD dwNumCyclesElapsed = 0; // Keeps track of where we are in the wave.
           static DWORD dwDutyFlip = 0;         // How many times the programmable timer must 
                                                // reload untill a duty flip.
           static DWORD dwWaveLength = 0;       // How many cycles till the programmable timer reloads.
     
           static BYTE  byOutput = 0x80; // The returned output volume.
           static BOOL  bDutyFlip = FALSE; // Has a duty flip happended?
     
           static BOOL bCalcSquare = FALSE;
           static BOOL bCalcOnNextFlag = FALSE;
     
           if (bCalculateSound)
                  bCalcOnNextFlag = TRUE;
           
           // If the calculate flag is set, then we need to calculate everything
           // that is needed to return an output volume to the calling function.
           // Otherwise we just return the precalculated data that was stored
           // in the static variables.
           if (bCalcSquare)
           {
    CalculateIt:
                  // First thing we need to do is start over by reseting the elapsed cycles.
                  dwNumCyclesElapsed = 0;
                  // Reset the duty toggle.
                  bDutyFlip = FALSE;
                  bCalcSquare = FALSE;
                  bCalcOnNextFlag = FALSE;
                  //---------------------------------------------------------------------
                  // Do the length counter part.
                  //---------------------------------------------------------------------


                  // If the length counter is enabled then we need to process it.
                  if (!(CPU.Memory[0x4000] & 0x20))
                  {
                         // If the length counter is not zero then decrement the value.
                         if (APU.sndchanSquare1.byLengthCtr)
                               APU.sndchanSquare1.byLengthCtr--;
                  }
     
                  //---------------------------------------------------------------------
                  // TODO: This is where the sweeping unit needs to be emulated.
                  //---------------------------------------------------------------------


                  //---------------------------------------------------------------------
                  // Emulate the programmable timer to get the wavelength.
                  //---------------------------------------------------------------------


                  // Take the 3 least significant bits from $4003 and
                  // use those as the bits 8-10 for our wavelength. Bits
                  // 0-7 come from $4002 to produce our 11-bit wavelength.
                  // The we need to add one to it.


                  dwWaveLength = ((((WORD)(CPU.Memory[0x4003]&0x7)) << 8) | CPU.Memory[0x4002]) + 1; 
             
                  //---------------------------------------------------------------------
                  // Emulate the duty flip part.
                  //---------------------------------------------------------------------


                  dwDutyFlip = abyDutyCycleTablePos[CPU.Memory[0x4000]>>6];


                  //---------------------------------------------------------------------
                  // Now finally send the signal through the volume/envelope decay unit.
                  //---------------------------------------------------------------------


                  // If the envelope decay bit is set, then the volume goes staight
                  // to the DAC...or in our case DirectSound. This means that
                  // the envelope decay is disabled.


                  if (!(CPU.Memory[0x4000] & 0x10))
                  {
                         // There are a few conditions when 0 is sent straight
                         // to the DAC for the volume. They are as follows:
                         // 1.  If the length counter is 0
                         // 2.  Something to do with the sweep unit.
                         // 3.  On the negative portion of the output frequency 
                         //     signal coming from the duty cycle.
                         //
                         // Otherwise bits (0-3) of $4000 are sent straight the
                         // DAC for the volume.


                         if (APU.sndchanSquare1.byLengthCtr == 0) // TODO: implement other conditions.
                               byOutput = 0x80;
                         else
                               byOutput = ((CPU.Memory[0x4000] & 0x0F) << 3);
                  }
                  else
                         byOutput = ((CPU.Memory[0x4000] & 0x0F) << 3);
           }
     
           // If we are done with the wave we need to start over.
           if (dwNumCyclesElapsed >= dwWaveLength)
           {
                  // We need to flip the volume to negative if the amount of
                  // times for a duty flip has passed.


                  if ((dwDutyFlip--) == 0)
                  {
                         // If the duty flip is positive then we load the counter with the negative
                         // value. If the duty flip is negative, then we load the counter
                         // with the positive counter.


                         if (bDutyFlip)
                         {
                               dwDutyFlip = abyDutyCycleTablePos[CPU.Memory[0x4000]>>6];
                               if (bCalcOnNextFlag)
                               {
                                      bCalcSquare = FALSE;
                                      //return (WORD)byOutput;
                                      goto CalculateIt;
                               }
                         }
                         else
                         {
                               dwDutyFlip = abyDutyCycleTableNeg[CPU.Memory[0x4000]>>6];
                         }
     
                         // Flip the output wave.
                         byOutput = -byOutput;
     
                         // Toggle the duty flip indicator.
                         bDutyFlip ^= TRUE;
                  }
     
                  dwNumCyclesElapsed -= dwWaveLength;
           }
           else
                  // Keep moving along the phase of the wave form.
                  dwNumCyclesElapsed += dwNumCyclesPerSample;
     
           // Return the final value.
           return (WORD)byOutput;


    } // end APU_DoSquare1()




    +------+
    | 优化 |
    +------+


    本节中,我将列出一些通用的优化方法以及一些针对 NES 的优化方法.


         1. 解开循环. 如果你有一个循环,并且你知道这个循环将被做一段确定的次数,就剪、贴同样的代码. 例如,如果你有一个执行某些代码3次的循环,就把被执行的代码重复三次来代替循环. 在汇编语言中,循环代码的比较和跳过指令将比直接执行代码花费更多时间.


            for (int i=0; i < 3; i++)
            {
                // Some Code
            }


            应当别写成:


            // Some Code
            // Some Code
            // Some Code


         2. 对于汇编语言,重写一些代码将受益匪浅. 循环就是在执行中获得的一个例子. 另一个例子是不再使用 C++ 中的 switch 和 case 陈述,你应当使用汇编语言中被称为跳转表格 (jump table) 的东西. 一个跳转表格是一个保存地址的数组. 不论 case 的值是多少,你都会到数组的相应位置,然后移动那个值到一个32位寄存器. 然后你跳转到那个寄存器中的值. 这种想法在模拟读写内存的函数和操作码 中的 switch 陈述中很有用.


            ; 获得操作码 字节.
            GET_MEMORY_BYTE CPU.P


            ; 为后面的使用保存操作码,然后用上面定义
            ; 的跳转表格跳转到正确的操作码
            and eax, 000000FFh
            mov dwOpcode, eax
            jmp [adwOpcodeJumpTable+eax*4]


         3. 对于 NES 的图形处理,是用马赛克隐藏算法 (tile caching algorithm). 他将在你读 NES 图形处理如何工作是有意义. 他们在 Yoshi 的 NES 文档中有解释. http://www.zophar.net/tech/files/ndox200.zip




    +----------+
    | 课程收获 |
    +----------+


    1. 不要在学校的学期内做这个项目 ;-)
    2. 当模拟 NES 时,读一些关于6502的不同的文档. 它们提供不同的信息. 例如,我读过的一个文档说 PLA 不设定 CPU 的标志,而我读过的另一个说事实上它设定标志.




    +------------+
    | 我的模拟器 |
    +------------+


    下面是我的模拟器和我的模拟器的代码. 这个项目使用 Visual C++ .NET 和 DirectX 8 SDK 制作的.


    在我的模拟其中运行 NES ROM,你需要先运行模拟器,然后选择文件菜单 (file menu) 中的打开游戏 (Open Rom),然后选择文件菜单中的运行 (Run). 当运行游戏的时候,使用光标移动键作为上、下、左、右,S 是 Start,A 是 Select,Z 是 B,X 是 A. ESC 将停止运行 ROM.


    在我的模拟器中 debug 一个 ROM,你可以双击任何一行来增加或删除一个中断点 (breakpoint). 你可以使用 Debug 菜单来一步一步运行代码或者运行到一个 NMI 中断. 使用察看菜单 (View menu) 来查看内存.


    NEStreme (http://www.cecs.csulb.edu/~hill/cecs497/nestreme/NEStreme.zip)
    NEStreme source (http://www.cecs.csulb.edu/~hill/cecs497/nestreme/NEStreme_src.zip)
    NEStreme Mapper0 source (http://www.cecs.csulb.edu/~hill/cecs497/nestreme/mapper0.zip)
    NEStreme Mapper2 source (http://www.cecs.csulb.edu/~hill/cecs497/nestreme/mapper2.zip)
    NEStreme Mapper3 source (http://www.cecs.csulb.edu/~hill/cecs497/nestreme/mapper3.zip)




    +----------+
    | 参考文献 |
    +----------+


    Zophar 的网站 (http://www.zophar.net/index.phtml)


    Holds tons of information on emulators as well as the emulators themselves.


    NES info, programs, and demos (http://nesdev.parodius.com)


    Holds over 2 thousand pounds of programming information on the NES as well as some free demos.


    * Translated by Blue Potato [bspotato@yahoo.com.cn] [http://bspotato.51.net] *
    * 虽然已经尽全力翻译了,不过还是会有很多不恰当的地方. 如果你有很好的建议,请发信到上面的信箱中. 非常感谢 *
    *--------------------------------------------------------------------------------------------------------*
    * 感谢 AgeMO 网友的建议,将 opcode 翻译为操作码                                                          *
    * 感谢 roseclarkh 网友的建议,对一些输入错误进行了校正                                                   *
    * 感谢 palomino 网友的建议,修正了对 Accumulator register 的错误翻译                                     *
    *--------------------------------------------------------------------------------------------------------*
    * Chinese Edition Version: 1 *
    展开全文
  • 数据库mysql,xml文件里的sql语句怎样写? 数据库mysql,xml文件里的sql语句怎样写
  • 我看淘宝里当页页面的页数是span,其他的就是a。这个我该怎样去写? 要写一套span和一套a的的样式吗?请问具体怎样写,谢谢!
  • jsp怎样写一个Button onclick事件
                   
    <input type="button" value="OK" onClick="showValue()" />


    注意c是大写的

               

    再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

    展开全文
  • 怎样写好一篇高质量的技术文章?

    千次阅读 2020-05-03 02:57:50
    怎样写好一篇高质量的技术文章? 培根说“读史使人明智,读诗使人聪慧,学习数学使人精密,物理学使人深刻,伦理学使人高尚,逻辑修辞使人善辩。” 核心心法 就是起心动念利他,一切方法自来。 先来问自己几个问题 ...

    怎样写好一篇高质量的技术文章?

    培根说“读史使人明智,读诗使人聪慧,学习数学使人精密,物理学使人深刻,伦理学使人高尚,逻辑修辞使人善辩。”

    核心心法

    就是起心动念利他,一切方法自来。

    先来问自己几个问题

    比如每天写文章之间,先来问自己几个问题。

    1、我写这篇文章的目的是什么?

    2、我能给读者带来什么好处?

    3、我如何安排文章结构,让读者快速获得好处?

    问完这几个问题,一篇文章就很快构思好了。

    因此,你想每天写出高质量的文章,你就问自己这个问题。

    我的这篇文章能给别人带来什么好处?

    也许有人会问,我凭什么每天要写一篇利他的原创文章,对我有什么好处?

    如果你有这样的疑问,那就分享两个原则给你。

    原则一:一个人的影响力不是由他的财富决定,也不是由他的智力决定,而是由他能帮助多少人决定。

    也就是说,你能帮助的人越多,你的影响力越大,你的能量就越强。

    原则二:成功的关键在于提高你的能量。当你的能量提高了,别人就被你吸引。一旦他们慕名前来,你就要他们付钱。

    因此,要想赚钱,你首先得值钱,怎么体现你值钱呢?那就是看你能帮助多少人,你帮他解决的问题有多重要。每天写一篇高质量的文章,每天都增强自己帮助别人的能力,看似在帮助别人,实则是在帮助自己。

    习惯养成:每天都阅读

    心法已经说完,现在来说一个习惯。

    每天写一篇高质量文章的习惯:每天都阅读

    如果把每天写作的你看成一头奶牛,你每天写的文章就是你产的牛奶。

    按照这个比喻,如果你想持续每天都产奶,那你需要做什么呢?

    答案很简单,那就是每天都吃草,如果不吃草,别说产奶了,可能连自己都要饿死。

    因此,如果你每天不看书,不阅读,没有素材,你怎么能写出高质量的文章呢?

    当然啦,也许你会说,你看不进书,不愿意看书。

    其实原因是你不知道看书的秘诀,正所谓“心怀利器,杀心自起”。

    如果你掌握了快速阅读的秘诀,你能1天看5本书,你就会爱上看书。

    一个观念:

    复杂事情简单化, 简单事情流程化.

    水到渠成,渠到水成。

    渠就是流程,水就是文章。当你的流程创建好了,就等于是修建好了渠道,有了流程,每次坐在电脑前,文章就像水龙头里的水一样自然地流出来了。一切都是那样的自然。

    每天写一篇高质量文章的流程:

    第一步:收集素材

    第二步:取出素材

    第三步:策划文章

    第四步:写出文章

    好啦,继续分享干货。

    3款工具。

    工欲善其事必先利其器,好的工具能提高10倍你写文章的效率。

    第一款工具是奇妙清单 To Do List 。

    它用来收集素材,收集灵感,收集你的一切idea。

    我更愿意把奇妙清单看成是灵感孵化器。我会把我的idea都放在奇妙清单中,这些idea就像种子一样,它会慢慢长大,会长成参天大树。

    第二天我再打开奇妙清单,找出适合当天的话题,然后写成一篇高质量的文章。

    第二款工具是xmind

    这是一款思维导图软件,能帮助你把一个idea构思成一篇文章,构思成一个课程,构思成一本书。

    小结

    每天写一篇高质量文章的关键是:

    1个心法+1个习惯+1个流程+3款工具****

    一篇好的技术文章是由哪几部分组成?

    1、引言|前言,精炼覆盖全篇所讲的内容。

    2、阐明该技术的重心/解决什么问题。

    3、着重分析自己所要表达的技术重心。

    4、总结/最佳实践。

    一篇好的技术文章,绝对不能仅仅是一堆文字(除非是论文类的),我们需要用更多的表达手段阐明一个问题。绘图,源码,列表,标题,引用等等都是很好的辅助手段,在分析源码的时候如果能有个uml类图,远远比一堆文字所表达的内容丰富的多。适当插入些源代码可以更好的用作分析,避免读者阅读疲劳。引用一些经典的理论或者他人的博客,可以给读者留下更深的印象。

    既然要写文章,记得多看文章

    明确了优秀的技术文章类型,在想象如何做才能做得更好的同时,你也可以思考为什么他们会这么写,最重要的是,除了知识点之外,仔细研究作者的可读性因素。

    一名可以坚持创作优质内容的写作者,也一定是需要不断地吸收,才能付出。所以多多阅读是最基本的条件。

    写好一篇优秀的文章,学会模仿优秀的案例非常重要。接下来给大家分享一下一篇高质量的技术文章有哪些技巧与方法

    1、确定标题

    最常用的一个方式,就是不写内容,先写自己的主副标题。通过标题,纵览出这篇文章的结构。

    2、学会做图

    我们要会一些简单的uml,时序图等,很多程序员都是在process做好图。大家可以试试procee这个在线作图软件。

    3、精准排版

    文章需要有个良好的排版,一般可以采用markdown的方案,他的排版语法简单,大部分的网站平台都支持解析,而且排版结构也足够清洗。

    4、模仿总结

    如果刚写文章不久,自己写出的干货太少,可以先去找一些优秀的案例文章,通过对他们的文章学习归纳,模仿写一写。

    5、核心技巧

    核心技巧,就是一个字-写。只有不停的写,才可以自己从中体会出自己的心得,有了自己的心得,以后写完整就会更加的有章法,舒畅了~

    6、适当发散

    发散指的是不满足,指的是在常规的东西之外一定要多想多思考,以Object的hashCode()方法为例,大家可能懂了hashCode()方法是干嘛的,但是如果多发散一点,写清楚:重写equals()方法的时候不重写hashCode()方法可能会导致什么问题,诸如这种别人不太会写的问题,这样自己会成长地更快,别人也会更喜欢你的文章——因为在你这里可以看到不一样的东西。

    7、端正心态

    最后其实就是说说写文章的心态了,如果写的文章只是为了自己看看,那完全可以使用那种笔记软件,没必要写成文章排版发布,如果写成文章发布到博客网站,那一定要注重两点:

    自己看得懂
    ②别人也得懂。

    读者和个人一定是同步成长的,因此把每个句子进行推敲并以最精准的语言表达出来,对别人有益、对自己对于知识的梳理也有益。

    在掌握以上技巧的同时,也要了解文章写作时的注意事项

    1、注意延伸适当

    写技术文章,不要在我们分析的技术体系上延伸到更多的领域,以免给读者抓不到重心的感觉,如果遇到其他的技术问题,我们可以直接给出技术答案,或者可以预留起来,引用其他人的博客文章。

    2、注意文字精炼

    技术文章,尽量不要长篇大论(除非是理论文章),因为大部分的技术人员不并不是理论学家,都是面向实际应用的,所以能用一句话解决的问题,不要反复重复。

    3、注意结论准确

    我们的技术文章,不仅是给写自己的,同样是写给他人的,不要没有经过反复实验就擅自发表出来,给人错误的答案就不太好了。当然如果自己没有分析对,发表出来被人指出了问题所在,我们也应该即使修改。

    总结,一篇好的技术文章,其实写作重要的不是别人的方法总结,而是自己的不断打磨练习。打磨文章,往小了说,可以让文章更完美、更耐读;往大了说,就是对读者负责。

    总之,你知道的越多,你创作时就越能够得心应手,运用自如,作品也就越饱满、越丰富。

    参考链接

    https://www.sohu.com/a/327457987_120001389
    https://www.sohu.com/a/160717557_140635


    Kotlin开发者社区

    专注分享 Java、 Kotlin、Spring/Spring Boot、MySQL、redis、neo4j、NoSQL、Android、JavaScript、React、Node、函数式编程、编程思想、"高可用,高性能,高实时"大型分布式系统架构设计主题。

    High availability, high performance, high real-time large-scale distributed system architecture design

    分布式框架:Zookeeper、分布式中间件框架等
    分布式存储:GridFS、FastDFS、TFS、MemCache、redis等
    分布式数据库:Cobar、tddl、Amoeba、Mycat
    云计算、大数据、AI算法
    虚拟化、云原生技术
    分布式计算框架:MapReduce、Hadoop、Storm、Flink等
    分布式通信机制:Dubbo、RPC调用、共享远程数据、消息队列等
    消息队列MQ:Kafka、MetaQ,RocketMQ
    怎样打造高可用系统:基于硬件、软件中间件、系统架构等一些典型方案的实现:HAProxy、基于Corosync+Pacemaker的高可用集群套件中间件系统
    Mycat架构分布式演进
    大数据Join背后的难题:数据、网络、内存和计算能力的矛盾和调和
    Java分布式系统中的高性能难题:AIO,NIO,Netty还是自己开发框架?
    高性能事件派发机制:线程池模型、Disruptor模型等等。。。

    合抱之木,生于毫末;九层之台,起于垒土;千里之行,始于足下。不积跬步,无以至千里;不积小流,无以成江河。

    展开全文
  • 怎样写一个解释器

    千次阅读 2016-11-08 17:33:43
    转载: 怎样写一个解释器写一个解释器,通常是设计和实现程序语言的第一步。解释器是简单却又深奥的东西,以至于好多人都不会写,所以我决定写一篇这方面的入门读物。虽然我试图从最基本的原理讲起,尽量不依赖于其它...
  • 1, 怎样写好英文论文的Introduction部分?
  • 应届毕业生应该怎样写简历?

    千次阅读 2018-03-30 10:42:45
    应届毕业生应该怎样写简历? 非常惭愧的是,被标榜为“个性”、“张扬”、“有活力”的许多 90 后末班青年们似乎并没有在求职大道上将这些闪光点亮出来,反倒是奉行了“我低调我自豪,我有人品我骄傲”的一套原则,...
  • ![图片说明](https://img-ask.csdn.net/upload/201506/16/1434469309_574444.png) 我要打开图中的txt文件,怎样写绝对路径?如果写相对路径,从哪个文件层算起?
  • 怎样写好一份IT技术岗位的简历

    万次阅读 多人点赞 2013-10-05 08:27:50
    10月是校园招聘的旺季,很多应届毕业生都忙碌起来了,从CSDN笔试-面试文章...所以呢,我想把自己对待简历的看法,怎样写好一份IT技术岗位的简历,分享出来,希望能够帮助到一些求职者,也期望能够得到一些同学的反馈。
  • 有两个表,表1中字段有姓名,编号,地址 表2中字段有省,地址。 例如表1数据如下 (张三,001,济南)(李四,002,青岛)(王五,003,威海) (王伟,004,石家庄)...如果搜索河北省的用户信息请问该怎样写sql语句
  • 怎样写获取时间最大的数据记录的sql语句
  • 文本框在div块中居中,css该怎样写? 写text-align:center不行。可能是因为文本框不是文本的原因吧
  • 就是在用手机登陆一个网站后,有一个活动报名,点击后有两个文本框,一个姓名,一个手机号,点击活动,让手机号直接显示,不必每次输入,在action中怎样写
  • 怎样写一个事件:点击一个按钮或者事件, 然后在本页面弹出一个页面
  • 怎样写出优秀的研究论文?

    千次阅读 2016-05-16 10:59:18
    怎样写出优秀的研究论文? 注:此文为转载,原创作者为:whatbeg,原文链接为:http://whatbeg.com/2016/05/10/how2wtpaper.html#版权为原创所有,尊重原创。 本文译自微软剑桥研究院Simon Peyton Jones的演讲PPT...
  • ssm怎样写统计表内的信息条数和每列的和: 统计一张表内的信息条数,和每一列的和 请附详细代码。(controller,service,model)![蓝色部分是需要求的和,表名 tb_inentterprise]...
  • 怎样写综述论文

    千次阅读 2019-03-22 10:21:46
    目录 1 系统的讲述 1.1 Writing a Review Article 1.1.2 Identifying the Relevant Literature 1.1.3 Structuring the Review 1.1.4 Tone ...1.1.6 Theoretical Development in YourArticle ...1.1.7 Creat...
  • hexo怎样写博客

    千次阅读 2017-07-24 02:11:52
    之前在博客园、简书、CSDN等地儿都开过博,一篇文章好了,我希望能在几个平台可以同步发布,可是操作起来成本不低。几个平台下的富文本编辑器比较起来还是博客园更顺手,看着更舒服,尤其是代码块的操作灵活、准确...
  • 怎样写开题报告

    千次阅读 2018-02-26 14:10:33
     好研究方案一方面要了解它们的基本结构与写法,但“汝果欲学诗,功夫在诗外”,好开题报告和研究方案重要还是要做好很多基础性工作。首先,我们要了解别人在这一领域研究的基本情况,研究工作最根本的特点就是...
  • 怎样写好技术贴

    千次阅读 热门讨论 2013-10-06 09:25:02
    大学同学问我如何好技术贴? 我感觉这个问题挺有意义的,不过我之前还没有系统地总结过,希望下此文能帮助到他和对技术贴有兴趣的同学,希望该同学能走上技术博客的道路,我也好学习下。 通过此文,我也能...
  • 怎样写好git comment

    千次阅读 2019-03-22 10:45:57
    实践中总结的好git comment的方式
  • 怎样写一个解释器_王垠_新浪博客.
  • extjs中怎样写正则表达式使输入的数据必须是1-10位的数字,不能为001这样的
  • 使用py2exe将多个python程序编译成exe文件,这多个程序有一个主入口.py文件,那么这个setup.py应该怎样写?在着多个程序中还引入了很多python的包,需要写入setup.py文件中吗?
  • C++中用二维数组传参时形参该怎样写 [转] 二维数组的存储方式是和一维数组没什么区别,但是用二维数组做参数,它的形参该怎样写?要注意的是:函数中的形参其实就相当于一个声明,并不产生内存分配,形参的目的...
  • 怎样写好简历

    千次阅读 2014-03-14 10:44:29
    之前了些应届毕业生简历的常见问题,很多人觉得我太苛刻,觉得对应届生要求有点高;有的觉得看了后还是不会。那么今天我们就来看看怎么才能做好。需要声明的是我帮不了太多人,只有那些对计算机科学真的有点兴趣...
  • 怎样写参数个数可变的宏?

    千次阅读 2014-06-06 15:56:17
    怎样写参数个数可变的宏? 一种流行的技巧是用一个单独的用括弧括起来的的“参数” 定义和调用宏, 参数在宏扩展的时候成为类似printf() 那样的函数的整个参数列表。#define DEBUG(args) (printf("DEBUG: "), printf ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 36,446
精华内容 14,578
关键字:

怎样写