程式碼

測驗中心

            # atestcenter.py
from adb import DB

class TestCenter():
    def __init__(self):
        self.menu_title = '測驗中心'
        self.menu = {
            'a':'單字建檔查詢',
            'b':'選擇題型設定',
            'c':'填充題型設定',
            'd':'參測考生列表',
            'e':'測驗成績統計',
            'f':'個人成績查詢',
            'q':'離開',
        }
        self.menu_func = {
            'a': lambda db, ft: self.create_words(db, ft),
            'b': lambda db, ft: self.set_multiple_choice(db, ft),
            'c': lambda db, ft: self.set_fill_in_the_blank(db, ft),
            'd': lambda db, ft: self.examinees(db, ft),
            'e': lambda db, ft: self.summary(db, ft),
            'f': lambda db, ft: self.score_list(db, ft),
        }
        self.divider = '='*20

    def show_menu(self):
        """ 主選單
        """
        print(self.divider)
        print(self.menu_title)
        print(self.divider)
        for fid, fname in self.menu.items():
            print('%s:%s' % (fid, fname))
        print(self.divider)
        opt = input('請選擇: ').lower()
        if opt in self.menu.keys():
            return opt, self.menu[opt]
        else:
            return '', '無此功能!'

    def create_words(self, db, func_title):
        """ 單字建檔查詢
        """
        while True:
            subopt = input('1.增修 2.查詢 3.刪除 4.列表 exit.離開: ')
            if subopt == 'exit':
                break
            else:
                print('單字數:', db.count_words())

                if subopt == '4':
                    db.list_all_words()
                    continue

                if subopt in ('1', '2', '3'):
                    in_word = input('單字: ')

                if subopt == '1':
                    in_def = input('定義: ')
                    db.insert_or_update_word(in_word, in_def)
                    db.check_word_out(in_word)
                elif subopt == '2':
                    db.check_word_out(in_word)
                elif subopt == '3':
                    db.delete_word(in_word)
                else:
                    db.check_word_out(subopt)
        return func_title

    def set_multiple_choice(self, db, func_title):
        """ 選擇題型設定
        """
        set_type = 'multiple_choice'
        while True:
            db.get_settings(set_type, 'show')
            subopt = input('3.三選一 4.四選一 5.五選一 exit.離開: ')
            if subopt == 'exit':
                break
            else:
                qnum = input('請輸入題數: ')
                if qnum.isdigit() and subopt in('3', '4', '5'):
                    db.update_settings(set_type, int(qnum), int(subopt))
                else:
                    print('設定錯誤')
        return func_title

    def set_fill_in_the_blank(self, db, func_title):
        """ 填充題型設定
        """
        set_type = 'fill_in_the_blank'
        while True:
            db.get_settings(set_type, 'show')
            subopt = input('1.提示首字母 2.提示首尾字母 exit.離開: ')
            if subopt == 'exit':
                break
            else:
                qnum = input('請輸入題數: ')
                if qnum.isdigit() and subopt in('1', '2'):
                    db.update_settings(set_type, int(qnum), int(subopt))
                else:
                    print('設定錯誤')
        return func_title

    def examinees(self, db, func_title):
        """ 參測考生列表
        """
        db.list_all_examinees()
        return func_title

    def summary(self, db, func_title):
        """ 測驗成績統計
        """
        db.score_summary()
        return func_title

    def score_list(self, db, func_title):
        """ 個人成績查詢
        """
        while True:
            qaccount = input('請輸入帳號 (exit.離開): ')
            if qaccount == 'exit':
                break
            db.list_scores_by_account(qaccount)
        return func_title

# entry point
with DB() as db:
    atestcenter = TestCenter()
    while True:
        func_id, func_name = atestcenter.show_menu()
        if func_id == 'q':
            break
        elif func_id == '':
            print(func_name)
        else:
            atestcenter.menu_func[func_id](db, func_name)
        print()
            
        

參測考生

            # aexaminee.py
from adb import DB

