Aegisub/kanamemo/game_display.cpp

585 lines
29 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2006, Rodrigo Braz Monteiro
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------------
//
// AEGISUB
//
// Website: http://aegisub.cellosoft.com
// Contact: mailto:zeratul@cellosoft.com
//
///////////
// Headers
#include "game_display.h"
#include "kana_table.h"
#include "game_window.h"
#include "game_panel.h"
#include "main.h"
///////////////
// Constructor
GameDisplay::GameDisplay(wxWindow *parent)
: wxWindow (parent,-1,wxDefaultPosition,wxSize(400,320))
{
// Get status bar
statusBar = ((wxFrame*)parent)->GetStatusBar();
wxMenuBar *menuBar = ((wxFrame*)parent)->GetMenuBar();
menuCheck[0] = menuBar->FindItem(Menu_Options_Hiragana);
menuCheck[1] = menuBar->FindItem(Menu_Options_Katakana);
EnableGame(false);
// Initialize
Reset();
}
//////////////
// Destructor
GameDisplay::~GameDisplay() {
Save();
delete table;
}
/////////
// Reset
void GameDisplay::Reset() {
autoLevel = true;
status = 0;
levelUp = 0;
level[0] = 1;
level[1] = 1;
enabled[0] = true;
enabled[1] = false;
menuCheck[0]->Check(enabled[0]);
menuCheck[1]->Check(enabled[1]);
table = new KanaTable();
current = NULL;
ResetTable(0);
ResetTable(1);
GetNextKana();
UpdateStatusBar();
}
/////////////////
// Get next kana
void GameDisplay::GetNextKana() {
// Select table
int tn;
if (!enabled[0]) tn = 1;
else if (!enabled[1]) tn = 0;
else tn = rand() % 2;
curTable = tn;
// Maximum number of glyphs
int n = GetNAtLevel(level[curTable],curTable);
// Add all valid positions to an array
// Each one will be added a number of times relative to its weight (1 to 20 times)
std::vector<int> picks;
int cur;
for (int i=0;i<n;i++) {
cur = scores[tn][i];
if (cur != -100) {
int weight = 10 - cur;
if (weight < 1) weight = 1;
if (weight > 20) weight = 20;
weight = weight * weight;
for (int j=0;j<weight;j++) picks.push_back(i);
}
}
// Pick one
int old = curn;
do {
curn = picks[rand()%picks.size()];
} while (old == curn);
current = &table->GetEntry(curn);
// Refresh
Refresh(false);
}
////////////////
// Enter romaji
void GameDisplay::EnterRomaji(wxString romaji) {
// Check if it's correct
bool correct = (romaji == current->hepburn);
lastEntry = romaji;
levelUp = 0;
// Get next
if (correct) {
// Add 1 to score
int temp = scores[curTable][curn] + 1;
if (temp > 10) temp = 10;
scores[curTable][curn] = temp;
// Check if it's OK to level up
int n = GetNAtLevel(level[curTable],curTable);
bool ok = true;
for (int i=0;i<n;i++) {
if (scores[curTable][i] <= 2) {
ok = false;
break;
}
}
// Level up
if (ok) {
int n2 = GetNAtLevel(level[curTable]+1,curTable);
if (n2 != n) {
levelUp = curTable+1;
level[curTable]++;
for (int i=n;i<n2;i++) scores[curTable][i] = 0;
UpdateStatusBar();
}
}
// Get next
lastTable = curTable;
previous = current;
status = 3;
GetNextKana();
Refresh(false);
}
// Wrong
else {
// Is asking?
bool isAsking = romaji == _T("?");
// Points to subtract
int toSub = 20;
if (isAsking) toSub = 2;
// Subtract points from this glyph
int temp = scores[curTable][curn] - toSub;
if (temp < -10) temp = -10;
scores[curTable][curn] = temp;
// Set status if it's asking
if (isAsking) status = 5;
// Otherwise...
else {
// Subtract points from the glyph the user typed, if it even exists
int n = table->GetNumberEntries();
for (int i=0;i<n;i++) {
if (table->GetEntry(i).hepburn == romaji) {
temp = scores[curTable][i];
if (temp == -100) break;
temp -= toSub;
if (temp < -10) temp = -10;
scores[curTable][i] = temp;
break;
}
}
// Set status
if (status == 2 || status == 4) status = 4;
else if (status == 1) status = 2;
else status = 1;
}
// Refresh
Refresh(false);
}
// Save
Save();
}
/////////////////
// Reset a table
void GameDisplay::ResetTable(int id) {
// Get entries for this table
int n = table->GetNumberEntries();
// Set size properly
scores[id].resize(n);
// Reset with -100
for (int i=0;i<n;i++) scores[id][i] = -100;
// Enable enough for the level
int leveln = GetNAtLevel(level[id],id);
for (int i=0;i<leveln;i++) scores[id][i] = 0;
}
/////////////////////////////////
// Get number of glyphs at level
int GameDisplay::GetNAtLevel(int level,int tablen) {
int max = table->GetLevels(tablen);
if (level > max) level = max;
return table->GetNumberEntries(level);
}
///////////////
// Event table
BEGIN_EVENT_TABLE(GameDisplay,wxWindow)
EVT_PAINT(GameDisplay::OnPaint)
EVT_LEFT_DOWN(GameDisplay::OnClick)
END_EVENT_TABLE()
///////////////
// Draw window
void GameDisplay::OnPaint(wxPaintEvent &event) {
// Begin drawing
wxPaintDC dc(this);
dc.BeginDrawing();
int w,h,tw,th;
GetClientSize(&w,&h);
// Background colours
//wxColour col1(130,177,236);
//wxColour col2(181,209,244);
wxColour col1(220,194,105);
wxColour col2(245,231,177);
// Draw background
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(col1);
dc.DrawRectangle(0,0,w,h);
dc.SetBrush(col2);
dc.DrawRectangle(5,5,w-10,h-10);
dc.SetBrush(col1);
dc.DrawRectangle(10,0,5,h);
dc.DrawRectangle(w-15,0,5,h);
dc.DrawRectangle(0,10,w,5);
dc.DrawRectangle(0,h-15,w,5);
// Enabled?
if (!isOn) {
dc.EndDrawing();
return;
}
// Set font
wxFont font(128,wxDEFAULT,wxFONTSTYLE_NORMAL,wxNORMAL,0,_T("MS Mincho"),wxFONTENCODING_DEFAULT );
dc.SetFont(font);
if (status == 0) dc.SetTextForeground(wxColour(0,0,0));
if (status == 1 || status == 2 || status == 4) dc.SetTextForeground(wxColour(220,10,10));
// Get text metrics
wxString text;
if (curTable == 0) text = current->hiragana;
else text = current->katakana;
dc.GetTextExtent(text,&tw,&th,NULL,NULL,&font);
// Draw text
dc.SetPen(wxColour(0,0,0));
dc.DrawText(text,(w-tw)/2,(h-th)/2);
// Prepare status text
font.SetFaceName(_T("Verdana"));
font.SetPointSize(16);
dc.SetFont(font);
wxString topString;
wxString bottomString;
wxString bottomString2;
wxString levelString;
// Correct
if (status == 3) {
dc.SetTextForeground(wxColour(0,128,0));
topString = wxString(_T("Correct! \""));
if (lastTable == 0) topString += previous->hiragana;
if (lastTable == 1) topString += previous->katakana;
topString += wxString(_T("\" is \"")) + lastEntry + _T("\"!");
}
// Wrong
if (status == 1 || status == 2 || status == 4) {
// Top text
const KanaEntry *real = table->FindByRomaji(lastEntry);
topString = wxString(_T("Wrong!"));
if (real) {
topString += _T(" \"");
if (curTable == 0) topString += real->hiragana;
if (curTable == 1) topString += real->katakana;
topString += wxString(_T("\" is \"")) + lastEntry + _T("\"!");
}
else topString += _T(" \"") + lastEntry + _T("\" doesn't exist!");
// Find hints
wxString group = current->group;
wxString vowel = current->hepburn.Right(1);
if (vowel == _T("n")) {
group = vowel;
vowel = _T("");
}
// Bottom text (hints)
bottomString = wxString(_T("Hint: \""));
if (curTable == 0) bottomString += current->hiragana;
if (curTable == 1) bottomString += current->katakana;
if (status == 4) {
bottomString += _T("\" is \"") + current->hepburn + _T("\".");
}
else {
if (group == _T("a")) {
if (status == 1) bottomString += _T("\" is a vowel.");
else bottomString += _T("\" is the vowel \"") + current->hepburn + _T("\".");
}
else {
bottomString += _T("\" is from the group of \"") + group + _T("\"");
if (status == 1 || vowel == _T("")) bottomString += _T(".");
else bottomString2 = _T("ending with the vowel \"") + vowel + _T("\".");
}
}
}
// Asking for help
if (status == 5) {
dc.SetTextForeground(wxColour(96,64,0));
bottomString = wxString(_T("\""));
if (curTable == 0) bottomString += current->hiragana;
if (curTable == 1) bottomString += current->katakana;
bottomString += _T("\" is \"") + current->hepburn + _T("\".");
}
// Level up
if (levelUp) {
levelString = _T("Level up on ");
if (levelUp == 1) levelString += _T("hiragana!");
if (levelUp == 2) levelString += _T("katakana!");
}
// Draw top text
int th2=0,tw2=0;
dc.GetTextExtent(topString,&tw,&th,NULL,NULL,&font);
dc.DrawText(topString,(w-tw)/2,20);
font.SetPointSize(14);
dc.SetFont(font);
dc.GetTextExtent(levelString,&tw2,&th2,NULL,NULL,&font);
dc.DrawText(levelString,(w-tw2)/2,20+th);
// Draw bottom text
font.SetPointSize(12);
dc.SetFont(font);
dc.GetTextExtent(bottomString,&tw,&th,NULL,NULL,&font);
if (!bottomString2.IsEmpty()) dc.GetTextExtent(bottomString2,&tw2,&th2,NULL,NULL,&font);
else {
tw2 = 0;
th2 = 0;
}
dc.DrawText(bottomString,(w-tw)/2,h-20-th-th2);
dc.DrawText(bottomString2,(w-tw2)/2,h-20-th2);
// Done
dc.EndDrawing();
}
/////////////////////
// Update status bar
void GameDisplay::UpdateStatusBar() {
statusBar->SetStatusText(playerName,0);
if (enabled[0]) statusBar->SetStatusText(wxString::Format(_T("Hiragana lvl %i"),level[0]),1);
else statusBar->SetStatusText(_T("Hiragana disabled"),1);
if (enabled[1]) statusBar->SetStatusText(wxString::Format(_T("Katakana lvl %i"),level[1]),2);
else statusBar->SetStatusText(_T("Katakana disabled"),2);
}
//////////////////////////
// Enable/disable a table
void GameDisplay::EnableTable(int tn,bool status) {
if (enabled[tn] != status) {
enabled[tn] = status;
if (status == false && curTable == tn) {
if (enabled[1-curTable] == false) {
enabled[1-curTable] = true;
menuCheck[1-curTable]->Check(true);
}
status = 0;
GetNextKana();
}
}
UpdateStatusBar();
}
/////////////
// Set level
void GameDisplay::SetLevel(int tablen,int lvl) {
// Set values
int max = table->GetNumberEntries();
int n = table->GetNumberEntries(lvl);
for (int i=0;i<n;i++) if (scores[tablen][i] == -100) scores[tablen][i] = 0;
for (int i=n;i<max;i++) scores[tablen][i] = -100;
level[tablen] = lvl;
// Check if currently shown image needs to be switched
if (tablen == curTable && current->level > lvl) {
status = 0;
GetNextKana();
}
// Update status
UpdateStatusBar();
}
///////////////
// Enable game
void GameDisplay::EnableGame(bool enable) {
UpdateStatusBar();
if (isOn == enable) return;
isOn = enable;
Refresh(false);
}
/////////////
// Save game
void GameDisplay::Save() {
// Player name set?
if (playerName.IsEmpty()) return;
// Open file
wxString path = KanaMemo::folderName + _T("/") + playerName + _T(".usr");
FILE *fp = fopen(path.mb_str(wxConvLocal),"wb");
if (!fp) return;
// Write version
char buffer[128];
strcpy(buffer,"kanamemo V2");
fwrite(buffer,1,12,fp);
// Write enabled status
char temp;
temp = enabled[0] ? 1 : 0;
fwrite(&temp,1,1,fp);
temp = enabled[1] ? 1 : 0;
fwrite(&temp,1,1,fp);
// Write levels
temp = level[0];
fwrite(&temp,1,1,fp);
temp = level[1];
fwrite(&temp,1,1,fp);
// Write autolevel flag
temp = autoLevel;
fwrite(&temp,1,1,fp);
// Write scores
int temp2;
temp2 = scores[0].size();
fwrite(&temp2,1,4,fp);
for (int i=0;i<temp2;i++) fwrite(&scores[0][i],1,4,fp);
temp2 = scores[1].size();
fwrite(&temp2,1,4,fp);
for (int i=0;i<temp2;i++) fwrite(&scores[1][i],1,4,fp);
// Close file
fclose(fp);
}
/////////////
// Load game
void GameDisplay::Load() {
// Player name set?
if (playerName.IsEmpty()) return;
// Open file
wxString path = KanaMemo::folderName + _T("/") + playerName + _T(".usr");
FILE *fp = fopen(path.mb_str(wxConvLocal),"rb");
if (!fp) return;
// Reset
Reset();
// Read version
int version;
char buffer[128];
fread(&buffer,1,12,fp);
if (strcmp("kanamemo V2",buffer) == 0) version = 2;
else if (strcmp("kanamemo V1",buffer) == 0) version = 1;
else {
fclose(fp);
return;
}
// Read enabled status
char temp;
fread(&temp,1,1,fp);
enabled[0] = temp == 1;
fread(&temp,1,1,fp);
enabled[1] = temp == 1;
// Read levels
fread(&temp,1,1,fp);
level[0] = temp;
fread(&temp,1,1,fp);
level[1] = temp;
// Read autolevel flag
if (version >= 2) {
fread(&temp,1,1,fp);
autoLevel = temp == 1;
}
// Read scores
int temp2;
fread(&temp2,1,4,fp);
scores[0].resize(temp2);
for (int i=0;i<temp2;i++) fread(&scores[0][i],1,4,fp);
fread(&temp2,1,4,fp);
scores[1].resize(temp2);
for (int i=0;i<temp2;i++) fread(&scores[1][i],1,4,fp);
// Close file
fclose(fp);
// Update
menuCheck[0]->Check(enabled[0]);
menuCheck[1]->Check(enabled[1]);
UpdateStatusBar();
GetNextKana();
Refresh(false);
}
/////////////////////////
// Click on display area
void GameDisplay::OnClick(wxMouseEvent &event) {
panel->enterField->SetFocus();
}