Adds InputWindowElement.

This commit is contained in:
falsycat 2021-08-26 14:51:50 +09:00
parent 11279acb02
commit a07c61675e
19 changed files with 385 additions and 9 deletions

View File

@ -148,6 +148,7 @@
<ItemGroup>
<ClCompile Include="src\Font.cc" />
<ClCompile Include="src\Game.cc" />
<ClCompile Include="src\HiraganaMatcher.cc" />
<ClCompile Include="src\Lua.cc" />
<ClCompile Include="src\main.cc" />
<ClCompile Include="src\PlayScene.cc" />
@ -155,6 +156,8 @@
<ClCompile Include="src\Win32Console.cc" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\HiraganaMatcher.h" />
<ClInclude Include="src\iInputMatcher.h" />
<ClInclude Include="src\common.h" />
<ClInclude Include="src\ElementStore.h" />
<ClInclude Include="src\Font.h" />
@ -166,6 +169,7 @@
<ClInclude Include="src\iElementFactory.h" />
<ClInclude Include="src\iLogger.h" />
<ClInclude Include="src\iClock.h" />
<ClInclude Include="src\InputWindowElementFactory.h" />
<ClInclude Include="src\iScene.h" />
<ClInclude Include="src\iWritable.h" />
<ClInclude Include="src\Logger.h" />
@ -181,6 +185,7 @@
<ClInclude Include="src\StackAllocator.h" />
<ClInclude Include="src\SystemClock.h" />
<ClInclude Include="src\Text.h" />
<ClInclude Include="src\InputWindowElement.h" />
<ClInclude Include="src\Texture.h" />
<ClInclude Include="src\TextureElement.h" />
<ClInclude Include="src\TickingClock.h" />

View File

@ -39,6 +39,9 @@
<ClCompile Include="src\PlayScene.cc">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\HiraganaMatcher.cc">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\iConsole.h">
@ -155,6 +158,18 @@
<ClInclude Include="thirdparty\utf8.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\InputWindowElement.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\iInputMatcher.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\HiraganaMatcher.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\InputWindowElementFactory.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Library Include="thirdparty\lua5.1.lib" />

View File