class Examinee():
    def __init__(self):
        self.menu_title = '參測考生'
        self.account = ''
        self.menu = {
            'a':'登入.註冊',
            'b':'選擇題測驗',
            'c':'填充題測驗',
            'd':'個人成績查詢',
            'e':'個人資料修改',
            'f':'亂數播放單字',
            'q':'離開',
        }
        self.menu_func = {
            'a': lambda db, ft: self.login_or_signup(db, ft),
            'b': lambda db, ft: self.test_multiple_choice(db, ft),
            'c': lambda db, ft: self.test_fill_in_the_blank(db, ft),
            'd': lambda db, ft: self.score_list(db, ft),
            'e': lambda db, ft: self.profile(db, ft),
            'f': lambda db, ft: self.show_words_randomly(db, ft),
        }
        self.divider = '='*20

    def show_menu(self, account=''):
        """ 主選單
        """
        print(self.divider)
        if self.account == '':
            print(self.menu_title, '尚未登入')
        else:
            print(self.menu_title, self.account)
        print(self.divider)
        for fid, fname in self.menu.items():
            print('%s:%s' % (fid, fname))
        print(self.divider)
        opt = input('請選擇: ').lower()
        if opt in self.menu.keys():
            return opt, self.menu[opt]
        else:
            return '', '無此功能!'

    def login_or_signup(self, db, func_title):
        """ 登入.註冊
        """
        account_input = input('請輸入帳號: ')
        if db.check_if_examinee_existed(account_input):
            self.account = account_input
            db.print_examinee_info(self.account)
        else:
            if db.insert_or_update_examinee(account_input, 'insert'):
                print('註冊成功,可立即參加測驗')

    def print_definitions(self, word_def):
        for item in word_def.split('||'):
            print('  ', item)

    def test_multiple_choice(self, db, func_title):
        """ 選擇題測驗
        """
        from random import choice, randint
        set_type = 'multiple_choice'
        qtype, qnums = db.get_settings(set_type, 'read')
        list_all_words = db.generate_dict_words()
        list_questions = []
        # 設定題目及答案
        for i in range(qnums):
            qitem = choice(list_all_words)
            if qitem not in list_questions:
                list_questions.append(qitem)
        # print(list_questions)

        # 出卷
        right_count = 0
        for qindex, qitem in enumerate(list_questions):
            # 出題
            print('第 %s 題: %s' % (qindex+1, qitem['WORD']))
            qitem_ans = randint(1, qtype)
            for i in range(1, qtype+1):
                if i == qitem_ans:
                    print('%s.' % i)
                    self.print_definitions(qitem['DEFS'])
                else:
                    while True:
                        qitem_other = choice(list_all_words)
                        if qitem_other != qitem:
                            print('%s.' % i)
                            self.print_definitions(qitem_other['DEFS'])
                            break
            # 答題
            ex_choice = input('請選答 (exit.中止): ')
            if ex_choice == 'exit':
                return func_title
            elif ex_choice == str(qitem_ans):
                right_count += 1
            print('>'*3)

        # 計算答對題數及分數
        score = round(right_count/qnums*100)
        print('答對題數 %s/%s 分數 %s' % (right_count, qnums, score))
        db.insert_score(self.account, set_type, score)
        return func_title

    def test_fill_in_the_blank(self, db, func_title):
        """ 填充題測驗
        """
        from random import choice
        set_type = 'fill_in_the_blank'
        qtype, qnums = db.get_settings(set_type, 'read')
        list_all_words = db.generate_dict_words()
        list_questions = []
        # 設定題目及答案
        for i in range(qnums):
            qitem = choice(list_all_words)
            if qitem not in list_questions:
                list_questions.append(qitem)
        # print(list_questions)

        # 出卷
        right_count = 0
        for qindex, qitem in enumerate(list_questions):
            # 出題
            qitem_ans = qitem['WORD']
            if qtype == 1:
                qword = qitem_ans[0] + '-'*(len(qitem_ans)-qtype)
            elif qtype == 2:
                qword = qitem_ans[0] + '-'*(len(qitem_ans)-qtype) + qitem_ans[-1]
            print('第 %s 題: %s' % (qindex+1, qword))
            for item in qitem['DEFS'].split('||'):
                print('  ', item)

            # 答題
            ex_input = input('請選答 (exit.中止): ')
            if ex_input == 'exit':
                return func_title
            elif ex_input == qitem_ans:
                right_count += 1
            print('正確為:', qitem_ans)
            print('>'*3)

        # 計算答對題數及分數
        score = round(right_count/qnums*100)
        print('答對題數 %s/%s 分數 %s' % (right_count, qnums, score))
        db.insert_score(self.account, set_type, score)
        return func_title

    def score_list(self, db, func_title):
        """ 個人成績查詢
        """
        db.list_scores_by_account(self.account)
        return func_title

    def profile(self, db, func_title):
        """ 個人資料修改
        """
        if db.insert_or_update_examinee(self.account, 'update'):
            print('--- 資料已更新 ---')
            db.print_examinee_info(self.account)
        else:
            print('--- 資料未更新 ---')
        return func_title

    def show_words_randomly(self, db, func_title):
        """ 亂數播放單字
        """
        import time
        from random import choice
        word_num = input('請輸入單字數量 (10~30): ')
        sleep_sec = input('請輸入停頓秒數 (0~5): ')
        if word_num.isdigit() and sleep_sec.isdigit():
            word_num = int(word_num)
            sleep_sec = int(sleep_sec)
            if sleep_sec not in range(6):
                sleep_sec = 0

            if word_num in range(10, 31):
                list_all_words = db.generate_dict_words()
                for i in range(word_num):
                    w = choice(list_all_words)
                    # 停頓
                    if sleep_sec > 0:
                        time.sleep(sleep_sec)
                    print(w['WORD']+':')
                    self.print_definitions(w['DEFS'])
            else:
                print('範圍錯誤')
        else:
            print('輸入錯誤')
        return func_title

# entry point
with DB() as db:
    aexaminee = Examinee()
    while True:
        func_id, func_name = aexaminee.show_menu()
        if func_id == 'q':
            break
        elif func_id == '':
            print(func_name)
        else:
            if aexaminee.account == '':
                func_id = 'a'
                print('請先登入或註冊')
            aexaminee.menu_func[func_id](db, func_name)
        print()
            
        

資料庫模組

            # adb.py
class DB():
    def __init__(self):
        self.conn = None
        self.cur = None
        self.title_side = '-'*12

    def __enter__(self):
        self.open()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()
        return False

    def open(self):
        """ 開啟資料庫連線
        """
        if self.conn is None:
            import sqlite3
            self.conn = sqlite3.connect('toeic.db')
            self.cur = self.conn.cursor()
        return True

    def close(self):
        """ 關閉資料庫連線
        """
        if self.conn is not None:
            self.conn.close()
            self.conn = None
        return True

    def count_words(self):
        """ 計算單字數
        """
        self.cur.execute("SELECT COUNT(*) FROM WORDS")
        return self.cur.fetchone()[0]

    def get_max_id(self, arg_table):
        """ 取得資料最新編號
        """
        self.cur.execute("SELECT MAX(ID) FROM {}".format(arg_table))
        return self.cur.fetchone()[0] + 1

    def list_all_words(self):
        """ 單字列表
        """
        self.cur.execute("SELECT * FROM WORDS")
        all_rows = self.cur.fetchall()
        for row in all_rows:
            print('{:04d} {}:'.format(row[0], row[1]))
            for item in row[2].split('||'):
                print('  ', item)
            print()
        print()

    def generate_dict_words(self):
        """ 設定單字字典
        """
        self.cur.execute("SELECT WORD, DEFINITIONS FROM WORDS")
        all_words = self.cur.fetchall()
        list_words = [dict(WORD=row[0], DEFS=row[1]) for row in all_words]
        return list_words

    def insert_or_update_word(self, arg_word, arg_def):
        """ 增修單字
        """
        words_max_id = self.get_max_id('WORDS')
        self.cur.execute("SELECT COUNT(*) FROM WORDS WHERE WORD=?", (arg_word,))
        count_result = self.cur.fetchone()[0]
        if count_result == 0:
            self.cur.execute("INSERT INTO WORDS VALUES (?, ?, ?)",
                (words_max_id, arg_word, arg_def))
        elif count_result > 0:
            self.cur.execute("UPDATE WORDS SET DEFINITIONS=? WHERE WORD=?", (arg_def, arg_word))
        return self.conn.commit()

    def check_word_out(self, arg_word):
        """ 查詢單字
        """
        arg_word = '%'+arg_word+'%'
        self.cur.execute("SELECT * FROM WORDS WHERE WORD LIKE ?", (arg_word,))
        result = self.cur.fetchall()
        for word in result:
            print('{:04d} {}:'.format(word[0], word[1]))
            for item in word[2].split('||'):
                print('  ', item)
            print()

    def delete_word(self, arg_word):
        """ 刪除單字
        """
        self.cur.execute("DELETE FROM WORDS WHERE WORD=?", (arg_word,))
        return self.conn.commit()

    def get_settings(self, arg_type, arg_opt):
        """ 擷取題型設定
        """
        self.cur.execute("SELECT OPTION, NUM_OF_QUESTIONS FROM SETTINGS WHERE TYPE=?", (arg_type,))
        settings = self.cur.fetchone()
        print('設定選項:%s 題數:%s' % settings)
        if arg_opt == 'read':
            return settings

    def update_settings(self, arg_type, arg_qnum, arg_opt):
        """ 更新題型設定
        """
        self.cur.execute("UPDATE SETTINGS SET NUM_OF_QUESTIONS=?, OPTION=? WHERE TYPE=?",
            (arg_qnum, arg_opt, arg_type))
        return self.conn.commit()

    def list_all_examinees(self):
        """ 考生列表
        """
        self.cur.execute("SELECT * FROM EXAMINEES")
        all_rows = self.cur.fetchall()
        print('{:3} {:8} {:18} {:4} {:6}'.format('ID', '帳號', '姓名', '性別', '出生年'))
        print('-'*50)
        for row in all_rows:
            print('{:3} {:10} {:20} {:<4} {:6}'.format(*row))
        print()

    def check_if_examinee_existed(self, arg_account):
        """ 檢查考生是否註冊
        """
        self.cur.execute("SELECT COUNT(*) FROM EXAMINEES WHERE ACCOUNT=?", (arg_account,))
        if self.cur.fetchone()[0] == 1:
            return True
        else:
            return False

    def insert_or_update_examinee(self, account_id, action):
        """ 增修考生
        """
        data_ok = True
        full_name = input('姓名: ')
        if full_name == 'q':
            return False

        gender = input('性別(F.女性 M.男性): ').upper()
        if gender not in ('F', 'M'):
            data_ok = False

        birth_year = input('出生年 (1970~2020): ')
        if birth_year.isdigit():
            birth_year = int(birth_year)
            if birth_year not in range(1950, 2020):
                data_ok = False
        else:
            data_ok = False

        # 資料無誤,准許註冊
        if data_ok:
            if action == 'insert':
                examinees_max_id = self.get_max_id('EXAMINEES')
                self.cur.execute("INSERT INTO EXAMINEES VALUES (?, ?, ?, ?, ?)",
                    (examinees_max_id, account_id, full_name, gender, birth_year))
            elif action == 'update':
                self.cur.execute("UPDATE EXAMINEES SET NAME=?, GENDER=?, BIRTH_YEAR=? WHERE ACCOUNT=?",
                    (full_name, gender, birth_year, account_id))
            self.conn.commit()
            return True
        else:
            return False

    def print_examinee_info(self, account):
        """ 查詢考生資訊
        """
        self.cur.execute("SELECT * FROM EXAMINEES WHERE ACCOUNT=?", (account,))
        examinee = self.cur.fetchone()
        print('編號: {}  帳號: {}\n姓名: {}\n性別: {}\n出生年: {}'.format(*examinee))

    def insert_score(self, account, ex_type, ex_score):
        """ 增修單字
        """
        scores_max_id = self.get_max_id('SCORES')
        self.cur.execute("INSERT INTO SCORES VALUES (?, ?, ?, ?, date('now'))",
            (scores_max_id, account, ex_type, ex_score))
        return self.conn.commit()

    def sql_case_type(self):
        return '''CASE TYPE WHEN 'multiple_choice' THEN '選擇題'
                            WHEN 'fill_in_the_blank' THEN '填空題' END TYPE'''

    def show_one_score(self, row):
        print('{:3} {:10} {:3} {:4} {:10}'.format(*row))

    def list_scores_by_account(self, account):
        """ 個人成績查詢
        """
        case_type = self.sql_case_type()
        account_like = ''.join(('%', account, '%'))

        self.cur.execute('''SELECT ID, ACCOUNT, {}, SCORE, DATE_TIME FROM SCORES
            WHERE ACCOUNT LIKE ? ORDER BY DATE_TIME DESC'''.format(case_type), (account_like,))
        all_rows = self.cur.fetchall()
        if len(all_rows):
            for row in all_rows:
                self.show_one_score(row)
        else:
            print(account, '查無任何成績')

    def score_summary(self):
        """ 測驗成績統計
        """
        case_type = self.sql_case_type()

        print(self.title_side, '測驗人數及平均分數', self.title_side)
        sql = "SELECT {}, COUNT(*), AVG(SCORE) FROM SCORES GROUP BY TYPE"
        self.cur.execute(sql.format(case_type))
        all_rows = self.cur.fetchall()
        if len(all_rows):
            for row in all_rows:
                print('{:3} {:4} {:6}'.format(*row))
        print(self.title_side, '成績列表依日期降冪', self.title_side)
        sql = "SELECT ID, ACCOUNT, {}, SCORE, DATE_TIME FROM SCORES ORDER BY DATE_TIME DESC"
        self.cur.execute(sql.format(case_type))
        all_rows = self.cur.fetchall()
        if len(all_rows):
            for row in all_rows:
                self.show_one_score(row)

if __name__ == '__main__':
    print('This is the DB class.')