@ -53,6 +53,7 @@ class ElementStore {
for (auto& eptr : performing_) {
iElement* e = eptr.get();
if (e->period.end <= now) {
e->Finalize();
eptr = nullptr;
} else {
e->Update(frame, e->period.Normalize(now));

View File

@ -1,5 +1,6 @@
#pragma once
#include <string>
#include <vector>
#include "iDrawable.h"
@ -46,6 +47,8 @@ class Frame : public iDrawable, public iWritable {
}
}
std::string input;
private:
std::vector<const iDrawable*> draw_;
std::vector<const iWritable*> write_;

View File

@ -36,10 +36,12 @@ class Game : public iDrawable, public iWritable {
Game(Param&& p);
void Update() {
void Update(const std::string& input) {
clock_.Tick();
frame_.Clear();
frame_.input = input;
UniqPtr<iScene> next = scene_->Update(frame_);
if (next) {
scene_ = std::move(next);

View File

@ -30,7 +30,7 @@ class GlyphElementFactory : public iElementFactory {
const intmax_t size = std::get<double>(param.custom[2]);
auto& font = FindOrCreateFont(name);
auto tex = std::move(font.RenderGlyphs(ConvertUtf8ToUtf16(text), size)); /* TODO */
auto tex = std::move(font.RenderGlyphs(ConvertStrToWstr(text), size)); /* TODO */
return alloc_->MakeUniq<iElement, TextureElement>(
param.period, std::move(tex), std::move(param.driver));

99
src/HiraganaMatcher.cc Normal file
View File

@ -0,0 +1,99 @@
#include "HiraganaMatcher.h"
#include <cassert>
#include <map>
#include "common.h"
static const std::vector<std::pair<std::wstring, std::wstring>> kPatterns = {
{ L"ka", L"", },
{ L"ki", L"", },
{ L"ku", L"", },
{ L"ke", L"", },
{ L"ko", L"", },
{ L"sa", L"", },
{ L"si", L"", },
{ L"su", L"", },
{ L"se", L"", },
{ L"so", L"", },
{ L"ta", L"", },
{ L"ti", L"", },
{ L"tu", L"", },
{ L"te", L"", },
{ L"to", L"", },
{ L"na", L"", },
{ L"ni", L"", },
{ L"nu", L"", },
{ L"ne", L"", },
{ L"no", L"", },
{ L"a", L"", },
{ L"i", L"", },
{ L"u", L"", },
{ L"e", L"", },
{ L"o", L"", },
};
bool gj::HiraganaMatcher::Input(wchar_t c) {
assert(!done());
const std::wstring remain = state_.text.substr(state_.match);
const std::wstring newbuf = buffer_ + c;
const size_t n = newbuf.size();
size_t last_match = 0;
bool accept = false;
for (size_t i = 0; i < kPatterns.size(); ++i) {
const auto& pattern = kPatterns[i];
if (pattern.first.substr(0, n) == newbuf) {
const size_t pn = pattern.second.size();
if (remain.substr(0, pn) == pattern.second) {
accept = true;
last_match = i;
}
if (pattern.first.size() == n) {
if (!accept) return false;
state_.match += pn;
buffer_ = L"";
UpdateExpects(i);
return true;
}
}
}
if (accept) {
buffer_ = newbuf;
UpdateExpects(last_match);
}
return accept;
}
void gj::HiraganaMatcher::UpdateExpects(size_t last_match) {
std::wstring text = state_.text.substr(state_.match);
expects_ = L"";
if (text.empty()) return;
if (last_match < SIZE_MAX && buffer_.size()) {
expects_ = kPatterns[last_match].first.substr(buffer_.size());
text = text.substr(kPatterns[last_match].second.size());
}
while (text.size()) {
bool match = false;
for (const auto& pattern : kPatterns) {
const size_t n = pattern.second.size();
if (text.size() < n) continue;
if (text.substr(0, n) == pattern.second) {
expects_ += pattern.first;
text = text.substr(n);
match = true;
break;
}
}
if (!match) Abort("hiragana janai");
}
}

44
src/HiraganaMatcher.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include <vector>
#include "iInputMatcher.h"
namespace gj {
class HiraganaMatcher : public iInputMatcher {
public:
HiraganaMatcher(HiraganaMatcher&&) = default;
HiraganaMatcher(const HiraganaMatcher&) = default;
HiraganaMatcher& operator=(HiraganaMatcher&&) = default;
HiraganaMatcher& operator=(const HiraganaMatcher&) = default;
HiraganaMatcher(const std::wstring& text) : state_{text, 0} {
UpdateExpects();
}
bool Input(wchar_t c) override;
const std::wstring& expects() const override {
return expects_;
}
bool done() const override {
return state_.text.size() <= state_.match;
}
const State& state() const override {
return state_;
}
private:
std::wstring buffer_;
std::wstring expects_;
State state_;
void UpdateExpects(size_t last_match = SIZE_MAX);
};
}

73
src/InputWindowElement.h Normal file
View File

@ -0,0 +1,73 @@
#pragma once
#include <cstdint>
#include "common.h"
#include "iElement.h"
#include "iElementDriver.h"
#include "iInputMatcher.h"
#include "Text.h"
namespace gj {
class InputWindowElement : public iElement {
public:
struct Param {
UniqPtr<iInputMatcher> matcher;
UniqPtr<iElementDriver> driver;
Period period;
std::wstring text;
};
InputWindowElement() = delete;
InputWindowElement(InputWindowElement&&) = delete;
InputWindowElement(const InputWindowElement&) = delete;
InputWindowElement& operator=(InputWindowElement&&) = delete;
InputWindowElement& operator=(const InputWindowElement&) = delete;
InputWindowElement(Param&& p) :
iElement(p.period),
matcher_(std::move(p.matcher)), drv_(std::move(p.driver)),
text_(p.text), guide_(matcher_->expects()) {
param_["posX"] = 0;
param_["posY"] = 0;
}
void Update(Frame& frame, double t) override {
for (auto c : frame.input) {
if (matcher_->done()) break;
if (matcher_->Input(c)) {
guide_ = Text(matcher_->expects());
}
}
drv_->Update(param_, t);
const auto posX = std::get<intmax_t>(param_["posX"]);
const auto posY = std::get<intmax_t>(param_["posY"]);
text_.SetPosition(posX, posY);
guide_.SetPosition(posX, posY+1);
frame.Add(&text_);
frame.Add(&guide_);
}
void Finalize() override {
/* TODO score calculation */
}
private:
UniqPtr<iInputMatcher> matcher_;
UniqPtr<iElementDriver> drv_;
Text text_;
Text guide_;
iElementDriver::Param param_;
};
}

View File

@ -0,0 +1,45 @@
#pragma once
#include "common.h"
#include "HiraganaMatcher.h"
#include "iAllocator.h"
#include "iClock.h"
#include "iElementFactory.h"
#include "InputWindowElement.h"
namespace gj {
class InputWindowElementFactory : public iElementFactory {
public:
InputWindowElementFactory(InputWindowElementFactory&&) = delete;
InputWindowElementFactory(const InputWindowElementFactory&) = delete;
InputWindowElementFactory& operator=(InputWindowElementFactory&&) = delete;
InputWindowElementFactory& operator=(const InputWindowElementFactory&) = delete;
InputWindowElementFactory(iAllocator* alloc) : alloc_(alloc) {
}
UniqPtr<iElement> Create(Param&& param) override {
if (param.custom.size() != 2) return nullptr;
const std::string text = std::get<std::string>(param.custom[0]);
const std::string kana = std::get<std::string>(param.custom[1]);
InputWindowElement::Param p;
p.period = param.period;
p.driver = std::move(param.driver);
p.matcher = alloc_->MakeUniq<iInputMatcher, HiraganaMatcher>(ConvertStrToWstr(kana));
p.text = ConvertStrToWstr(text);
return alloc_->MakeUniq<iElement, InputWindowElement>(std::move(p));
}
private:
iAllocator* alloc_;
};
}

View File

@ -1,15 +1,18 @@
#include "PlayScene.h"
#include "GlyphElementFactory.h"
#include "InputWindowElementFactory.h"
gj::PlayScene::PlayScene(Param&& p) :
alloc_(p.alloc), logger_(p.logger), w_(p.w), h_(p.h),
clock_(p.clock), store_(&clock_, 256) {
GlyphElementFactory glyph(alloc_);
GlyphElementFactory glyph(alloc_);
InputWindowElementFactory inputWin(alloc_);
Lua::FactoryMap map;
map["Glyph"] = &glyph;
map["Glyph"] = &glyph;
map["InputWin"] = &inputWin;
lua_ = alloc_->MakeUniq<Lua>(
alloc_, &store_, map, "res/score/" + p.score + ".lua");

View File

@ -44,6 +44,9 @@ public:
frame.Add(&tex_);
}
void Finalize() override {
}
private:
Texture tex_;
UniqPtr<iElementDriver> drv_;

View File

@ -47,6 +47,35 @@ void gj::Win32Console::main() {
constexpr CONSOLE_CURSOR_INFO cursor{ 1, FALSE };
SetConsoleCursorInfo(screen_, &cursor);
/* read input */
{ /* critical section */
std::lock_guard<std::mutex> _(mtx_);
INPUT_RECORD input[256];
DWORD input_n;
PeekConsoleInput(buffer_, input, 256, &input_n);
if (input_n) {
/* Peek* can retrieve the input but doesn't remove it from the buffer
* so calls Read* to clear. */
ReadConsoleInput(buffer_, input, 256, &input_n);
}
for (DWORD i = 0; i < input_n; ++i) {
const auto& rec = input[i];
switch (rec.EventType) {
case KEY_EVENT:
if (rec.Event.KeyEvent.bKeyDown) {
input_ += rec.Event.KeyEvent.uChar.AsciiChar;
}
break;
default:
break;
}
}
}
/* write output */
CHAR_INFO* c = chars_.get();
{ /* critical section */
std::lock_guard<std::mutex> _(mtx_);

View File

@ -3,6 +3,7 @@
#include <atomic>
#include <cstdint>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
@ -10,6 +11,7 @@
#include <windows.h>
#undef NOMINMAX
#include "common.h"
#include "iConsole.h"
@ -32,10 +34,13 @@ class Win32Console : public iConsole {
tb_main_(alloc, w, h), tb_sub_(alloc, w, h),
chars_(std::make_unique<CHAR_INFO[]>(static_cast<uint64_t>(w)*h)),
win_(GetConsoleWindow()) {
_ASSERT(win_);
if (!win_) gj::Abort("GetConsoleWindow returned nullptr");
screen_ = GetStdHandle(STD_OUTPUT_HANDLE);
_ASSERT(screen_ != INVALID_HANDLE_VALUE);
buffer_ = GetStdHandle(STD_INPUT_HANDLE);
if (screen_ == INVALID_HANDLE_VALUE || buffer_ == INVALID_HANDLE_VALUE) {
gj::Abort("GetStdHandle returned nullptr");
}
CONSOLE_SCREEN_BUFFER_INFOEX size;
size.cbSize = sizeof(size);
@ -83,6 +88,13 @@ class Win32Console : public iConsole {
std::swap(tb_main_, tb_sub_);
}
std::string TakeInput() override {
std::lock_guard<std::mutex> _(mtx_);
std::string ret = std::move(input_);
input_ = "";
return ret;
}
uint32_t width() const override {
return w_;
}
@ -107,8 +119,11 @@ class Win32Console : public iConsole {
std::unique_ptr<CHAR_INFO[]> chars_;
HANDLE screen_ = INVALID_HANDLE_VALUE;
HANDLE buffer_ = INVALID_HANDLE_VALUE;
HWND win_ = nullptr;
std::string input_;
void main();
};

View File

@ -18,7 +18,7 @@ using mat3 = ::linalg::mat<double, 3, 3>;
using vec3 = ::linalg::vec<double, 3>;
static inline std::wstring ConvertUtf8ToUtf16(const std::string& str) {
static inline std::wstring ConvertStrToWstr(const std::string& str) {
std::wstring ret;
const void* c = str.c_str();
@ -33,7 +33,7 @@ static inline std::wstring ConvertUtf8ToUtf16(const std::string& str) {
[[noreturn]]
static inline void Abort(const std::string& msg) {
MessageBox(NULL, ConvertUtf8ToUtf16(msg).c_str(), L"PROGRAM ABORTED", MB_OK);
MessageBox(NULL, ConvertStrToWstr(msg).c_str(), L"PROGRAM ABORTED", MB_OK);
std::exit(1);
}

View File

@ -26,6 +26,8 @@ class iConsole {
virtual Textbuffer& TakeTextbuffer() = 0;
virtual void SwapTextbuffer() = 0;
virtual std::string TakeInput() = 0;
virtual uint32_t width() const = 0;
virtual uint32_t height() const = 0;
};

View File

@ -23,6 +23,8 @@ class iElement {
virtual void Update(Frame& frame, double t) = 0;
virtual void Finalize() = 0;
/* Interfaces had better not have a variable but this is for optimization. */
const Period period;
};

35
src/iInputMatcher.h Normal file
View File

@ -0,0 +1,35 @@
#pragma once
#include <string>
namespace gj {
class iInputMatcher {
public:
struct State {
std::wstring text;
size_t match;
};
iInputMatcher(iInputMatcher&&) = default;
iInputMatcher(const iInputMatcher&) = default;
iInputMatcher& operator=(iInputMatcher&&) = default;
iInputMatcher& operator=(const iInputMatcher&) = default;
iInputMatcher() = default;
virtual ~iInputMatcher() = default;
virtual bool Input(wchar_t c) = 0;
virtual const std::wstring& expects() const = 0;
virtual bool done() const = 0;
virtual const State& state() const = 0;
};
}

View File

@ -32,7 +32,7 @@ int main() {
param.h = kHeight;
gj::Game game(std::move(param));
while (true) {
game.Update();
game.Update(console.TakeInput());
{
auto& fb = console.TakeColorbuffer();
fb.Clear();