Team LRN
Game Developer’s Guide to Cybiko™
Ernest Pazera
Wordware Publishing, Inc.
Team LRN
Library of Congress C...
114 downloads
906 Views
13MB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
Team LRN
Game Developer’s Guide to Cybiko™
Ernest Pazera
Wordware Publishing, Inc.
Team LRN
Library of Congress Cataloging-in-Publication Data Pazera, Ernest. Game developer’s guide to Cybiko / by Ernest Pazera. p. cm. ISBN 1-55622-854-6 (pbk.) 1. Computer games--Programming. I. Title. QA76.76.C672 P39 2001 794.8'167765--dc21
2001046843 CIP
© 2002, Wordware Publishing, Inc. All Rights Reserved 2320 Los Rios Boulevard Plano, Texas 75074
No part of this book may be reproduced in any form or by any means without permission in writing from Wordware Publishing, Inc. Printed in the United States of America
ISBN 1-55622-854-6 10 9 8 7 6 5 4 3 2 1 0110
Cybiko is a trademark of Cybiko, Inc. Other product names mentioned are used for identification purposes only and may be trademarks of their respective companies.
All inquiries for volume purchases of this book should be addressed to Wordware Publishing, Inc., at the above address. Telephone inquiries may be made by calling: (972) 423-0090
Team LRN
Contents Foreword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvi Chapter 1 Welcome . . . . . . . . . . . Overview . . . . . . . . . . . . . . . . . . . . About Me . . . . . . . . . . . . . . . . . . . . About You . . . . . . . . . . . . . . . . . . . . Things You Will Need. . . . . . . . . . . . . . Configuring the Software. . . . . . . . . . . . CyberLoad . . . . . . . . . . . . . . . . . . Cybiko SDK . . . . . . . . . . . . . . . . . Text Editor . . . . . . . . . . . . . . . . . . Test CyberLoad and the CyConsole . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
1 1 1 1 2 2 2 3 3 3
Chapter 2 Your First Cybiko Application. . . . . . . . . . . . . . . . . . . . . . 4 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 A Simple Cybiko “Hello, world!” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Res . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Src . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Trying Out CyBk2_1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 A Real Cybiko “Hello, world” Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Res . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Root.inf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Root.spl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Root.ico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Intro.pic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Src Subfolder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Filer.list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 CyBk2_2’s Src Directory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Chapter 3 Introduction to Message Handling Overview. . . . . . . . . . . . . . . . . . . . . . . . Message Queues . . . . . . . . . . . . . . . . . . . Initializing the Module . . . . . . . . . . . . . . . . Grabbing Messages . . . . . . . . . . . . . . . . . . cWinApp_get_message . . . . . . . . . . . . . . Message_delete . . . . . . . . . . . . . . . . . . cWinApp_peek_message . . . . . . . . . . . . . Contents of a Message . . . . . . . . . . . . . . . . Common Messages . . . . . . . . . . . . . . . . . . Keyboard Messages . . . . . . . . . . . . . . . . Default Message Processing . . . . . . . . . . . . . A Basic Message Pump . . . . . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . .
17 17 17 18 19 19 21 21 23 24 25 27 28
iii
Team LRN
Contents
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Chapter 4 Resources and Development Tools . Overview. . . . . . . . . . . . . . . . . . . . . . . . . Resources Defined . . . . . . . . . . . . . . . . . . . Text Resources . . . . . . . . . . . . . . . . . . . . Picture Resources . . . . . . . . . . . . . . . . . . Music Resources . . . . . . . . . . . . . . . . . . . Other Binary Resources . . . . . . . . . . . . . . . Special Resources (Root.*, Intro.pic, and 0.help) . . . Root.ico . . . . . . . . . . . . . . . . . . . . . . . . Root.inf . . . . . . . . . . . . . . . . . . . . . . . . Root.spl . . . . . . . . . . . . . . . . . . . . . . . . Intro.pic . . . . . . . . . . . . . . . . . . . . . . . . 0.help . . . . . . . . . . . . . . . . . . . . . . . . . Compiling Resources with Makeres.bat . . . . . . . . 2mus . . . . . . . . . . . . . . . . . . . . . . . . . mus2txt . . . . . . . . . . . . . . . . . . . . . . . . t2mf . . . . . . . . . . . . . . . . . . . . . . . . . . 2pic . . . . . . . . . . . . . . . . . . . . . . . . . . PicView . . . . . . . . . . . . . . . . . . . . . . . . Third-Party Resource Conversion Tools . . . . . . . . Fontmake.exe. . . . . . . . . . . . . . . . . . . . . Bseqmake.exe . . . . . . . . . . . . . . . . . . . . Adding Resources Using Filer.list . . . . . . . . . . . Makefiles. . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
30 30 30 31 31 31 31 32 32 32 33 33 33 34 35 35 35 36 37 38 38 39 41 42 44
Chapter 5 The Cybiko Way of Object-Oriented Programming Overview. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . OOP (Object-Oriented Programming) . . . . . . . . . . . . . . . . . . Encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Constructors and Destructors . . . . . . . . . . . . . . . . . . . . . . Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dynamic Allocation Constructors . . . . . . . . . . . . . . . . . . . Destructors. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . .
45 45 45 46 47 49 49 49 50 51 52
Chapter 6 Program Frameworks Overview. . . . . . . . . . . . . . . . Quick-Running Framework . . . . . . Event-Driven Framework . . . . . . Idle-Loop Framework . . . . . . . . . Real-Time Framework . . . . . . . . Dialog-Based Framework . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . .
53 53 53 54 62 63 64
. . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . .
iv
Team LRN
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . .
Contents
Multi-threaded Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Chapter 7 Messages . . . . . Overview. . . . . . . . . . . . . . Event-Driven Operating Systems The Message Class . . . . . . . . Types of Messages . . . . . . . . Retrieving Messages . . . . . . . Handling Messages . . . . . . . . Keyboard Messages . . . . . . MSG_KEYDOWN . . . . . MSG_CHARTYPED . . . . MSG_KEYUP . . . . . . . . The KeyParam Struct. . . . Focus Messages . . . . . . . . Quit and Shut Up . . . . . . . . Sending Messages. . . . . . . . . User-Defined Messages . . . . . Summary . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
66 66 66 68 69 69 70 71 71 71 72 72 73 74 74 75 75
Chapter 8 Basic Graphics . . . . . . . . Overview. . . . . . . . . . . . . . . . . . . . . TGraph, Graphics, and DisplayGraphics . . . . Creating a DisplayGraphics Object . . . . . Clearing the Screen . . . . . . . . . . . . . . . color_t . . . . . . . . . . . . . . . . . . . . DisplayGraphics_fill_screen. . . . . . . . . DisplayGraphics_show. . . . . . . . . . . . Screen Filling Example . . . . . . . . . . . Plotting Pixels . . . . . . . . . . . . . . . . . . Pixel Plotting Example . . . . . . . . . . . Retrieving Pixels . . . . . . . . . . . . . . . . Drawing Lines . . . . . . . . . . . . . . . . . . Setting the Current Color . . . . . . . . . . Horizontal Lines . . . . . . . . . . . . . . . Vertical Lines. . . . . . . . . . . . . . . . . Free-Form Lines . . . . . . . . . . . . . . . Random Lines Example . . . . . . . . . . . Rectangles . . . . . . . . . . . . . . . . . . . . Framed Rectangles. . . . . . . . . . . . . . Filled Rectangles . . . . . . . . . . . . . . . rect_t . . . . . . . . . . . . . . . . . . . . . The rect_t Functions. . . . . . . . . . . . . rect_set . . . . . . . . . . . . . . . . . . rect_and . . . . . . . . . . . . . . . . . . rect_or . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
77 77 77 78 78 78 79 79 80 82 82 84 85 85 85 86 86 87 88 88 89 90 90 90 91 91
v
Team LRN
Contents
Rectangle Drawing Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 Chapter 9 Bitmap and Font Basics . . . . . . . . . . . . . . . . . . . . . . . . 94 Overview. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 Bitmap Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 Creating a Bitmap Resource. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Bitmap_ctor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Bitmap_ctor_Ex1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Bitmap_ctor_Ex2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 Bitmap_ctor_Ex3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 Drawing a Bitmap on Screen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Draw Modes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 Cleaning Up after a Bitmap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 Bitmap Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 Font Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 Global Fonts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 Setting the Current Font . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 Writing Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 Formatting Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 Font Demo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 Chapter 10 Sounds, Music, and Vibration Overview . . . . . . . . . . . . . . . . . . . . . Beeps . . . . . . . . . . . . . . . . . . . . . . . Tone Generation . . . . . . . . . . . . . . . . . Raw Tone Generation . . . . . . . . . . . . . . MSequence . . . . . . . . . . . . . . . . . . . . MSequence_ctor . . . . . . . . . . . . . . . MSequence_dtor . . . . . . . . . . . . . . . Playing and Stopping . . . . . . . . . . . . . Information Functions . . . . . . . . . . . . Muting . . . . . . . . . . . . . . . . . . . . . MSequence Example . . . . . . . . . . . . . The Voice of Sanity . . . . . . . . . . . . . . Enabling and Disabling Tone Generation . . . . is_tone_playing . . . . . . . . . . . . . . . . get_sounds_enabled and enable_sounds . . Key Clicks . . . . . . . . . . . . . . . . . . . . Enabling and Disabling Key Clicks . . . . . Advanced Disables . . . . . . . . . . . . . . Kinds of Clicks . . . . . . . . . . . . . . . . Vibration . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . .
vi
Team LRN
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
112 112 112 113 114 115 115 116 116 117 118 118 118 118 119 119 120 120 120 121 122 122
Contents
Chapter 11 Basic Dialogs . . . . . . . . . . . Overview . . . . . . . . . . . . . . . . . . . . . . . Cybiko Dialogs . . . . . . . . . . . . . . . . . . . . cDialog_ctor. . . . . . . . . . . . . . . . . . . . cDialog Styles . . . . . . . . . . . . . . . . . cDialog_dtor. . . . . . . . . . . . . . . . . . . . cDialog_ShowModal . . . . . . . . . . . . . . . cDialog_GetEditText and cDialog_SetEditText. Simple Message Box. . . . . . . . . . . . . . . . . The Cybiko Way . . . . . . . . . . . . . . . . . My Way . . . . . . . . . . . . . . . . . . . . . . Simple Input Box. . . . . . . . . . . . . . . . . . . The Cybiko Way . . . . . . . . . . . . . . . . . My Way . . . . . . . . . . . . . . . . . . . . . . Interface Standards . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
123 123 123 123 124 127 127 128 129 129 131 132 132 133 135 135
Chapter 12 Some Miscellaneous Basics . . . Overview . . . . . . . . . . . . . . . . . . . . . . . Input Files . . . . . . . . . . . . . . . . . . . . . . Opening a Resource . . . . . . . . . . . . . . . Opening an External File. . . . . . . . . . . . . Destroying an Input or FileInput Object . . . . Retrieving Information about an Input Stream . Reading from an Input Stream . . . . . . . . . . Maneuvering through an Input Stream . . . . . The Wait Icon. . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
136 136 136 137 137 138 138 139 139 140 141
Chapter 13 A Simple Cybiko Game . . . . Overview . . . . . . . . . . . . . . . . . . . . . Game Theory. . . . . . . . . . . . . . . . . . . What is a Game? . . . . . . . . . . . . . . . What Purpose Do Games Serve? . . . . . . Where Do Games Come From? . . . . . . . . . Game Design . . . . . . . . . . . . . . . . . . . Game Analysis Case Study: Pac-Man . . . . Fleshing Out the Design . . . . . . . . . . . Simulation . . . . . . . . . . . . . . . . . . . Making the Game . . . . . . . . . . . . . . . . Faux Functions . . . . . . . . . . . . . . . . Extending the MainCharacter Class. . . . . The Monster Class . . . . . . . . . . . . . . Base Class for Monster and MainCharacter Deriving MainCharacter from Character . . MainCharacter Implementation . . . . . . . Monster Implementation . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
142 142 142 142 143 144 144 144 147 147 148 150 151 152 153 154 155 156
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
vii
Team LRN
Contents
Levels. . . . . . . . . . . . . Dots and Power Pellets . . . Where Do We Go from Here Summary . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
156 161 163 163
Chapter 14 Intermediate Graphics . . . . . Overview . . . . . . . . . . . . . . . . . . . . . . Writing to Bitmaps . . . . . . . . . . . . . . . . . Constructing/Destructing a Graphics Object . Setting and Getting the Bitmap . . . . . . . . Attributes of a Graphics Object . . . . . . . . Font. . . . . . . . . . . . . . . . . . . . . . Color . . . . . . . . . . . . . . . . . . . . . Draw Mode. . . . . . . . . . . . . . . . . . Background Color . . . . . . . . . . . . . . Clipping Rectangle. . . . . . . . . . . . . . Drawing Primitives . . . . . . . . . . . . . . . Text Metrics . . . . . . . . . . . . . . . . . . Drawing Bitmaps and Text . . . . . . . . . . . Other Functions. . . . . . . . . . . . . . . . . Graphics_put_background. . . . . . . . . . Graphics_fill_screen . . . . . . . . . . . . . Graphics_scroll . . . . . . . . . . . . . . . More Functions to Come... . . . . . . . . . . . Draw Modes . . . . . . . . . . . . . . . . . . . . Bitmap Draw Modes . . . . . . . . . . . . . . . . The BitmapSequence Class . . . . . . . . . . . . Construction and Destruction . . . . . . . . . Retrieving Information . . . . . . . . . . . . . The Font Class . . . . . . . . . . . . . . . . . . . Construction and Destruction . . . . . . . . . Attributes of a Font Object . . . . . . . . . . . Name . . . . . . . . . . . . . . . . . . . . . Fixed-Width and Proportional Fonts . . . . Spacing . . . . . . . . . . . . . . . . . . . . Bitmap Array. . . . . . . . . . . . . . . . . Size . . . . . . . . . . . . . . . . . . . . . . Image Offsets . . . . . . . . . . . . . . . . Text Metrics . . . . . . . . . . . . . . . . . . Loading a Font Object from an Input Stream . Splitting Strings. . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
164 164 164 164 165 166 166 166 166 167 167 168 168 169 169 169 169 170 170 170 173 174 174 175 175 176 176 176 176 177 177 177 178 179 179 179 180
Chapter 15 Form and Menu Basics . . . . Overview . . . . . . . . . . . . . . . . . . . . . Structure of the UI System . . . . . . . . . . . cObject . . . . . . . . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . . . . . . . . . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . . . . . . . . . . . . . . . . . . . . .
181 181 181 182
. . . .
viii
Team LRN
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
Contents
cClip . . . . . . . . . . . . . . . . . . . . cEngine. . . . . . . . . . . . . . . . . cCustomForm . . . . . . . . . . . . . cList . . . . . . . . . . . . . . . . . . Making a cCustomForm . . . . . . . . . . . Adding Controls to the Form. . . . . . . Creating Simple Objects for Your Form . cBevel . . . . . . . . . . . . . . . . . cBitmap . . . . . . . . . . . . . . . . cBox . . . . . . . . . . . . . . . . . . cText . . . . . . . . . . . . . . . . . . Interacting with Controls. . . . . . . . . cButton . . . . . . . . . . . . . . . . . cEdit . . . . . . . . . . . . . . . . . . cList and cItem . . . . . . . . . . . . . . cList . . . . . . . . . . . . . . . . . . cItem . . . . . . . . . . . . . . . . . . Custom Form Behavior . . . . . . . . . . . cObject and cClip Common Functionality . cObject Common Functionality . . . . . cClip Common Functionality . . . . . . . Summary . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
182 182 183 183 183 184 185 185 186 187 188 188 189 190 193 193 193 196 201 201 202 203
Chapter 16 More Miscellaneous . . . . . . . . . . . . . . . . Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Output and FileOutput . . . . . . . . . . . . . . . . . . . . . . . . . Creating Output and FileOutput Objects . . . . . . . . . . . . . Destroying Output and FileOutput Objects . . . . . . . . . . . . Getting Information about Output/FileOutput Objects. . . . . . Writing to a File or Resource . . . . . . . . . . . . . . . . . . . Navigating through a File . . . . . . . . . . . . . . . . . . . . . About Writing to Resources . . . . . . . . . . . . . . . . . . . . Buffers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Creating and Destroying Buffers . . . . . . . . . . . . . . . . . Retrieving Information . . . . . . . . . . . . . . . . . . . . . . . Locking and Unlocking Buffers . . . . . . . . . . . . . . . . . . Storing and Reading Information . . . . . . . . . . . . . . . . . Buffer Sizing . . . . . . . . . . . . . . . . . . . . . . . . . . . . Buffers and Messages . . . . . . . . . . . . . . . . . . . . . . . DirectKeyboard . . . . . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
204 204 204 204 205 205 206 206 207 207 207 208 208 209 210 210 211 212
Chapter 17 Advanced Cybiko Graphics . Overview . . . . . . . . . . . . . . . . . . . . . Direct Memory Access . . . . . . . . . . . . . Pixel Formats . . . . . . . . . . . . . . . . . Monochrome Pixel Format . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
213 213 213 214 214
. . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
ix
Team LRN
Contents
Four-Color Pixel Format . . . . . . . . Using Direct Memory Access for Drawing Signed Values . . . . . . . . . . . . . . . . Pixel Alignment. . . . . . . . . . . . . . . Four-Color Transparency. . . . . . . . . . cy3d . . . . . . . . . . . . . . . . . . . . . . . Fundamental Types Used in cy3d . . . . . fixed_t . . . . . . . . . . . . . . . . . . point_t and pos_t . . . . . . . . . . . . raster_t . . . . . . . . . . . . . . . . . . How cy3d Works . . . . . . . . . . . . . . Textures . . . . . . . . . . . . . . . . . Looking Around . . . . . . . . . . . . . Moving Around . . . . . . . . . . . . . Sprites . . . . . . . . . . . . . . . . . . Collision Detection . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
216 217 218 218 219 222 222 222 225 226 227 227 231 236 238 240 241
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
242 242 242 243 245
Chapter 19 Wireless Communication . . . Overview . . . . . . . . . . . . . . . . . . . . . CyIDs . . . . . . . . . . . . . . . . . . . . . . . Finding Another CyID . . . . . . . . . . . . . . Launching the Application or Game . . . . . . Running a Game Session . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
250 250 250 251 253 254 255
Chapter 20 File System. . . . . . . . . . . . . . Overview . . . . . . . . . . . . . . . . . . . . . . . . The File and FileFind Class . . . . . . . . . . . . . . The File Class. . . . . . . . . . . . . . . . . . . . The FileFind Class . . . . . . . . . . . . . . . . . Archive Class. . . . . . . . . . . . . . . . . . . . . . Creating and Destroying an Archive Object . . . Retrieving Information about an Archive Object . Connecting with an Archive Object’s Resources . Opening a Resource for Input . . . . . . . . . . Opening a Resource for Output . . . . . . . . . Other Devices . . . . . . . . . . . . . . . . . . . . . Getting Information about a Device . . . . . . . . mFileName Functions . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
256 256 256 257 258 260 260 260 261 262 262 262 263 264 264
Chapter 18 Advanced Forms Overview . . . . . . . . . . . . . Form Class Template . . . . . . Skeleton Code . . . . . . . . . . The proc Function . . . . . . . .
. . . . .
. . . . .
x
Team LRN
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
Contents
Chapter 21 Modules and Processes . . . Overview . . . . . . . . . . . . . . . . . . . . . module_t . . . . . . . . . . . . . . . . . . . . . cWinApp (m_process member of module_t) SystemThread . . . . . . . . . . . . . . . Thread . . . . . . . . . . . . . . . . . . . Process . . . . . . . . . . . . . . . . . . . AppGeneric . . . . . . . . . . . . . . . . cWinApp . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
265 265 265 266 266 268 268 269 271 272
Chapter 22 Odds and Ends . . . . . . . . . . . . Overview . . . . . . . . . . . . . . . . . . . . . . . . . imin and imax. . . . . . . . . . . . . . . . . . . . . . . Memory Management . . . . . . . . . . . . . . . . . . Allocation, Reallocation, and Deallocation . . . . . Running on Empty . . . . . . . . . . . . . . . . . . Memory Manipulation . . . . . . . . . . . . . . . . Other Memory Management Functions . . . . . . . Random Numbers . . . . . . . . . . . . . . . . . . . . Randomizer Seeds . . . . . . . . . . . . . . . . . . Score and score_t . . . . . . . . . . . . . . . . . . . . score_t. . . . . . . . . . . . . . . . . . . . . . . . . score.inf . . . . . . . . . . . . . . . . . . . . . . . . Score class . . . . . . . . . . . . . . . . . . . . . . Retrieving Information about a Score Object . . Reading and Writing to and from a Score Object . String Manipulation . . . . . . . . . . . . . . . . . . . Copying Strings . . . . . . . . . . . . . . . . . . . . Measuring Strings . . . . . . . . . . . . . . . . . . Concatenation . . . . . . . . . . . . . . . . . . . . . Comparison . . . . . . . . . . . . . . . . . . . . . . Searching for Characters and Strings . . . . . . . . Alpha and Omega . . . . . . . . . . . . . . . . . . . The Match Game . . . . . . . . . . . . . . . . . . . White Space . . . . . . . . . . . . . . . . . . . . . . Time . . . . . . . . . . . . . . . . . . . . . . . . . . . clock_t . . . . . . . . . . . . . . . . . . . . . . . . . time_t . . . . . . . . . . . . . . . . . . . . . . . . . Timer Resolution . . . . . . . . . . . . . . . . . . . Trusted Time . . . . . . . . . . . . . . . . . . . . . The Time Struct . . . . . . . . . . . . . . . . . . . Encoding and Decoding . . . . . . . . . . . . . . The Real-Time Clock . . . . . . . . . . . . . . . Alarm On, Alarm Off . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
273 273 273 274 274 275 275 276 276 277 278 278 278 279 279 280 280 281 281 281 282 283 283 284 284 284 285 285 285 286 286 286 287 287
xi
Team LRN
Contents
Chapter 23 Libs and Classes . . . . . . . . . . . . . . . . . Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Utility Libraries . . . . . . . . . . . . . . . . . . . . . . . . . . . . DynAlloc.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . coord.h/c. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . IO_Ext.h/c. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . string_ext.h/c . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dialogs and Forms . . . . . . . . . . . . . . . . . . . . . . . . . StdDlg.h/c . . . . . . . . . . . . . . . . . . . . . . . . . . . . MenuForm.h/c . . . . . . . . . . . . . . . . . . . . . . . . . . FileListForm.h/c . . . . . . . . . . . . . . . . . . . . . . . . . Utility Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Image, Animation Sequence, and Animation Sequence Set . . . Image . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . AniSeq . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . AniSeqSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . Linked Lists and Nodes . . . . . . . . . . . . . . . . . . . . . . Nodes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . Miscellaneous Classes . . . . . . . . . . . . . . . . . . . . . . . Cursor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Games. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
289 289 289 289 290 291 291 295 295 296 297 297 297 298 299 300 301 301 303 305 305 306 308 308 309
Appendix A The C Programming Language. Overview . . . . . . . . . . . . . . . . . . . . . . What is Programming?. . . . . . . . . . . . . . . How Do Computing Devices Work? . . . . . . . How Does a Program Work? . . . . . . . . . . . How is Memory/Storage Structured?. . . . . . . What is Cybiko C? . . . . . . . . . . . . . . . . . How Do I Program in Cybiko C? . . . . . . . . . The main Function . . . . . . . . . . . . . . . int main() . . . . . . . . . . . . . . . . . . . The Curly Braces . . . . . . . . . . . . . . return 0; . . . . . . . . . . . . . . . . . . . A Cybiko main Function . . . . . . . . . . . Built-in Types and Variables . . . . . . . . . . int and Binary Representations . . . . . . . Other Built-in Types. . . . . . . . . . . . . Assigning Values to Variables . . . . . . . . Operators . . . . . . . . . . . . . . . . . . Arrays . . . . . . . . . . . . . . . . . . . . Pointers . . . . . . . . . . . . . . . . . . . Structures . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
310 310 310 310 312 312 313 313 313 314 314 314 314 315 315 317 318 319 334 337 341
xii
Team LRN
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Contents
Loops . . . . . . . . . . . . . . . . Switches . . . . . . . . . . . . . . typedef . . . . . . . . . . . . . . . Functions. . . . . . . . . . . . . . #define . . . . . . . . . . . . . . . #ifndef, #ifdef, #else, and #endif #include . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
343 345 347 348 351 353 354 355
Appendix B File Format for Pic Files . . . Overview . . . . . . . . . . . . . . . . . . . . . Contents of a Pic File . . . . . . . . . . . . . . File Header . . . . . . . . . . . . . . . . . . Bitmap Record . . . . . . . . . . . . . . . . Bitmap Record Header . . . . . . . . . . Bitmap Data . . . . . . . . . . . . . . . . Conversion from Image to Bitmap Record . . . Conversion from Binary Data to Image. . . . . Summary . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
356 356 356 356 357 357 358 358 364 366
Appendix C File Format—Fnt . Overview . . . . . . . . . . . . . Header . . . . . . . . . . . . . . Bitmap Records . . . . . . . . . Bitmap Data . . . . . . . . . . . Encoding a Monochrome Bitmap Summary . . . . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
367 367 367 368 368 368 371
Appendix D Cybiko Graphics Quick Reference . TGraph, Graphics, and DisplayGraphics . . . . . . . Constructors and Destructors . . . . . . . . . . . . Constructor and Destructor Function Details . . Drawing Primitives . . . . . . . . . . . . . . . . . . Drawing Primitives Function Details . . . . . . . Pixel Functions . . . . . . . . . . . . . . . . . Line Functions . . . . . . . . . . . . . . . . . . Framed Rectangle Functions . . . . . . . . . . Filled Rectangle Functions . . . . . . . . . . . Fill Screen Function . . . . . . . . . . . . . . . Context Attributes . . . . . . . . . . . . . . . . . . . Context Function Details. . . . . . . . . . . . . . Current Color Functions . . . . . . . . . . . . Drawing Mode Functions . . . . . . . . . . . . Background Color Function . . . . . . . . . . . Clipping Area Functions . . . . . . . . . . . . . Current Font Functions . . . . . . . . . . . . . Current Bitmap Functions . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
372 372 372 373 374 374 374 375 377 378 378 379 380 380 381 381 382 384 384
. . . . . . .
. . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
xiii
Team LRN
Contents
Page Functions. . . . . . . . . . . . . . . . . . . . . . . Writing Bitmaps, Characters, and Text . . . . . . . . . . . . . Bitmap, Character, and Text Drawing Function Details . . Bitmap and Character Drawing Functions . . . . . . . . String Drawing Functions . . . . . . . . . . . . . . . . . Text Metrics . . . . . . . . . . . . . . . . . . . . . . . . . . . Text Metrics Function Details . . . . . . . . . . . . . . . . Character Size Functions . . . . . . . . . . . . . . . . . String Size Functions . . . . . . . . . . . . . . . . . . . Page Operations . . . . . . . . . . . . . . . . . . . . . . . . . Page Operation Function Details . . . . . . . . . . . . . . Page Copying Functions . . . . . . . . . . . . . . . . . . Show Functions . . . . . . . . . . . . . . . . . . . . . . Miscellaneous Functions . . . . . . . . . . . . . . . . . . . . Miscellaneous Function Details . . . . . . . . . . . . . . . Other Functions Dealing with Drawing Graphics . . . . . . . Function Details . . . . . . . . . . . . . . . . . . . . . . . Types and Constants Associated with TGraph, Graphics, and DisplayGraphics Objects. . . . . . . . . . . . . . . . . . . . . color_t . . . . . . . . . . . . . . . . . . . . . . . . . . . . . drawmode_t . . . . . . . . . . . . . . . . . . . . . . . . . . rect_t . . . . . . . . . . . . . . . . . . . . . . . . . . . . . rect_t Function Details . . . . . . . . . . . . . . . . . . Other Constants . . . . . . . . . . . . . . . . . . . . . . . Screen Constants . . . . . . . . . . . . . . . . . . . . . Rounded Rectangle Styles . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
385 386 386 386 387 388 389 389 390 391 391 391 392 393 393 395 395
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
397 397 397 397 398 399 399 400
Appendix E Cybiko Bitmap Quick Reference . . . . . . . . . . . . . . . . . . . Bitmap, BitmapSequence, and Font. . . . . . . . . . . . . . . . . . . . . . . . . . . . . Constructors and Destructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Constructor and Destructor Details . . . . . . . . . . . . . . . . . . . . . . . . . . . Bitmap Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . BitmapSequence/Font Constructors . . . . . . . . . . . . . . . . . . . . . . . . . Destructor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bitmap Member Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bitmap Member Function Details . . . . . . . . . . . . . . . . . . . . . . . . . . . . Loading and Storing Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Loading and Storing Function Details . . . . . . . . . . . . . . . . . . . . . . . . . . Attributes Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Attribute Function Details . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Character and String Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Character and String Function Details . . . . . . . . . . . . . . . . . . . . . . . . . Miscellaneous Functions (draw_lib) . . . . . . . . . . . . . . . . . . . . . . . . . . . . Constants and Variables Associated with Bitmaps, BitmapSequences, and Fonts . . . . Bitmap Mode Flags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Built-in Font Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
401 401 401 402 402 403 404 404 404 406 406 406 407 410 411 412 413 413 413
xiv
Team LRN
Contents
Appendix F Cybiko UI Classes . cObject . . . . . . . . . . . . . . . cClip. . . . . . . . . . . . . . . . . cEngine . . . . . . . . . . . . . . . Controls. . . . . . . . . . . . . . . cBevel . . . . . . . . . . . . . . cProgressBar . . . . . . . . . . cBitmap . . . . . . . . . . . . . cBox . . . . . . . . . . . . . . . cButton . . . . . . . . . . . . . cEdit . . . . . . . . . . . . . . . cText . . . . . . . . . . . . . . . Forms and Dialogs . . . . . . . . . cList and cItem . . . . . . . . . . . cList . . . . . . . . . . . . . . . cItem and cSItem . . . . . . . . cXItem, cXStr, and cXByte . . . . Types and Enums . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
414 415 422 429 430 431 431 432 433 434 434 439 440 443 443 445 447 452
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
xv
Team LRN
Foreword I’ve long considered myself a Windows game programmer. Although I started with Atari computers (back in the day), most of my game programming knowledge has been gained using Windows PCs. In recent years, however, I’ve found myself developing for the Pocket PC, Palm Pilot, and UNIX, and I now make my living developing for the Playstation 2, X-box, and GameCube. As chance would have it, I’m writing this on an Apple iBook laptop. It turns out that I’m not really a Windows game programmer after all; I’m simply a game programmer. I develop for the platform that offers the greatest opportunities to create games that interest me. Almost a year ago, I decided to try developing for the Cybiko, mainly because several of my associates were making games for it that I thought I could outdo. I came up with the basic design for a game—with a working title of BattleQuest—and set to coding. I quickly found that although Cybiko provides a software development kit specifically tailored to game development, it is poorly documented. Through experimentation, I was able to work out some of it on my own, but I eventually found myself turning again and again to a friend of mine who had already figured everything out. Who was that friend? Ernest Pazera. You have in your hands the only comprehensive guide to game programming on the Cybiko. Whether you’re new to game programming, or you’re adding a new platform to your repertoire, this book will provide you with the resources you need to begin making a name for yourself in a relatively new and untapped market. It will be well worth your effort to develop for the Cybiko. At the very least, it will give you experience in developing for a platform with a proprietary API and limited hardware resources. In addition, the number of Cybiko owners is growing rapidly. So far, there are few good third-party developers making Cybiko games. Accordingly, there is a big demand from Cybiko owners for better, richer games. Good things await those savvy developers who meet that demand. So, what are you waiting for? Dive on in and become a game programmer. Dave Astle Avalanche Software
xvi
Team LRN
Chapter 1
Welcome Overview Welcome to the first book on Cybiko development. This book was written from a game developer’s perspective (I am a game developer after all), and so you’ll notice that a lot of what I say brings to mind games and aspects of games. In this chapter, you will get to know me a little bit. It’s important for the reader to know the author, even if just a little. Also, you’ll learn what the book is about and what you need to get set up for Cybiko development.
About Me I am a 27-year-old programmer from Kenosha, WI. I’ve been programming for over 14 years, in a variety of languages, including dialects of BASIC, Pascal, C++, and C. Currently, I am considered primarily to be a Windows programmer, although lately it would appear that I have been primarily a Cybiko programmer. My methods of programming are intentionally simple and easy to understand. This is how my skills have developed over the years. I write what is easy for me to decipher, and hopefully easy for you to decipher.
About You You are interested in Cybiko development, and you are at least familiar with programming, although being an expert isn’t required. There is a sort of “refresher course” for C in Appendix A, but this book does not teach you how to program from scratch. At least a nominal amount of exposure to C will be helpful. If you don’t know anything about C, you’ll likely have a hard time. I suggest getting a good book on C if you don’t have one already.
1
Team LRN
2
Chapter 1: Welcome
Things You Will Need There are a number of things you will need in order to make full use of this book. First and foremost, you will need a Cybiko. Without one, you will not be able to run the programs. At the time of this writing, there is an emulator in the works, but I have no guarantee that it will be done by the time this book hits the shelves. Also, while emulators are nice, it is best to test things out on the real thing. Second, you will need a computer that can run the various applications needed to develop software. Not all platforms have a good set of tools for this. If you have Windows, you should be just fine. If you have Linux, you should be all right, too. I use Windows, so I write from the Windows perspective. Third, you need to install the necessary applications for Cybiko development. This means putting CyberLoad and the Cybiko SDK on your machine. I have included copies of these on the companion CD-ROM. When this book gets to the shelves, these applications may not be the most up-to-date versions, so you may want to check on http://www.cybiko.com for newer versions. Fourth, you will need an Internet connection. It is possible to get by without one, but in order to download applications from Cybiko, Inc., you need to connect to them through the Internet. Also, you will need an Internet connection to go to this book’s support site, http://www.cybikodev.com. By the time you purchase this book, there will be new information available on this site.
Configuring the Software There are two important pieces of software you will need, CyberLoad and the Cybiko SDK. Both of these can be found at www.cybiko.com, and I have included versions of these on the CD-ROM as well. NOTE: On the off chance that you’ve never used WinZip before, you simply copy the zip archive over onto your hard drive (I usually put them in a new folder I name MyProjects), turn off the read-only properties, right-click on the zip archive, and select “Extract to folder.... filename.” This will extract the archive into a new folder named filename, with all of the files located where they are supposed to be.
CyberLoad You can find CyberLoad on the CD-ROM under Tools, with the filename CyberloadSetup.exe. This is a Windows program that interfaces your computer to the Cybiko. You need it to transfer content from Cybiko, Inc., to your device.
Team LRN
Chapter 1: Welcome
Cybiko SDK The Cybiko SDK is a collection of tools used to create new software for the Cybiko. You can find the Windows and Linux versions on the CD-ROM under Tools. The Windows version is in Cybiko_SDK_2112_Std.exe, and the Linux version is in Cybiko_SDK_2.1.13.i386.rpm. Install the appropriate one.
Text Editor In order to view, edit, and create code for your programs, you will need some sort of text editor. You can get by with something like Notepad, but I suggest getting a tool made for writing code. UltraEdit is a good text editor. You can find it at http://www.ultraedit.com. It is shareware, and it does expire, but it’ll do the job. Personally, I use Microsoft Visual C++ 6.0, but then again, I’m usually a Windows programmer, so this is no surprise.
Test CyberLoad and the CyConsole Before doing anything else, plug your Cybiko into your machine, and test to make sure that CyberLoad works. If it doesn’t, look on www.cybiko.com for support. If that doesn’t help, call the Cybiko support line; it’s what they are paid to do. Also, be sure to test the CyConsole. The CyConsole is part of the SDK, and gives you a command-line interface on your computer to your Cybiko. If you have trouble, Cybiko should help you, or you can look on www.cybikodev.com for some of the common problems people have with the CyConsole. That’s it. You’re all set up. Now we can start with the fun stuff.
Team LRN
3
Chapter 2
Your First Cybiko Application Overview Ever since Kernigan and Ritchie created the C programming language, the tradition has been to make a “Hello, world!” program as your initial foray into programming in C. I’m going to respect this fine tradition, and to get you into programming for the Cybiko, you are going to make your own “Hello, world!” application, Cybiko style. The K&R Hello.C looks something like the code in Listing 2.1. Listing 2.1 Hello.C, K&R style /******************* Hello.C *******************/ #include <stdio.h> int main() { printf(“Hello, world!\n”); return(0); }
Hello.C is used as the very first program by teachers for a couple reasons. One, it contains all of the parts essential to larger programs (it has a main function and includes a header file). Two, it is easy to understand. All that Hello.C does is write “Hello, world!” and then quit. Unfortunately, if you were to look through the Inc folder of the Cybiko SDK, you would not find a file named stdio.h, and if you were to look through the Cybiko SDK help files you would see that there is no printf function anywhere. Therefore, the traditional Hello.C cannot be compiled and run on your Cybiko.
4
Team LRN
Chapter 2: Your First Cybiko Application
In addition, for an application or game to work on the Cybiko, there are a few extra files (resources) that must accompany it in order for you to see it in the application or game section. This chapter will get your feet wet (or at least slightly damp) in Cybiko programming.
A Simple Cybiko “Hello, world!” While there may not be a printf function, there is a cprintf function on the Cybiko. This function prints to the console, not to the Cybiko’s screen. Even though you won’t see anything on the Cybiko itself, the code in Listing 2.2 is pretty close to the original K&R Hello.C. Listing 2.2 Hello.C, Cybiko style /* ************************************* *CyBk2_1.C * *16FEB2001 * *Simple "Hello, world!" application ************************************* */ //Master Include file for entire Cybiko SDK #include "cywin.h" //main function long main(int argc, char* argv[], bool start) { //print to the console cprintf("Hello, world!"); //exit the program, and return zero return(0); }
This code is found on the CD, under Examples, in a file named CyBk2_1. Once you’ve copied the file, take a look at it. The folder CyBk2_1 should look something like Figure 2.1. NOTE: You will need WinZip to open some of the files on the CD. A copy of WinZip can be downloaded from www.winzip.com.
Team LRN
5
6
Chapter 2: Your First Cybiko Application
Figure 2.1: CyBk2_1 folder
You will see three subfolders, named Res, Src, and Tmp, and two files, Make.bat and Makefile. The Res subfolder contains any resources that are used by an application (I’ll show you more about resources later). The Src subfolder contains any source files (*.c and *.h) and any object files (*.o) that are part of the application. The Tmp subfolder is for temporary files created and used by the compiler, so don’t worry about it too much. The Make.bat file contains the commands shown in Listing 2.3. Listing 2.3 Make.bat path %CYBIKO_SDK%/bin vmake new –p2 %1
The makefile is shown in Listing 2.4. I don’t even like makefiles, and the one in Listing 2.4 is going to be the one we’ll use over and over, just changing the value of the NAME variable. Listing 2.4 Makefile NAME = CyBk2_1 OBJ = src/$(NAME).o RES = res/filer.list PP CC AS LN LD RM
= = = = = =
vcpp vcc1 vas vlink filer vrm
Team LRN
Chapter 2: Your First Cybiko Application
.SUFFIXES : .c all : $(NAME).app $(NAME).app : tmp/bytecode.bin $(RES) @echo building app archive... @$(LD) a $@ @res/filer.list tmp/bytecode.bin "$(CYBIKO_SDK)"/lib/main.e tmp/bytecode.bin : $(OBJ) @echo linking ... @$(LN) –o $@ src/*.o .c.o : @echo compiling $<... @$(PP) –I"$(CYBIKO_SDK)"/inc $< | $(CC) | $(AS) –O –o $@ src/$(NAME).o : src/$(NAME).c clean : @echo cleaning... @$(RM) –f tmp/*.* src/*.o $(NAME).app new : clean all
A makefile just contains a script that tells vmake what to do. If you are unfamiliar with makefiles, they’ll look like a bunch of gobbledegook. If you come from a UNIX programming background, you’re probably quite comfortable with makefiles, and I’ll leave exploring the features of the makefile script up to you. For those of you scratching your head and saying “Huh?”, don’t sweat it. I’m going to show you how to set up a project so that the only thing you need to change in the makefile is the first line.
Res The Res subfolder of CyBk2_1 contains another subfolder named Src, and a file named Filer.list. The Res/src folder contains a single file called Makeres.bat. The Filer.list file is blank, but for all but the simplest applications, it would contain a list of the resources used by the application, and there would be more files in the Res subfolder. Similarly, there would be source bitmaps for whatever pictures you were using in the Res/src subfolder, and Makeres.bat would contain code to convert a normal Windows bitmap to a Cybiko picture. There will be more on resources later.
Src The Src subfolder contains the C source files and the object files for your application (which you probably won’t be messing with). The CyBk2_1.C file contains the source shown in Listing 2.2. There is also CyBk2_1.o, which isn’t terribly important at the moment.
Team LRN
7
8
Chapter 2: Your First Cybiko Application
Trying Out CyBk2_1 Browse over to the CyBk2_1 folder, and double-click Make.bat. If all goes well, a DOS window will pop up and show you the progress of building the application, and CyBk2_1.app will appear in the CyBk2_1 folder. If things don’t go well, you probably need to reinstall the Cybiko SDK, or you just haven’t installed it. If it still doesn’t work, go to http://www.cybikodev.com, and check out the FAQ, where you can probably read how to fix the problem you are having. Once you have CyBk2_1.app built, hook up your Cybiko to the serial port, and start the console program. Make sure it connects to the proper COM port, and then drag CyBk2_1.app into the console. You’ll get a status bar showing the download process (it usually only takes about a second). Once you have done this, the console should look like Figure 2.2. Figure 2.2: Downloading an app to the Cybiko with the console
Next, run the application through the console by typing the name of the app into the input field at the bottom of the console’s window and then hitting Enter. The result should look like Figure 2.3.
Team LRN
Chapter 2: Your First Cybiko Application
Figure 2.3: Running an application from the console
And that’s all there is to our little “Hello, world!” program. I admit it isn’t much, but it’s at least a start. Now that we’ve got this, it’s time to take it to the next step, and make a program that you can actually see on the Cybiko.
A Real Cybiko “Hello, world” Program CyBk2_1 is fine and dandy, but you aren’t reading this to learn to write programs that can only be seen on the console. That was just the simplest program with an actual visible result that I could think of. Most of the time, you’ll only use the cprintf function if you are debugging an application or just want to be able to get a log file of a running application, so it’s still a handy thing to know. Now, it is time to make a real Cybiko application that responds to keyboard input, complete with an icon, a splash screen, and scrolling instructions. Also, it will show “Hello, world!” on the Cybiko display, and it will show up in the application list. Simply adding resources to the project does most of this. Go ahead and copy CyBk2_2 from the CD onto your hard drive. This file contains another set of subfolders similar to the CyBk2_1 file we used earlier. Before I get into the actual code for CyBk2_2, I want to show you the contents of the Res subfolder.
Team LRN
9
10
Chapter 2: Your First Cybiko Application
Res The Res subfolder for CyBk2_2 contains a number of files besides the Src subfolder and Filer.list. Also present are Root.inf, Root.spl, Root.ico, and Intro.pic. All of these files have special meaning to a Cybiko application.
Root.inf This is a special file that you must include in your applications in order for the Cybiko to show it on the desktop. It is a simple text file that contains information about the application. This crucial information is stored in ten lines. Each line has a particular meaning for the file manager, as shown in Listing 2.5. Listing 2.5 Format of Root.inf 01: 02: 03: 04: 05: 06: 07: 08: 09: 10:
Where the application will be shown, app, game, or root Name of the application, as shown on the desktop and in CyberLoad (blank) (blank) (blank) (blank) Name of .app archive Version of the application Version of CyOS required to run this application Copyright information for this application
CAUTION: You must leave lines three through six of Root.inf empty! Putting something in them will make your application not work!
If you open up CyBk2_2’s Root.inf in Notepad (or another simple text editor of your choice), you will see the information in Listing 2.6. Listing 2.6 CyBk2_2’s Root.inf 01: 02: 03: 04: 05: 06: 07: 08: 09: 10:
app CyBk2_2
CyBk2_2 1.0.0 1.2.56 Copyright (C) DTM Software 2001
Naturally, the numbers and colons aren’t there. I just put those in to more easily reference the line numbers. Line 01 contains the word “app.” This indicates to the Cybiko desktop that this program is an application, and should be listed when the Applications icon is selected by the user, just like the Phone Book program. Other values for line 01 include
Team LRN
Chapter 2: Your First Cybiko Application
11
“game,” which will list the application under “Games,” or “root” which will list the application at the highest level of the desktop. Line 02 contains the name you want to show underneath the icon in the desktop. Often, it is not the same as the name of the .app file. For example, a tic-tac-toe program might show up on the desktop as “Tic-Tac-Toe,” but the file is TicTacToe.app. This line also dictates how your application will show up in CyberLoad. Line 07 contains the name of the app archive, without the “.app” on the end. In the tic-tac-toe example above, line 07 would contain “TicTacToe.” Line 08 is the version number of your application. Put whatever version number you have for your app. Most of the time I just leave mine as 1.0.0, unless I really have a need to change it. Line 09 contains the version of CyOS required to run your application. It is difficult, if not impossible, to determine which prior versions of CyOS will run your application, so a good rule of thumb is to just put whatever the current version of CyOS is, and update this whenever you recompile the application and the CyOS version has changed. You can generally rely on Cybiko users to have the most current version of CyOS, since CyberLoad updates it automatically. Line 10 is your copyright information. My company is DTM Software, so I put it in. If you don’t have a company, you can either make one up or just put your name.
Root.spl The Root.spl file is just a simple text file, much like Root.inf. Unlike Root.inf, it doesn’t have any special format. This file just contains the text you want the users of your application to see when the application starts up, after either the icon moves into the corner or the intro picture is shown. Common things to include in Root.spl: name of the application, copyright information, basic instructions on program use, and a general description of the program. Third-party developers (that would be me and you) tend to also put an e-mail address for contacting the author and/or a web site where the user can download other games written by you and your friends.
Root.ico The Root.ico file contains a Cybiko format picture that will serve as the icon for your application on the desktop. The source for this image is in Res/src under the name Icon.bmp. The source image name is unimportant, but this file must be named Root.ico. The picture is 48 pixels wide and 47 pixels high.
Intro.pic This file is optional. It is the full screen picture that is shown while an application is loading up, and before Root.spl is shown as the picture scrolls up. The source for this image is located in Res/src under the name Intro.bmp. The picture is 160x100, taking up the entire screen.
Team LRN
12
Chapter 2: Your First Cybiko Application
If you opt not to have an Intro.pic, then there will be an animated sequence using Root.ico when the application is loading instead of showing Intro.pic on the screen.
Src Subfolder The Res/src subfolder contains three files, Intro.bmp (the source image for Intro.pic), Icon.bmp (the source image for Root.ico), and Makeres.bat. I’ve already covered what Intro.bmp and Icon.bmp are used for, so I’ll just cover Makeres.bat, which is shown in Listing 2.7. Listing 2.7 Makeres.bat path %CYBIKO_SDK%/bin 2pic ../root.ico icon.bmp 2pic ../intro.pic intro.bmp
This batch file is intended to be double-clicked in My Computer or Windows Explorer, and its task is to convert any of the source images or sounds in Res/src into the Cybiko format images in the Res folder. Since we only are converting images, we are only concerned with using 2pic.exe, which you can find in the Bin subfolder of the Cybiko SDK. Full documentation for using 2pic.exe can be found in the Cybiko SDK help files. Basically, any image you need to convert to the Cybiko format will have its own line calling 2pic in Makeres.bat, so any time you add a new image, you’ll want to add another line to Makeres.bat. Any time you modify any of the source images, you’ll want to run Makeres.bat, so that your resources are up to date.
Filer.list As I said earlier, Filer.list contains a list of all resources that are to be included in a project’s application archive. Back in CyBk2_1, this file was empty. Now that we have resources, we have to list them in Filer.list, so that when we run Make.bat, they will be included in the application. Listing 2.8 shows the contents of Filer.list. Listing 2.8 Filer.list res/root.ico res/root.inf res/root.spl res/intro.pic
NOTE: All of the resources have “res/” in front of them. This is because Filer.list is going to be accessed from one folder up, so the subfolder must precede the resource name.
It might seem overwhelming to throw all of this resource stuff at you at once, but it’s not as complicated as it may seem. You’ll get used to this resource management scheme in time.
Team LRN
Chapter 2: Your First Cybiko Application
CyBk2_2’s Src Directory We’re still only doing really simple applications, so there is only one source file for CyBk2_2, named (to no one’s surprise) CyBk2_2.C. It is shown in Listing 2.9. Listing 2.9 CyBk2_2.C /* ******************************************* *CyBk2_2.C * *16FEB2001 * *Cybiko style Hello World application ******************************************* */ //master include file for cybiko sdk #include "cywin.h" //global variables struct module_t main_module; bool exit_application;
//main module //exit application flag
//forward declarations for functions bool Prog_Init(); //initialize the program void Prog_Loop(); //idle loop for the program void Prog_Done(); //clean up for the program //main functions long main(int argc, char* argv[], bool start) { //declare local variable for message retrieval struct Message* ptr_message; //initialize the main module init_module(&main_module); //initialize the program exit_application=!Prog_Init(); //while program is still running while(!exit_application) { //check for a message ptr_message = cWinApp_get_message(main_module.m_process,1,1,MSG_USER); //if there is a message... if(ptr_message) { //...check what kind of message it is, and process it switch(ptr_message–>msgid)
Team LRN
13
14
Chapter 2: Your First Cybiko Application
{ case MSG_SHUTUP: //Processes system exit signal. case MSG_QUIT: exit_application = TRUE; break; case MSG_GOTFOCUS: //Redraws screen. break; case MSG_KEYDOWN: //Processes keyboard messages. if(Message_get_key_param(ptr_message)–>scancode == KEY_ESC) //escape key has been pressed { exit_application = TRUE; //set the exit flag break; } default: //Processes all unprocessed messages. cWinApp_defproc(main_module.m_process, ptr_message); } //delete the message Message_delete(ptr_message); } else { //no message, so execute an idle loop Prog_Loop(); }
} //while(!exit_application)
//clean up after the program Prog_Done(); //return 0, we're done return 0; } bool Prog_Init() { //initialization //return TRUE if program initialized, and FALSE if it did not return(TRUE); } void Prog_Loop() { //idle loop //fill the screen with white DisplayGraphics_fill_screen(main_module.m_gfx,CLR_WHITE);
Team LRN
Chapter 2: Your First Cybiko Application
15
//select font DisplayGraphics_set_font(main_module.m_gfx,cool_normal_font); //select color DisplayGraphics_set_color(main_module.m_gfx,CLR_BLACK); //write text DisplayGraphics_draw_text(main_module.m_gfx,"Hello, world!",0,0); //show the screen DisplayGraphics_show(main_module.m_gfx); } void Prog_Done() { //cleanup }
This only looks like a lot of code. It is the fundamental code to have a simple Cybiko application run, plus a few lines that will print “Hello, world!” on the screen. I will cover the details of what’s going on in the main function in Chapter 3, so don’t worry about it yet. These are the basic steps that this program is taking: 1.
Initialize the application (run Prog_Init).
2.
Check for a message.
3.
If there is a message, process it.
4.
If there is no message, run the idle loop (run Prog_Loop).
5.
Repeat steps 2-4 until the Esc key is pressed.
6.
Clean up the application and exit (run Prog_Done).
For this application, we are only concerned with the idle loop (the code within Prog_Loop), since that’s what does the drawing on the screen. These are the steps taken by Prog_Loop (all of the functions used in Prog_Loop are covered in greater detail in future chapters). 1.
Clear the screen to white.
2.
Set the current font.
3.
Set the current color.
4.
Draw the text.
5.
Show the screen.
As you might notice from the code, all of the screen drawing uses functions that start with DisplayGraphics, and pass main_module.m_gfx as the first parameter. This might seem a little strange right now, but most of Cybiko programming is like this. It is
Team LRN
16
Chapter 2: Your First Cybiko Application
object oriented, while being completely in C. (I’ll be clearing this all up in Chapter 5, once you’ve gotten a more solid footing in Cybiko programming.) For now, just build the application using Make.bat, and transfer it over to your Cybiko using the console. Reboot your Cybiko, either by typing “reboot” into the console program or just turning it off and back on again. Once the desktop shows up, go into Applications, and you should find CyBk2_2. Run it, and you will see the Intro.pic, the contents of Root.spl, and, after you hit a key, the words “Hello, world!” at the top of the screen. That’s it for the Cybiko-style “Hello, world!” program.
Summary In this chapter, I’ve kind of bogged you down with a lot of stuff, and I didn’t explain it as well as I could have. This is mainly because I wanted to put the detailed descriptions of things in their appropriate chapters, and not have two explanations for things or make you have to hunt down explanations for functions that logically belong in the same chapter.
Team LRN
Chapter 3
Introduction to Message Handling Overview If you have experience programming message-based operating systems, you should have no trouble with Cybiko message handling. It is very similar to Windows and other operating systems. If you’ve never programmed for a message-based operating system before (perhaps you used to program in DOS), messages might seem a little strange.
Message Queues In any operating system, there must be a way to gain information about what keys were pressed, what messages are coming from other devices, and so on. For the moment, I’m going to use keyboard presses as an example, since they are so common. In DOS, keyboard response was handled through interrupts by hardware. When a key was pressed, the hardware would interrupt whatever application was currently running and read information about what key was pressed, translate this information into a character code, and place it into a keyboard buffer, which the application would later read using a function like Inkey or getch. In the Windows environment, a similar thing happens, except that more than one application might be running at the same time. This leads to such concepts as the “active window” and the application with “keyboard focus.” The active window is the window with the highlighted title bar; all other applications have gray title bars. Only the active window receives keyboard messages. When a key is pressed, Windows sends a WM_KEYDOWN message to the active window, along with data saying which key was pressed. Then, if the key corresponds to a generated character (“A” or “/”) it will generate a WM_CHAR event, and send data corresponding to which character was generated. Not all keys generate character
17
Team LRN
18
Chapter 3: Introduction to Message Handling
data (for example, the Shift and Ctrl keys do not), but serve only to modify the character codes generated in a WM_CHAR. When a key is released, Windows sends a WM_KEYUP message, stating which key was released. CyOS works in much the same way that Windows does, as far as keyboard messages are concerned. When a key is pressed, the active application receives a MSG_KEYDOWN. When a character is generated, it receives a MSG_CHARTYPED, and when a key is released, it receives a MSG_KEYUP. So, this leaves us with the quandary of which application is the active one. When you run an application from the desktop, that application is the active application, until you exit, or something else preempts it. A couple of cases where your application might be preempted are when another Cybiko user comes into the area (you get the “person has entered your area” message box), and when another user is trying to send you a file (you get the “file send confirmation” box). During these times, those message boxes are the “active application” and receive all keyboard input. In order for messages to be delivered to their appropriate recipients, each application has a message queue. A message queue is just a list of messages that have been sent to that application but have not been processed yet. This allows input to come in whenever it needs to, but doesn’t interrupt an application while it is busy (which would be a nightmare). In order for everything to work properly, an application is responsible for periodically checking for messages and handling them. There are a couple of ways to go about this. Before this happens, however, we need to set up the message queue.
Initializing the Module In order for us to run an application in CyOS, we must first initialize a module. A module is nothing more than a graphics context (which allows us to draw onto the screen) and a thread (which sets up a message queue for us to check messages). This was done in the last chapter in CyBk2_2, but here I’m actually going to explain it. First, you must have a variable of type struct module_t. This is shown below in Listing 3.1. Listing 3.1 struct module_t main_module;//declare the module
The declaration of the module is most commonly a global declaration, outside of any functions. This isn’t strictly required. You could also make it a local variable within your main function, but then you have to send a pointer to main_module every time you need to use part of it, so it is probably best left as a global. struct module_t contains two data members: m_gfx and m_process. The m_gfx member is a pointer to a graphics context, which you use to draw on, and m_process is a cWinApp pointer, which is the thread and contains the message queue (i.e., you send this member to functions that read in messages).
Team LRN
Chapter 3: Introduction to Message Handling
19
For the remainder of the chapter, we will primarily be concerned with main_module.m_process. We initialize this module by calling the init_module function, shown below in Listing 3.2. Listing 3.2 init_module(&main_module);//initialize the main module
This call sets up the m_process and m_gfx members of main_module. Calling the init_module function is kind of like saying, “I want a graphics context and a message queue.” We only have to call this once, at the very beginning of the program, and we are all set up.
Grabbing Messages Once our module has been initialized, we are ready to receive messages. There are two methods of checking your messages, and each has its pros and cons. The two functions are cWinApp_get_message and cWinApp_peek_message.
cWinApp_get_message Listing 3.3 shows the syntax for the cWinApp_get_message function. Listing 3.3 struct Message* cWinApp_get_message ( struct cWinApp* ptr_win_app, //pointer to a cWinApp structure long timeout, //time to wait for a message int min, //lowest value of message to look for int max); //highest value of message to look for
This function returns a pointer to a Message struct. You do not have to allocate space for it, you simply use an uninitialized pointer. The parameters for cWinApp_get_message are shown in Table 3.1. Table 3.1 cWinApp_get_message parameters Parameter
Meaning
ptr_win_app
A pointer to a cWinApp structure (i.e., main_module.m_process).
timeout
A time to wait for a message to be retrieved (zero waits forever).
min
The lowest value for the message allowed (usually 1).
max
The highest value for the message allowed (usually MSG_USER).
You can specify a timeout value in the timeout parameter, or put zero to tell the function to wait until a message shows up. Whether you use zero or not depends entirely
Team LRN
20
Chapter 3: Introduction to Message Handling
upon what kind of application or game you are writing. For example, if you are making a text editing program, you have the luxury of just waiting for a message before doing anything. This is the event-driven model. However, if you are continuously playing music or need to update enemy positions in real time, then you can’t just wait until a message occurs before updating the application, so you might specify a timeout value other than zero. The timeout value is measured in clock ticks, which occur once every 10 milliseconds, or 100 times per second, so if you wanted to wait no more than half a second for a message, you would use 50 for the timeout. If no message is received within the timeout period, cWinApp_get_message returns NULL, and you can go ahead and do whatever updating you need to do in the absence of user input. Listings 3.4 and 3.5 show abbreviated examples of each of these uses for cWinApp_get_message. Listing 3.4 Event-driven message retrieval //ptr_msg is a pointer to a struct Message //exit_app is a bool, where TRUE means to exit the program while(!exit_app) { //wait for a message ptr_msg=cWinApp_get_message(main_module.m_process,0,1,MSG_USER); //handle message here //destroy the message //update the application }
Listing 3.5 Real-time message retrieval //ptr_msg is a pointer to a struct Message //exit_app is a bool, where TRUE means to exit the program while(!exit_app) { //wait for a message for half a second(50 clock ticks) ptr_msg=cWinApp_get_message(main_module.m_process,50,1,MSG_USER); //make sure a message occured if(ptr_msg!=NULL) { //handle message here //destroy the message } //update the application }
Team LRN
Chapter 3: Introduction to Message Handling
21
There are only subtle differences between Listings 3.4 and 3.5. In Listing 3.4, a message will occur during each loop. In Listing 3.5, a message may or may not occur before the timeout, so you need to check and make sure the message is not NULL before handling it. In 3.4, the application is only updated after a message occurs, and in 3.5 the application will be updated at least once every half a second.
Message_delete While you don’t ever have to allocate space for a message you receive, you do need to free up the memory it uses once you have handled it. To do this, you use the Message_delete function, shown in Listing 3.6. Listing 3.6 void Message_delete( struct Message* ptr_message );
//message that must be deleted
This function returns no value and has only one parameter—a pointer to a message that must be destroyed. Its use is pretty simple, as shown in Listing 3.7. Listing 3.7 //destroy the message Message_delete(ptr_msg);
CAUTION: Every time you read a message, you must delete it using Message_delete. If you don’t, then your application will leak memory, and eventually, unpredictable behavior (the device may lock up or your data might become garbled) will be the final result, requiring you to reboot the machine and lose whatever data you have. A similar caution is necessary for making sure you are checking messages often enough. If you aren’t checking messages frequently enough, the message queue might overflow the memory, giving you the same result.
cWinApp_peek_message Another way to read messages from the message queue is to use cWinApp_peek_ message. The syntax is shown in Listing 3.8. Listing 3.8 struct Message * cWinApp_peek_message ( struct cWinApp * ptr_win_app, //pointer to a thread(main_module.m_process) bool remove, //flag to remove the message from the queue int min, //lowest message to check for int max //highest message to check for );
Team LRN
22
Chapter 3: Introduction to Message Handling
This function returns a pointer to a Message struct, just like cWinApp_get_message. The parameters are shown in Table 3.2. Table 3.2 cWinApp_peek_message parameters Parameter
Meaning
ptr_win_app
Pointer to a thread.
remove
Flag telling function to remove message from queue; TRUE removes the message.
min
Minimum message value to look for.
max
Maximum message value to look for.
Be careful with this one. It sounds like a really great function to use, but I’ve experienced frequent lockups while using it. cWinApp_peek_message checks to see whether or not a message exists in the queue, and optionally can remove it from the queue, which might make you think that cWinApp_peek_message is like using cWinApp_get_message, which returns immediately. It is, sort of. However, the SDK help files warn us only to use it if we absolutely need it, and from tests I’ve done using it, I have to agree. Half the time the application works fine, and half the time the application locks up. Lesson learned: Try to avoid using cWinApp_peek_message, and instead use cWinApp_get_message with a timeout value whenever possible. So, when is a good time to use cWinApp_peek_message? The answer is during long operations, such as world generation, long file loads, or uploads to other Cybiko devices. As an example, we’ll say you’re saving a file, and it’s pretty big, so you think it might take some time. Listing 3.9 shows an example. Listing 3.9 //first part of long file operation //check for a message, but do not remove it if(cWinApp_peek_message(main_module.m_process,FALSE,1,MSG_USER)!=NULL) { //message has been found, so do something about it } //second part of long file operation //etc.
The idea here is that during a lengthy operation you periodically check for a message. If you find one, you should handle it at that time, so you avoid overflowing the message queue.
Team LRN
Chapter 3: Introduction to Message Handling
23
Contents of a Message By now, you understand how to retrieve a message, but you don’t know what’s in it, how it is formatted, and what it means. In this section, I go into the anatomy of a message. Let’s begin with how the actual Message struct looks. Check out Listing 3.10. Listing 3.10 struct Message{ struct Message* next; char* dst_name; cyid_t cyid_from; cyid_t cyid_to; bool deleted; short msgid; long param [2] = ; };
There are a few new members here. Some of them are pretty obvious, like cyid_from and cyid_to. All of the members are explained a little better in Table 3.3. Table 3.3 struct Message members Member
Meaning
next
Points to the next message in the queue.
dst_name
The destination application for which this message is intended.
cyid_from
The ID of the Cybiko that originated this message.
cyid_to
The ID of the Cybiko that is the recipient of this message.
deleted
Whether or not the message has been deleted.
msgid
Which message was sent.
param
An array of message parameters, specifying extra information about the message.
For the most part, the only parts of the Message struct you have to concern yourself with are msgid and param. Only when you are sending messages manually do you need to worry about any of the others. You never have to worry about them for keyboard messages. The way to send messages manually is with Message_post. The syntax for it is shown in Listing 3.11. Listing 3.11 bool Message_post ( struct Message * ptr_message, char * sz_process_name,
Team LRN
24
Chapter 3: Introduction to Message Handling
cyid_t cyid )
This function returns TRUE if the message found its way into the destination application’s message queue, and FALSE if it does not. sz_process_name is the name of the application to which you are sending the message, and cyid is the ID of the Cybiko that is to receive the message. Place get_own_id() in cyid if you are sending the message to the same Cybiko as you are sending from. Listing 3.12 shows you how to manually create and send a message. Listing 3.12 struct Message* ptr_message = Message_new( sizeof( struct Message ) ); ptr_message–>msgid = MSG_USER; Message_post( ptr_message, "app_name", get_own_id() );
This snippet sends MSG_USER to application app_name, on the same Cybiko unit as it is sent. The call to Message_new in the first line of the listing is simply how you create a blank, new message.
Common Messages There are many types of messages. You don’t have to worry about most of them as they are handled just fine by CyOS through default processing. Table 3.4 shows the most common messages you will receive, and under what circumstances you will receive them. Table 3.4 Common messages Message
Meaning
MSG_KEYDOWN
A key has been pressed or a key has autorepeated.
MSG_KEYUP
A key has been released.
MSG_CHARTYPED
A character has been generated based on key presses.
MSG_QUIT
A quit message has been posted.
MSG_GOTFOCUS
The application has received focus.
MSG_SHUTUP
Something has happened that requires emergency shutdown of the application.
MSG_USER
A user-defined message.
If you make your own messages, you can start with MSG_USER, and use MSG_USER+1, MSG_USER+2, and so on. I suggest making #defines for your own messages. If you make and use your own messages, don’t forget to change the
Team LRN
Chapter 3: Introduction to Message Handling
25
maximum number of messages to look for in your call to cWinApp_get_message! Look at Listing 3.13 to see what I mean. Listing 3.13 //extra defines for user messages #define MSG_SYNC MSG_USER+1 #define MSG_UPDATE MSG_USER+2 //the call to cWinApp_get_message //include searching for MSG_SYNC and MSG_UPDATE ptr_msg=cWinApp_get_message(main_module.m_process,100,1,MSG_UPDATE);
Keyboard Messages By far, the most common messages you will receive and have to process are those that come from the keyboard: MSG_KEYDOWN, MSG_KEYUP, and, to a lesser extent, MSG_CHARTYPED. These are handled in a special way, using the Message_get_ key_param function. The syntax for this function is shown in Listing 3.14. Listing 3.14 struct KeyParam*
Message_get_key_param (struct Message* ptr_message);
Not much to it. The function takes in a pointer to a Message struct and returns a pointer to a KeyParam struct. That leads us to the question of what is in a KeyParam struct. The KeyParam struct contains encoded information about a keyboard event. Details of the structure can be found in Listing 3.15. Listing 3.15 struct KeyParam { int scancode; int mask; char ch; };
The scancode member is the scancode of the pressed key. The mask member contains information about the Shift key, the Fn key, and whether or not this is an autorepeat key. The ch member contains the ASCII code for the character generated. So, whenever the msgid field of a Message struct contains MSG_KEYDOWN, you know that a key has been pressed. You can use Message_get_key_param to find out information about what key was pressed and whether or not the Shift key is affecting it. Unless you are writing an application that deals with text manipulation, you usually won’t be concerned with the ch member of KeyParam. Instead, you will respond to the scancode member of KeyParam. Each key has its own scancode, including the seven section keys at the top of the device. Whether or not you respond to any or all
Team LRN
26
Chapter 3: Introduction to Message Handling
of the keys is entirely up to you, the programmer. A bit of a warning, though: some keys, like the section keys at the top of the device, have a special meaning to CyOS; for example, one of them brings you to the Applications section of the desktop, and when combined with the Fn key, these toggle sound or music, or change the contrast of the display, or put the device into suspend mode. For this reason, you might not want to override the behavior of those keys, unless you have a good reason for doing so. All of the scancodes start with KEY_, and most of them have common-sense names. All of the numeric keys and all of the alphabet keys are KEY_0 through KEY_9, and KEY_A through KEY_Z, so those are pretty easy to remember. The other keys are shown in Table 3.5. Table 3.5 KEY_* Constants Constant
Meaning
KEY_SECTION1
First section key (with the Cybiko icon below it)
KEY_SECTION2
Second section key (with the phone icon below it)
KEY_SECTION3
Third section key (with a person’s profile below it)
KEY_SECTION4
Fourth section key (with a picture of a clock below it)
KEY_SECTION5
Fifth section key (with a picture of a die below it)
KEY_SECTION6
Sixth section key (with a picture of a target below it)
KEY_SECTION7
Seventh section key (with a picture of a CyB’s head below it)
KEY_ESC
The Escape key
KEY_LEFT
Left arrow
KEY_UP
Up arrow
KEY_RIGHT
Right arrow
KEY_DOWN
Down arrow
KEY_INS
Insert key
KEY_DEL
Delete key
KEY_TAB
Tab key
KEY_SELECT
Select key
KEY_ENTER
Enter key
KEY_BACKSPACE
Backspace key
KEY_HELP
Help key (above the main keyboard, with the ? on it)
KEY_SHIFT
Shift key
KEY_CONTROL
Fn key
KEY_SPACE
Spacebar
KEY_QUOTE
The apostrophe/quote key
Team LRN
Chapter 3: Introduction to Message Handling
Constant
Meaning
KEY_COMMA
The comma/less than key
KEY_MINUS
The minus/underscore key
KEY_PERIOD
The period/greater than key
KEY_SLASH
The slash/question mark key
KEY_SEMICOLON
The semicolon/colon key
KEY_BACKSLASH
The backslash/pipe key
KEY_EQUAL
The equals/plus key
KEY_OPEN_SBRACKET
The open bracket/open brace key
KEY_CLOSE_SBRACKET
The close bracket/close brace key
KEY_BACKQUOTE
The reverse single quote key
27
Most of the keys in Table 3.5 have names that imply which key you are pressing, so you shouldn’t have much problem remembering which key has which scancode. Also, you are usually going to respond to the keys above the main keyboard anyway. KEY_ESC, KEY_UP, KEY_RIGHT, KEY_DOWN, KEY_LEFT, KEY_DEL, KEY_INS, KEY_TAB, KEY_SELECT, and KEY_ENTER are usually more than enough to do most of what you want to do. They are conveniently positioned, and don’t require the use of the stylus.
Default Message Processing If for some reason you don’t handle a message (and this includes any keyboard message you aren’t handling), you should send the message off to the default processing function, cWinApp_defproc. The syntax is shown in Listing 3.16. Listing 3.16 bool cWinApp_defproc ( struct cWinApp * ptr_app, struct Message * ptr_msg ) ;
This function returns TRUE if the message was handled, and FALSE if it was not. The ptr_app parameter is a pointer to a thread (main_module.m_process, usually), and ptr_msg is a pointer to the message you want to send to be handled by the default handler. The benefit of using the default handler is that you don’t have to write the code to handle section keys being pressed, or the special function keys. CyOS takes care of those for you. Treat it like a contract between you and CyOS; CyOS pledges to take care of all the special keypresses, and you promise to send them if you get them.
Team LRN
28
Chapter 3: Introduction to Message Handling
A Basic Message Pump So, you’ve got the goods on how to read messages, what is contained in them, and how to pass them along to CyOS when you don’t need them. Now you are ready to build a skeleton application, the foundation upon which you can build greater things. First, we need to take some basic steps, no matter what the program does. 1.
Initialize the module, so that your application will have a message queue.
2.
Read in messages and handle them.
3.
If you don’t make use of a message, be sure to send it to default processing.
4.
A program may end for several reasons, including receiving a MSG_QUIT or a MSG_SHUTUP, or the user telling the application to quit.
Listing 3.17 shows a main function that does all of these things. Listing 3.17 #include “cywin.h” module_t main_module; bool exit_application; long main(int argc, char* argv[], bool start) { struct Message* ptr_message; init_module(&main_module); exit_application=FALSE; while(!exit_application) { ptr_message = cWinApp_get_message(main_module.m_process,100,1, MSG_USER); if(ptr_message) { switch(ptr_message–>msgid) { case MSG_SHUTUP: // Processes system exit signal. case MSG_QUIT: exit_application = TRUE; break; case MSG_GOTFOCUS: // Redraws screen. break; case MSG_KEYDOWN: // Processes keyboard messages. if(Message_get_key_param(ptr_message)–>scancode == KEY_ESC) { exit_application = TRUE; break; } default: // Processes all unprocessed messages. cWinApp_defproc(main_module.m_process, ptr_message); }
Team LRN
Chapter 3: Introduction to Message Handling
29
Message_delete(ptr_message); } } // while(!exit_application) return(0); }
Now, if you were to take this code and compile it, move it to the Cybiko and run it, it would work just fine. Of course, it doesn’t actually do anything. It simply waits patiently for you to press the Escape key, and then exits back to the desktop. It’s not all that different from the Hello, world program you did in Chapter 2. You can use this very code as a framework upon which to develop real applications. Listing 3.17 shows the required “junk” the program has to have in order for it to work on the Cybiko.
Summary So ends my bit about messages. As you can see, they are highly important, which is why I spent so much time on them. This isn’t the last word on messages, either, but the remainder of the topic is covered in Chapter 21, when I show you wireless communications. Hopefully, you got enough out of this crash course on Cybiko messaging to be able to handle just about any message the Cybiko throws at you.
Team LRN
Chapter 4
Resources and Development Tools Overview I touched on the topic of resources back in Chapter 2, but I didn’t give them a complete explanation. There is a lot you can do with resources to help minimize the space taken up by your application archive, and in a memory-constrained environment like the Cybiko, you want to do everything you can to minimize application size. I’ll also go more in depth on the resources that have a special meaning in a Cybiko application, including Root.ico, Root.inf, Root.spl, and Intro.pic. Also, I’ll cover using the media converting utilities that come with the Cybiko SDK. Cybiko has made resource conversion very easy for us, and it is best that we make use of it.
Resources Defined A resource is simply a file stored within an application archive that is not code. Technically, the code itself is a resource, but that is not what I mean when I talk about resources. In this book, resource means something other than code stored in a .app archive. Resources come in several different forms. They can be compressed or uncompressed. They can be text data or binary data. Whether the resource is compressed or not depends on the usage of that resource. If the resource is read-only (and most resources are), you should probably make it compressed, so you can save space in the application archive. If the application stores data in the resource (like a top ten list, or a name field), you probably want to make a blank file big enough to store the data, and leave that file uncompressed. I’ve divided resources into four basic categories: text resources, picture resources, music resources, and other binary resources.
30
Team LRN
Chapter 4: Resources and Development Tools
31
Text Resources Text resources are just what they sound like. They are files containing nothing but text. This text could be anything from a Root.inf file, which contains important information about your application, to Root.spl, which contains the text shown during application startup, to help files, which are read when the help key is pressed. For the most part, text files should be compressed, since string data is one of the worst hogs of space. Only if you are going to write to this file from the program should you leave it uncompressed. Any file extension may be used for a text resource. Text resources do not have to be compiled. You can simply use a text editor like Notepad to create and modify them.
Picture Resources Picture resources contain binary data about one or more bitmaps. These are usually named with the .pic file extension, but it is not a requirement. I use .pic since I can then see what kind of resource it is at a glance. Root.ico is also a picture resource. Normally, you probably won’t find a reason to have a picture resource uncompressed, as pictures are almost always read-only. Picture resources come in several flavors. Some contain only a single image, some contain multiple images (up to 255 can be stored in a resource). Some are monochrome (black and white) and others are four-color grayscale. Fonts are a special type of picture resource. They are always monochrome and contain many images to allow the writing of text. Usually, fonts have the .fnt extension. In later chapters, I discuss loading bitmaps, bitmap sequences, and fonts. Bitmaps and fonts start out in another format that you use tools on your computer to edit. Then, you use a media converter utility to convert them into the Cybiko format.
Music Resources Music resources contain binary data about a music sequence. These are usually named with the .mus file extension, but that’s just a general rule, not a requirement. There is a media converting utility that takes MIDI files and converts them to the Cybiko music format.
Other Binary Resources “Other binary resources” is just a name I give to any resources that are not text, pictures, or music. You might have a special file format that describes a level in your game, or details what a map looks like, or whatever.
Team LRN
32
Chapter 4: Resources and Development Tools
Pick whatever file extension you like for these. For example, if you are describing a level, you might want to make it an .lvl file, or if it’s a map, a .map file. Unless your application needs to write to it, make it read-only. If you are going to be writing to the resource, make sure it will always be a fixed size. If you need to write out a file somewhere and it isn’t a fixed size, you are better off just saving it to a file.
Special Resources (Root.*, Intro.pic, and 0.help) I’ve already briefly described these in Chapter 2, but a treatment of resources could not be complete without talking about them again and in more detail. Each of these special resources absolutely must have the filenames listed here, and some of the text resources must be formatted in a particular way in order for them to work properly.
Root.ico We looked at root.ico back in Chapter 2. Root.ico is a picture resource that must be 48 pixels wide and 47 pixels tall. It is the picture that is shown on the desktop for your application. This resource may be compressed, but must be named Root.ico. This file is optional for an application archive, but if you do not include one, your application will show up with the default Cybiko icon. NOTE: Root.ico does not have to be 48x47, but using a different size will make the icon look disproportionate.
Root.inf I covered Root.inf in Chapter 2, but it is such an important file that it bears repeating. This is a text file with ten lines in it, each of which has a particular meaning. Line one contains where the application is to be shown. This value can be app, game, or root. If it is app, the application will show up on the desktop under the Applications icon (or upon hitting the Section 4 key). If game, it will show up under the Game icon (or upon hitting the Section 5 key). If root, it will show up on the root part of the desktop. As a general rule, try to avoid making too many root applications. Line two contains the name of the application, as it shows up on the desktop and in CyberLoad. Lines three through six should be left blank. If you don’t, unpredictable behavior will result. Line seven is the name of the application. If your application is named Test.app, then “Test” should be on line seven of Root.inf. Line eight is the version of the SDK used to compile the application.
Team LRN
Chapter 4: Resources and Development Tools
33
Line nine is the version number of CyOS needed to run this application. Before the desktop runs this application, it will reference the current version of CyOS on the device and check against line nine of the Root.inf. If the number in root.inf isn’t prior to or the same as the CyOS version, the application won’t run. You’ll get a little pop-up box telling you that you cannot run this application. Line ten is your copyright information. The Root.inf resource is special in that it is absolutely required for your application to show up on the desktop. None of the other special resources are required for your application to run. It must be named Root.inf, but it may be compressed if you wish.
Root.spl The Root.spl file is just a simple text file that you put in as a resource. It scrolls during application loading, right after Intro.pic is shown (if there is an Intro.pic resource) or the animated sequence with Root.ico is shown. There is no special format for Root.spl, but that must be the filename. It can be compressed if you like, and since it is generally a read-only file, it probably should be. This file is optional. If you don’t include it, you’ll just get the animated sequence using Root.ico. If you have an Intro.pic, you must have a Root.spl, and it must not be an empty file.
Intro.pic The Intro.pic resource is a picture resource containing a single four-color image that measures 160x100. It is shown during program startup. You can make it larger or smaller than this, but if it is smaller, it will still be against the top and left of the display, and the rest of the screen will be filled with white. If you use a larger image, it will be cropped to 160x100, and the extra bits of the picture will never be shown. NOTE: I found out while experimenting with these resources that Intro.pic file will only be shown if there is a non-empty Root.spl file.
0.help The help system for the Cybiko is very simple. Help files are simple text resources, but they do have something of a format. The first line in the *.help file contains the text to be shown in the title bar of the help window. A simple help file would look something like Listing 4.1. Listing 4.1 Help Title Text of help file.
Team LRN
34
Chapter 4: Resources and Development Tools
This case is, of course, extremely simple, and your own help files will naturally contain more information (after all, it’s not a help file unless it actually helps, right?). In order to make use of the default Cybiko help system, you must call cWinApp_defproc whenever you receive a KEY_HELP message. If you make your own help system, you can respond to KEY_HELP in any manner you wish. From the application itself, the only help file that will be called is 0.help, in which you should put key information about using your application. You can use other help files as well, as part of the Cybiko GUI (covered in Chapters 16 and 19). From experiments I’ve done, the help system runs in a separate thread, so it won’t look right unless you are using cWinApp_get_message with 0 as the timeout value.
Compiling Resources with Makeres.bat Picture resources and music resources need to be in the Cybiko format, and there are no editing tools currently available for the Cybiko format that you can use (I’m working on some, and I know there are others that are working on this problem as well). As luck would have it, Cybiko did provide us with some command-line tools to convert from other formats into the special Cybiko formats. These tools are listed in Table 4.1. Each of these utilities can be found in the Bin subfolder of the Cybiko SDK installation folder. Table 4.1 Utilities for resource conversion Tool
Purpose
2mus
Converts MIDI to Cybiko music file.
mus2txt
Converts Cybiko music file to MIDI sequence text format.
t2mf
Converts MIDI sequence text format to MIDI.
2pic
Converts one or more bitmaps into the Cybiko picture format.
PicView
(Non-command line) Allows viewing of Cybiko pictures/picture sequences.
Normally, when compiling resources, I put a batch file in Res/src of the project’s directory. I call this file Makeres.bat, so that all I have to do is double-click on the batch file, and all of my resources are rebuilt (as you might be able to tell, I don’t really like using the command line!). A normal Makeres.bat file looks something like Listing 4.2. Listing 4.2 path %CYBIKO_SDK%/bin 2pic ../root.ico icon.bmp 2pic ../intro.pic intro.bmp
Team LRN
Chapter 4: Resources and Development Tools
35
This listing has the code that will convert my source bitmaps (Icon.bmp and Intro.bmp) into the Cybiko file equivalents (Root.ico and Intro.pic) in the parent folder (Res). If I had more pictures or music (and I usually do), there would be additional lines for each resource in Makeres.bat. Some of the tools have extra switches that specify how a resource is to be made, and while these are all documented in the SDK help file, I figured I would also put them here, and give a better explanation on how to fine tune your resource building.
2mus The 2mus tool is pretty basic. The syntax for it is shown in Listing 4.3. Listing 4.3 2mus .mid
This is a pretty spartan tool. It only has one parameter, and will be the same name for both the source MIDI file and the Cybiko music file. Since you cannot direct where the music file will be created, you will either have to move it yourself or put the MIDI file in the Res subfolder of your project. I personally don’t use 2mus. I make my music files using a hex editor, so I never need to compile them.
mus2txt If you have a Cybiko music file, and you wish to convert it back to a MIDI (for whatever reason), you must use mus2txt followed by t2mf. I’m not sure why this is a two-step process. Lising 4.4 shows the syntax for using mus2txt. Listing 4.4 mus2txt .mus .txt
is the name of your music file, and is the desired name of the output text file. This text file can then be sent through t2mf to convert it to a MIDI file.
t2mf Once you have a text file that has been generated by mus2txt, you can send it through t2mf to convert it to a MIDI file. Listing 4.5 shows the syntax. Listing 4.5 t2mf .txt .mid
is the name of your source text file, and is the desired name of the destination MIDI file.
Team LRN
36
Chapter 4: Resources and Development Tools
I’m not a big fan of using MIDI file editors for making Cybiko music. MIDI files have multiple voices and instruments and tracks that can be playing simultaneously. The Cybiko only has one, so a regular MIDI editor is sort of overkill in my opinion, but it is a common enough format that you can find a nice shareware MIDI editor at www.download.com and make your own music files (if you are of a musical bent).
2pic This tool is a great little utility. It takes one or more source bitmaps and generates a Cybiko picture file which contains a bitmap sequence. A bitmap sequence contains at least one bitmap, and may contain as many as 255 bitmaps. However, with 2pic, your effective limit is about 20 to 30, since the command line can only have a limited number of characters for each command. (I’ve written a tool that makes bitmap sequences that get rid of this limit, but I’ll show you that a little later on.) With 2pic, you can make monochrome or four-color pictures, and you may specify a color to use for transparency. Listing 4.6 shows the syntax for 2pic. Listing 4.6 2pic [<mode>] [.pic] .bmp|dib|fli|flc|cel ...
This is perhaps the most complex and robust of the command-line resource conversion tools. The <mode> may be 0, 1, 2, 3, –f, or –p. These modes are shown in Table 4.2. Table 4.2 2pic modes Mode
Meaning
0
2-bit picture; use white as transparency.
1
2-bit picture; use light gray as transparency.
2
2-bit picture; use dark gray as transparency.
3
2-bit picture; use black as transparency.
–f
1-bit picture; white is transparent.
–p
1-bit picture; no transparency.
If no mode is specified, then the output will be a 2-bit picture with no transparency. Cybiko transparency is different than you might think, but I’ll be covering that in Chapter 9. is the destination picture file, i.e., ../root.ico or ../intro.pic, or whatever else your picture might be named. The (and there can be one or many) may be a bmp, dib, fli, flc, or cel format picture. Since I’m a Windows user, my primary format is bmp anyway, so this suits me just fine. In fact, I had never even heard of fli, flc, or cel, and my primary graphical tool (PaintShop Pro) does not support them, so I just use bmps. Heck, with bmps, you
Team LRN
Chapter 4: Resources and Development Tools
37
can use MS Paint, ensuring that every Windows-based computer on the planet can make graphics for the Cybiko. There are also some excellent free bitmap editors to be found on the web that will do the job just fine. CAUTION: When creating your source bitmaps, here’s something to keep in mind. The normal Windows palette contains a dark gray and a light gray, which you might be tempted to use in your images. Don’t do that, they won’t work. Instead use RGB (83,83,83) for dark gray, and RGB (163,163,163) for light gray, to ensure that the colors will come out correctly on your Cybiko. I learned this the hard way.
I would say to use 2pic for any bitmap sequence up to about ten images in size. Above that, I would suggest using the tools I’ve made, Fontmake.exe and Bseqmake.exe, which are detailed later in this chapter.
PicView PicView is the only non-command-line utility included in this list. It is a full Windows program that you can use to look at bitmap sequences. It’s not as full featured as you might like it to be (hence, there are other tools being written by third-party developers). PicView.exe is shown in Figure 4.1.
Figure 4.1: PicView.exe
Team LRN
38
Chapter 4: Resources and Development Tools
With PicView, you can look at the contents of a Cybiko picture file, add images to it, export the first image in the list, and change the colors used by the program. In other words, it doesn’t do a whole lot other than look at a .pic file and give you some statistics about it. Theoretically, you can use PicView to make bitmap sequences of whatever length you wish. In practical terms, it takes way too long to do this, so you’d be better off using Bseqmake or Fontmake.
Third-Party Resource Conversion Tools Because some of the Cybiko resource conversion tools are a little too primitive, I decided to take matters into my own hands. After trying several times to make a font file using 2pic, I created Fontmake.exe and a flexible bitmap sequence maker called Bseqmake.exe. Both of these are command-line tools, and designed to be placed in the Bin subfolder of the Cybiko SDK, so you can use them just like any of the Cybiko resource conversion tools. These tools can be found on the CD under the Tools directory. The full VC++ source is included with these tools, and you are free to use, modify, and extend them, as long as you give me due credit.
Fontmake.exe When I began trying to make fonts using 2pic, I kept getting errors because the line in my batch file was too long. Eventually, after several hours of frustration and yelling at my cats to leave me alone, I just said to heck with it, and wrote Fontmake.exe. Fontmake creates a fixed-width font that contains 96 characters (characters 32 through 127), encompassing all of the standard ASCII characters you might need for writing text. The standard Cybiko fonts have more than this, to include special characters like the (©) symbol and diacritics, but a 96-character font should be more than sufficient for general usage. The syntax for Fontmake.exe is shown in Listing 4.7. Listing 4.7 fontmake
is the input file (Fontmake supports bmp files only), and is the output .pic file (there is no default extension, but I usually use .fnt for my font files). All 96 characters of the font are placed into a single bitmap, as shown in Figure 4.2.
Team LRN
Chapter 4: Resources and Development Tools
39
Figure 4.2: A source bitmap for Fontmake.exe
The figure is blown up to 16 times its original size (the original is only 128x48), so you can see the pixels a little better. Fontmake divides an image like this up into 16 columns and 6 rows, for a total of 96 images. Notice that the first image is the space, and it follows the standard ASCII character set from there on. Each of the characters in the figure are 8x8 pixels in size (this font is an exact replica of the old 8x8 DOS font). There are other fonts located on the CD under Resources/fonts. All of these can be modified and rebuilt using Fontmake. To use Fontmake, simply make a bitmap 16 cells wide and 6 cells tall. The cell may be of any size up to 255x255 (larger than this and the picture would not be loadable on the Cybiko). In each cell, draw the character corresponding to that position. When you are done, send the file to Fontmake, and you’ve got yourself a Cybiko font, which you can use just like any of the predefined Cybiko fonts.
Bseqmake.exe Bseqmake.exe is a tool I wrote so that I could use a single bitmap as the source for an entire sequence of pictures, rather than have a long command line, and have to work with dozens of pictures. It’s a more advanced tool than Fontmake, in that it supports transparency, and it supports cells of multiple sizes in the same bitmap. The syntax for Bseqmake is shown in Listing 4.8. Listing 4.8 bseqmake
is the source bitmap (Bseqmake only supports bmp), and is the name of the destination picture file (you can use any extension you like, but I tend to stick with .pic). Figure 4.3 shows a picture of a sample source image for Bseqmake. This image can be found in the zip file that contains Bseqmake.
Team LRN
40
Chapter 4: Resources and Development Tools
Figure 4.3: Sample Bseqmake source image
You should probably find this picture (it’s called Dungeon.bmp) and open it in a graphics editor, because the explanation of how the picture is formatted discusses the colors of the pixels, but the figure above is grayscale. The first thing I would like to point out is the green border around each of the images. Each image is separated from its neighbors by this green border. Along the top and left of the picture, there are magenta pixels that correspond to the locations of the green borders. This is how Bseqmake knows where rows and columns start and stop, so they must be there. You can replace magenta and green with whatever colors you wish, and Bseqmake will still work. On the right edge of the bitmap, under the magenta pixel are four dots—black, dark gray, light gray, and white. They must be there for Bseqmake to work. These pixels are read in to control which color in the bitmap means the colors black, dark gray, light gray, and white for the Cybiko. You can use any color you wish for these, just be consistent. The magenta portions of the image specify transparency. Be careful with this. Within a cell, any column where there is a transparent pixel will make the entire column transparent. Similarly, any row with a transparent pixel will be totally transparent. Hence, the non-transparent part of the image is always rectangular. The first cell (cell#0) is the upper-left cell, and it increases to the right. Once the end of a row has been reached, the count continues on the next row. So, if you have four columns and four rows, the image numbers are as follows: Row 1: 0 1 2 3 Row 2: 4 5 6 7 Row 3: 8 9 10 11 Row 4: 12 13 14 15 You may have any number of columns or rows, and they do not all have to be the same size. Don’t put more than 255 pictures into the bitmap (17 columns by 15 rows, or vice versa), and don’t make any row or column wider than 255 pixels. Bseqmake is a good little tool, and I’ve gotten quite a bit of use out of it. I hope that you will, too.
Team LRN
Chapter 4: Resources and Development Tools
41
Adding Resources Using Filer.list Once you have all of your resources in the proper format and have placed them into the Res directory, you need to let your project know what resources you wish to use, and whether or not you would like them to be compressed. This is done in Filer.list. Filer.list is the source file for another development tool called Filer.exe, which is called by Vmake.exe when processing your makefile. You can also use Filer.exe to make archives that don’t contain any executable code (if, for instance, you wanted to make a game builder in which you simply supply an archive and the game reads all of its data from it). Full documentation for using Filer.exe can be found in the SDK help files. Each resource should be listed in Filer.list. They don’t have to be in any particular order. Since Filer.exe is called from the parent director of your project, you need to add “Res/” before the name of your resources. By default, resources sent to Filer.exe are compressed, but if you are using a different makefile than the one supplied, this might not be the default. To specify that a resource is to be compressed, you put a plus sign (+) in front of the resource, as in “+res/root.ico.” This makes the resource compressed, even if the default is uncompressed. Similarly, if you want the resource to be uncompressed, you place a “–”, as in “–res/root.ico.” This overrides any default. Usually, you’ll just use + and – in your filer.list. However, there are a few other symbols you might be interested in. The asterisk (*) stores the file but not the filename. The question mark (?) stores the filename but not the extension (i.e., “root” instead of “root.ico”). Finally, the number sign (#) starts a comment. Listing 4.9 shows a sample Filer.list file. Listing 4.9 ######################################### #root.* resources ######################################### +res/root.ico +res/root.inf +res/root.spl ######################################### #intro.pic ######################################### +res/intro.pic ######################################### #top ten file(uncompressed) ######################################### –res/topten.txt ######################################### #secret text file(no file name, compressed) ######################################### +@res/secret.txt
Team LRN
42
Chapter 4: Resources and Development Tools
So, as you can see, you have a lot of control over how your resources are stored in your application archive, whether you use it or not.
Makefiles Finally, I wanted to cover the innards of makefiles. Once upon a time, back when dinosaurs roamed the earth, using a makefile was the best way to build a project. Today, most systems have fancy IDEs (integrated development environments), in which you can build a project just by clicking a button. These IDEs still come with makefile utilities, of course, for the old-school programmers who have been programming since the late Jurassic period and don’t like IDEs. I give makefiles their due. They are an extremely flexible way to build programs without clicking on dozens of different menu items on toolbars to set up how you want your application to be made. It is also a lot easier to make a program that reads in a makefile and builds the application than it is to develop an IDE. Cybiko’s main goal is to make hardware, and games and applications to run on this hardware, not to make tools that are easy for a person to use. So we have to use makefiles until such time as someone makes an IDE for the Cybiko. I don’t blame them; I would have done the same thing. So, we have these makefiles, which to any normal human and programmers who are used to IDEs (i.e., me) look like a bunch of gibberish. I invite you to look at Listing 4.10, which contains the makefile for CyBk2_2. Listing 4.10 NAME = CyBk2_2 OBJ = src/$(NAME).o RES = res/filer.list PP CC AS LN LD RM
= = = = = =
vcpp vcc1 vas vlink filer vrm
.SUFFIXES : .c all : $(NAME).app $(NAME).app : tmp/bytecode.bin $(RES) @echo building app archive... @$(LD) a $@ @res/filer.list tmp/bytecode.bin "$(CYBIKO_SDK)"/lib/main.e tmp/bytecode.bin : $(OBJ) @echo linking ...
Team LRN
Chapter 4: Resources and Development Tools
43
@$(LN) –o $@ src/*.o .c.o : @echo compiling $<... @$(PP) –I"$(CYBIKO_SDK)"/inc $< | $(CC) | $(AS) –O –o $@ src/$(NAME).o : src/$(NAME).c clean : @echo cleaning... @$(RM) –f tmp/*.* src/*.o $(NAME).app new : clean all
Unless you are familiar with makefiles, this code listing probably looks like nonsense. That’s what it looked like to me at first. If you stare at it long enough, however, it begins to make sense. The top part of the makefile contains stuff like “NAME = CyBk2_2.” These are macros, sort of like variables, that are used later on. Once you have “NAME = something,” you can use $(NAME) to refer to it later in the script, and the makefile processor replaces it with whatever you said NAME ought to be. Make.bat runs Vmake, which is the makefile processor. The contents of Make.bat are shown in Listing 4.11. Listing 4.11 path %CYBIKO_SDK%/bin vmake new –p2 %1
The first line of Make.bat simply ensures that the Bin directory of the Cybiko SDK is in our executable path, so we don’t have to specify where each tool is all the time. The second line calls vmake, with the parameters of new, –p2, and %1. %1 is used in DOS batch files to specify “use the first parameter sent to this batch file.” We did not send any such parameters to the batch file, so this is blank. If we had a different name for our application besides the default name, we would need to have this parameter. –p2 is a switch. There are three of these, –p0, –p1, and –p2. These tell Vmake if and how we want it to pause. p0 means to never pause. p1 means to pause only if there is an error. p2 means to always pause, whether there was an error or not. The first parameter, new, specifies where in the makefile you want Vmake to start. In this makefile, new is on the last line, with a colon and “clean all” after it. If you scroll up a few lines, you will also see “clean” and “all,” both with colons after them. These are labels which Vmake uses to run the makefile. So, with Vmake new, new will be run, which will run clean first, and then all. So, as you can see this is not total gibberish, and there is more than enough documentation on makefiles and how to use them in the SDK help. In this book, we will
Team LRN
44
Chapter 4: Resources and Development Tools
just be using the makefile above and changing the NAME= macro line. If you want to get more into makefiles, I suggest looking at the SDK help.
Summary I hope you now have a better grasp on what resources are, how to make them, and how to include them in your applications. In addition, you should have a good grasp on the developer tools that can assist you with your resource creation. I know these first few chapters have been a little bit light in the code department, but the learning curve for doing things the Cybiko way is a little steep. Luckily, it will soon be over, and we can get onto the fun stuff.
Team LRN
Chapter 5
The Cybiko Way of Object-Oriented Programming Overview This chapter is about coding conventions that make the SDK documentation more readable and also demonstrate how most of the code for this book is going to be written. If you are a C++ programmer, you will find this chapter pretty easy to read, but you are going to think that doing all of this stuff is sort of silly. If you are a C programmer who has never touched C++, you should also have an easy time reading this chapter, and you probably use similar methods in your C programs. This chapter is on object-oriented programming, and how it works on the Cybiko. I’m not going to go into great detail about the theory of OOP, because it’s one of those things that you either like or you don’t. I personally am a C++ programmer, and I like OOP. I think that the equivalent C code is a little strange to look at and bulky, but I am not paid for my opinion, I am paid for content. I know that a number of you will be C programmers, and since the Cybiko only allows C, that’s what we have to use.
OOP (Object-Oriented Programming) If you’ve never been exposed to the idea of OOP, you might want to read this next bit where I explain the basic theory under which this type of programming works. If you are comfortable with OOP, feel free to skip it. I’m not going to go into long and boring graphic detail about OOP, since there are many books on the topic (really big books, usually very boring and dry) and this is a book on game programming, so I don’t wish to bore you. Instead, I’m going to give you
45
Team LRN
46
Chapter 5: The Cybiko Way of Object-Oriented Programming
the “big picture” story of OOP—enough to form a foundation, and if you later wish to explore it in greater detail, you can do so on your own. The three pillars of OOP are Encapsulation, Inheritance, and Polymorphism. These three pillars are in capital letters to appease the gods of OOP, who would strike me dead for not capitalizing them since they are so important.
Encapsulation Central to the idea of OOP is the use of objects. Objects are variables, just like any other, but they are independent of other objects (except when talking to one another), and usually incorporate aspects of an entire autonomous thing, like a car. A car is a separate entity from the road, the driver, the passenger, and the other cars, so it would be a good example of something you might make into an object. An object is usually said to be of a certain “class.” In the case of a car object, it would be of the Car class. All of the internal workings of a car are just that, internal to the car. If we were programming a car simulation program, we shouldn’t care about how the car works inside. We should simply be able to tell the car “turn left,” “turn right,” “speed up,” “slow down,” or “run over little old lady.” Now, a car has a number of things that can be considered data, such as which gear it is in, how far the accelerator has been pressed, whether or not the parking brake is activated, the orientation of the steering wheel, and so on. To incorporate all of this, we might make a struct, called Car, as shown in Listing 5.1. Listing 5.1 struct Car { int steering_wheel_orientation; int gas_in_tank; bool running; bool parking_brake_activated; bool brake_down; int accelerator_level; bool door_ajar; //and so on.... };
This example has just a scant few of the things that make up a car, but I think you get the idea. All of the aspects needed for a car should be stored in a variable of the Car struct, to make it easier. Each of these items in the Car struct is called a “field,” or in C++ parlance a “member.” This is half of encapsulation. The other half deals with the operations that can take place on a car—for example, starting the car, stopping the car, opening the door, turning the steering wheel, activating/deactivating the parking brake, stepping on the pedals, and so on.
Team LRN
Chapter 5: The Cybiko Way of Object-Oriented Programming
47
In C++, we would just make these functions part of the struct. We can’t do that in C. In normal C, we could put function pointers in for all of these functions to accomplish the same thing. Cybiko C doesn’t allow the use of function pointers. So, we are left with using functions external to the struct to allow us to perform these functions. Listing 5.2 shows what some of these functions might look like. Listing 5.2 bool Car_start(struct Car* ptr_car); bool Car_shut_off(struct Car* ptr_car); int Car_set_steering_direction(struct Car* ptr_car ,int dir); bool Car_set_parking_brake(struct Car* ptr_car ,bool value); //and so on
Each of these functions takes as the first parameter a pointer to a Car struct, which allows us to say which car we want to perform the activity on. Functions with more than one parameter allow us to say to what degree we want the activity to be performed. As you can see, it doesn’t really matter what is going on inside the Car_start function; we just want the thing to start. The return values in these examples tell us the relative success of the activity. For example, Car_start returns whether or not the car is running. If a call to Car_start does not start the car, something must be wrong with the car. Each of these functions, if they were inside of the struct, either with the C++ mechanism or function pointers, would be called “methods” or “member functions.” They are not, they are merely functions, but for the purpose of Cybiko programming I will call them member functions, because that is their intended use. And so, encapsulation is the bringing together of data and functions to better model your programming.
Inheritance So, we have a Car class, from which we can make car objects. This is good and useful. But what about a truck? A truck shares many similarities with a car, certainly, but it is not a car. Just ask anyone who owns a truck. The same thing goes for vans, recreational vehicles, tractors, riding lawn mowers, and motorcycles. One thing that we can say about all of these things is that they are automobiles. They all run on internal combustion (we’ll ignore electric cars for now), and they all move across the land using friction. So, if we are dealing with many different types of automobiles, we might want to make a struct called Automobile, with all of the members and member functions we can think of that pertain to the greater extent of the automobile family, and we would want Car to be an automobile with the special attributes pertaining to a car, and a Van to be an automobile with the special attributes pertaining to a van, and so on. In regular C, this would look like Listing 5.3.
Team LRN
48
Chapter 5: The Cybiko Way of Object-Oriented Programming
Listing 5.3 //automobile “class” struct Automobile { //automobile stuff here }; //car “class” struct Car { struct Automobile* ptr_auto;//contains information common to automobiles //more stuff here, specific to a car };
This will work, but it is clumsy. If we were calling an automobile function for a car, there would be excessive use of the –> operator, so this solution is more trouble than it is worth. It would be better if we could use a few C++ extensions and use inheritance as shown in Listing 5.4. Listing 5.4 //automobile “class” struct Automobile { //automobile stuff here }; //car “class” struct Car: public Automobile { //more stuff here, specific to a car };
I put the pertinent part of the listing in bold type. A colon after the type, with the word “public” and a structure name says, “I want to use all of the members of Automobile in Car, and in addition I want to use . . . . ” This is how C++ works. As you might guess, it comes in pretty handy. Luckily, Cybiko C isn’t pure ANSI C. It has a few C++ extensions, including the one that allows us to use the code in Listing 5.4. This ability to extend a base class is inheritance. Automobile, in this case, would be the “parent class” or “base class,” and Car would be the “child class” or “derived class.” Now we can make our Car functions, and send a struct Car* as the first parameter. We do, however, “inherit” the member functions for Automobile, but since the member functions are done in a strange way, they are better discussed under polymorphism.
Team LRN
Chapter 5: The Cybiko Way of Object-Oriented Programming
49
Polymorphism Polymorphism is the last pillar of OOP, and perhaps the most important. This is what enables us to reuse code written for other classes. For example, you might have an Automobile_start function, like the one shown in Listing 5.5. Listing 5.5 bool Automobile_start(struct Automobile* ptr_auto);
Now that we have this function, and we know that Car, a child class of Automobile, starts in exactly the same way, we don’t want to have to make a separate Car_start function; we would like to make use of the Automobile_start function, since the start procedure works in the same way. Luckily, in Cybiko C, we don’t have to write a separate function. Automobile_ start is just as happy to take the struct Car* as it is the struct Automobile*, since Car is derived from Automobile and has all of the pertinent data. This is polymorphism. Of course, we don’t want to call Automobile_start to start our car. Rather, we’d like to call Car_start. We can do this by making a simple macro, as shown in Listing 5.6. Listing 5.6 #define Car_start Automobile_start
Now, when you compile your application, the preprocessor will replace any Car_start you have with Automobile_start, so you don’t have to remember which base class has which functions.
Constructors and Destructors In OOP, there are two types of member functions that are set apart from the rest. These are the constructor and the destructor, and they each have a special purpose.
Constructors A constructor is said to “construct” an object. It fills in an object with the default values, which may or may not be supplied along with the constructor. For example, an Automobile constructor may set the steering wheel to the zero position, set the gas tank to full, set the running member to FALSE, and so on. In addition, if there are any dynamically allocated parts of the object, the constructor allocates the memory for these. An example of a constructor function is shown in Listing 5.7.
Team LRN
50
Chapter 5: The Cybiko Way of Object-Oriented Programming
Listing 5.7 void Automobile_ctor(struct Automobile* ptr_auto);//constructor for automobile
A class might have more than one way in which it can be constructed. You might, for example, want a default constructor to set the gas tank to full, and another to allow you to set the level of the gas tank yourself. To do this, you make another constructor, and name it with _Ex at the end, as shown in Listing 5.8. Listing 5.8 void Automobile_ctor_Ex(struct Automobile* ptr_auto,int fuel_level);
If you have more than two constructors, you can use _Ex1 instead of _Ex, and use _Ex2 for the third constructor, etc. Have as many constructor functions as you like. However, you probably want to make the usual constructor (the one without any _Ex suffix) take only one parameter and have it default all of the values, if you can possibly do so. This is how all of the Cybiko SDK constructors are done, and one of the goals of programming is to be consistent.
Dynamic Allocation Constructors You will not always just declare objects and construct them using the constructor functions, as shown in Listing 5.9. Listing 5.9 struct Car MyCar; Car_ctor(&MyCar);
There will be times when you will dynamically allocate your objects. To do this, you use code similar to Listing 5.10. Listing 5.10 struct Car* ptr_MyCar; ptr_MyCar=(struct Car*)malloc(sizeof(struct Car)); Car_ctor(ptr_MyCar);
As you can see in Listing 5.10, the dynamic allocation of the car is a little wordy. It might be better to have a function that you just call with no parameter that dynamically allocates one for you, constructs it, and returns the pointer to it, as shown in Listing 5.11. Listing 5.11 struct Car* ptr_MyCar; ptr_MyCar=Car_New();
The code in Listing 5.11 is not only shorter, but it better communicates to other programmers what your intent was. Your intent was to dynamically allocate a new car and
Team LRN
Chapter 5: The Cybiko Way of Object-Oriented Programming
51
construct it. For this, we use the _New member function. If you have multiple constructors, and your objects are likely to be dynamically allocated with regularity, you may wish to make some _New functions, one for each constructor. Make these functions match up with the appropriate _ctor, such as _New, _New_Ex, _New_Ex1, and so on. The code for a _New function is quite simple, as shown in Listing 5.12. Listing 5.12 struct Car* Car_New() { struct Car* ptr_car; //allocate memory for a new car object ptr_car=(struct Car*)malloc(sizeof(struct Car)); //construct the car Car_ctor(ptr_car); //return the new car return(ptr_car); }
With such a function, the code in Listing 5.11 is possible. You would make a similar _New function for every constructor for your class.
Destructors On the flip side of a constructor is a destructor, which deallocates any memory that the constructor and sequential operations might have allocated for the object. Unlike the constructor or the _New functions, there should only be one destructor, as shown in Listing 5.13. Listing 5.13 void Car_dtor(struct Car* ptr_car,int memflag);
This is what all of the Cybiko destructors look like. The first parameter is a pointer to the object, and the second is one of two values, LEAVE_MEMORY or FREE_MEMORY. If the object was dynamically allocated (either manually or by using a _New function), use FREE_MEMORY. If it was not, use LEAVE_MEMORY. Some examples are shown in Listing 5.14. Listing 5.14 struct Car car1; struct Car* ptr_car2; //construct car 1 Car_ctor(&car1); //construct car 2, dynamic allocation ptr_car2=Car_New(); //other code goes here //destruct car 1
Team LRN
52
Chapter 5: The Cybiko Way of Object-Oriented Programming
Car_dtor(&car1,LEAVE_MEMORY); //destruct car 2 Car_dtor(ptr_car2,FREE_MEMORY);
A destructor function should clean up the object, no matter which constructor was used, and no matter if it was dynamically allocated or not. Some example code of what a destructor might look like can be found in Listing 5.15. Listing 5.15 void Car_dtor(struct Car* ptr_car,int memflag) { //code to clean up car goes here //if memflag is FREE_MEMORY if(memflag==FREE_MEMORY) { //free the memory for this object free(ptr_car); } }
Summary Now you should have at your command the basics of OOP on the Cybiko. It’s a little weird at first, but it grows on you. Pretty soon, after you’ve programmed the Cybiko a while, you’ll go back to whatever other platform you program for, and you’ll find yourself doing the weird Cybiko OOP in your normal projects. I couldn’t possibly do justice to a big topic like OOP, but I certainly hope that you have found this crash course to be helpful. We have just one more stop on our way through Cybiko fundamentals, and then we can get into more interesting subject matter.
Team LRN
Chapter 6
Program Frameworks Overview In a general purpose programming language like C, there are many types of applications and games you might write. Each application has its own needs, and lends itself to a certain program framework. A program framework is nothing more than a skeleton upon which the rest of the program can work. I’ve come up with six basic frameworks upon which you might build your applications. There are undoubtedly more, but for most purposes one of these should do the job. The six frameworks are called Quick-Running, Event-Driven, Idle-Loop, Real-Time, Dialog-Based, and Multi-threaded. This chapter is going to be pretty code heavy, since I am presenting most of these frameworks in this chapter. (Dialog-Based and Multi-threaded frameworks are covered in later chapters.)
Quick-Running Framework By far, the simplest running framework is the Quick-Running framework. There is no call to init_module or cWinApp_get_message; essentially, this is just a plain main function with no extras. Normally, you would just run this program from the console. You wouldn’t make any end-user games or applications with it; it would usually just be some sort of utility. One thing you might use it for is a little utility program that you run from your own application, such as something that performs some operation on a file (perhaps a conversion utility that runs on the Cybiko). Generally, you won’t use this framework much, but I have included it in order to be more complete. The framework is shown in Listing 6.1. The workspace for this application can be found on the CD, named CyBk6_1.
53
Team LRN
54
Chapter 6: Program Frameworks
Listing 6.1 /* ******************************************* *CyBk6_1.C * *27FEB2001 * *"Quick Running" Framework ******************************************* */ //master include file for cybiko sdk #include "cywin.h" //main functions long main(int argc, char* argv[], bool start) { //code for application goes here //return 0, we're done return 0; }
As you can see, the program does almost nothing. It returns zero, but it does nothing else. The first example program in Chapter 2 (CyBk2_1) is an example of the quickrunning framework in use.
Event-Driven Framework The event-driven framework includes the use of init_module and message processing, as do the idle-loop and real-time framework. In fact, they all pretty much work in the same way, but the subtle differences make them useful for different types of applications. In the event-driven framework, the application waits patiently for a message, then handles the message, then goes back to waiting for a message. This is good for most types of non-game applications, like word processors and other tools where there is nothing happening in the background. Most turn-based strategy games and puzzle games fall into this category. Action games, where timing is important and things happen even when the messages aren’t being received, are not good candidates for the event-driven framework. Listing 6.2 shows the event-driven framework. A workspace with this code can be found on the companion CD in the file CyBk6_2. Listing 6.2 /* ******************************************* *CyBk6_2.C
Team LRN
Chapter 6: Program Frameworks
* *16FEB2001 * *Event Driven Framework ******************************************* */ //master include file for cybiko sdk #include "cywin.h" //global variables struct module_t main_module;
//main module
//forward declarations for functions bool Prog_Init(); void Prog_Done();
//initialize the program //clean up for the program
//message routing functions bool Message_pump(struct cWinApp* ptr_win_app); bool Message_handle(struct Message* ptr_message); //message handling functions //key handlers bool OnKeyDown(int scancode,int mask,char ch); bool OnKeyUp(int scancode,int mask,char ch); bool OnCharTyped(int scancode,int mask,char ch); //timer bool OnTimer(); //power down bool OnPowerDown(); //quit bool OnQuit(); //paint bool OnPaint(); //files bool OnFiles(); //focus bool OnLostFocus(); bool OnGotFocus(); //launch bool OnLaunch(); //device bool OnDevice(); //ping bool OnPing(); //shut up bool OnShutUp(); //user bool OnUser(); //main functions
Team LRN
55
56
Chapter 6: Program Frameworks
long main(int argc, char* argv[], bool start) { //declare local variable for message retrieval struct Message* ptr_message; //initialize the main module init_module(&main_module); //initialize the program if(Prog_Init()) { //pump messages Message_pump(main_module.m_process); } //clean up after the program Prog_Done(); //return 0, we're done return 0; } bool Prog_Init() { //initialization //return TRUE if program initialized, and FALSE if it did not return(TRUE); } void Prog_Done() { //cleanup } bool Message_pump(struct cWinApp* ptr_win_app) { bool quit=FALSE; struct Message* ptr_msg; //while there are still messages coming, handle them while(!quit) { ptr_msg=cWinApp_get_message(ptr_win_app,0,1,MSG_USER); quit=!Message_handle(ptr_msg); Message_delete(ptr_msg); } } bool Message_handle(struct Message* ptr_message)
Team LRN
Chapter 6: Program Frameworks
{ //if there is a message... if(ptr_message) { //...check what kind of message it is, and process it switch(ptr_message–>msgid) { case MSG_KEYDOWN: //key has been pressed { if(!OnKeyDown(Message_get_key_param(ptr_message)–>scancode, Message_get_key_param(ptr_message)–>mask, Message_get_key_param(ptr_message)–>ch)) { cWinApp_defproc(main_module.m_process, ptr_message); } }break; case MSG_KEYUP: //key has been released { if(OnKeyUp(Message_get_key_param(ptr_message)–>scancode, Message_get_key_param(ptr_message)–>mask, Message_get_key_param(ptr_message)–>ch)) { cWinApp_defproc(main_module.m_process, ptr_message); } }break; case MSG_CHARTYPED: //character has been generated { if(OnCharTyped(Message_get_key_param(ptr_message)–>scancode, Message_get_key_param(ptr_message)–>mask, Message_get_key_param(ptr_message)–>ch)) { cWinApp_defproc(main_module.m_process, ptr_message); } }break; case MSG_TIMER: //timer event has fired { if(OnTimer()) { cWinApp_defproc(main_module.m_process, ptr_message); } }break; case MSG_POWERDOWN: //a powerdown message has been received { if(OnPowerDown()) { cWinApp_defproc(main_module.m_process, ptr_message); } }break; case MSG_QUIT: //a quit message has been received {
Team LRN
57
58
Chapter 6: Program Frameworks
case
case
case
case
case
case
case
if(OnQuit()) { cWinApp_defproc(main_module.m_process, ptr_message); } return(FALSE); }break; MSG_PAINT: //a paint message has been received { if(OnPaint()) { cWinApp_defproc(main_module.m_process, ptr_message); } }break; MSG_FILES: //a filed message has been received { if(OnFiles()) { cWinApp_defproc(main_module.m_process, ptr_message); } }break; MSG_LOSTFOCUS: //the application has lost focus { if(OnLostFocus()) { cWinApp_defproc(main_module.m_process, ptr_message); } }break; MSG_GOTFOCUS: //the application has received focus { if(OnGotFocus()) { cWinApp_defproc(main_module.m_process, ptr_message); } }break; MSG_LAUNCH: //the application has received a launch message { if(OnLaunch()) { cWinApp_defproc(main_module.m_process, ptr_message); } }break; MSG_DEVICE: //the application has received a device message { if(OnDevice()) { cWinApp_defproc(main_module.m_process, ptr_message); } }break; MSG_PING: //a ping has been received {
Team LRN
Chapter 6: Program Frameworks
if(OnPing()) { cWinApp_defproc(main_module.m_process, ptr_message); } }break; case MSG_SHUTUP: //application has received a shutup message { if(OnShutUp()) { cWinApp_defproc(main_module.m_process, ptr_message); } return(FALSE); }break; case MSG_USER: //application has received a user message { if(OnUser()) { cWinApp_defproc(main_module.m_process, ptr_message); } }break; default: //unknown message { cWinApp_defproc(main_module.m_process, ptr_message); }break; } } return(TRUE); } bool OnKeyDown(int scancode,int mask,char ch) { struct Message* ptr_msg; //if the escape key has been pressed, quit if(scancode==KEY_ESC) { //create a new message ptr_msg=Message_new(sizeof(struct Message)); //make it a quit message ptr_msg–>msgid=MSG_QUIT; //send this message to the message queue Message_post(ptr_msg,cWinApp_get_name(main_module.m_process),get_own_id()); //the message has been handled, so return true return(TRUE); } //if message hasn't been handled, return FALSE return(FALSE); } bool OnKeyUp(int scancode,int mask,char ch)
Team LRN
59
60
Chapter 6: Program Frameworks
{ //if message hasn't been handled, return FALSE return(FALSE); } bool OnCharTyped(int scancode,int mask,char ch) { //if message hasn't been handled, return FALSE return(FALSE); } bool OnTimer() { //if message hasn't been handled, return FALSE return(FALSE); } bool OnPowerDown() { //if message hasn't been handled, return FALSE return(FALSE); } bool OnQuit() { //if message hasn't been handled, return FALSE return(FALSE); } bool OnPaint() { //fill the screen with white DisplayGraphics_fill_screen(main_module.m_gfx,CLR_WHITE); //show the screen DisplayGraphics_show(main_module.m_gfx); //message has been handled return(TRUE); } bool OnFiles() { //if message hasn't been handled, return FALSE return(FALSE); } bool OnLostFocus() { //if message hasn't been handled, return FALSE
Team LRN
Chapter 6: Program Frameworks
61
return(FALSE); } bool OnGotFocus() { //do the same thing as OnPaint return(OnPaint()); } bool OnLaunch() { //if message hasn't been handled, return FALSE return(FALSE); } bool OnDevice() { //if message hasn't been handled, return FALSE return(FALSE); } bool OnPing() { //if message hasn't been handled, return FALSE return(FALSE); } bool OnShutUp() { //if message hasn't been handled, return FALSE return(FALSE); } bool OnUser() { //if message hasn't been handled, return FALSE return(FALSE); }
I know this code is quite lengthy, considering it does almost nothing. However, using this code as a base, you can easily write a function for handling whatever messages may come your way. For example, a MSG_KEYDOWN is processed in OnKeyDown, a MSG_GOTFOCUS is processed in OnGotFocus, and so on. Central to how the event-driven framework operates is the Message_pump function, which reads in messages and sends them to Message_handle, which returns TRUE if the application should continue running, and FALSE if not. Message_handle routes the message based on msgid. From there, it gets sent along to the individual message handler, which returns TRUE if the message was handled, and FALSE if it was not. If the message is not handled by the individual handler
Team LRN
62
Chapter 6: Program Frameworks
function, Message_handle will send the message along to cWinApp_defproc, which will do whatever default processing might be required for the message. Additional functions in this framework are Prog_Init and Prog_Done. Prog_Init currently does nothing but return TRUE. In an application based on this framework, Prog_Init would include any code that sets up global information used by the application. On the other end is Prog_Done, which cleans up any data used by the application after the Message_pump function exits. CyBk6_2 only handles the OnPaint, OnGotFocus, and OnKeyDown messages. The rest just return FALSE, which allows default processing of the messages. During OnPaint, the screen is filled by CLR_WHITE, then shown. During OnGotFocus, OnPaint is called. During OnKeyDown, it checks to see if the Escape key was pressed, and if it was, then it sends MSG_QUIT to the application, which will shut it down.
Idle-Loop Framework In the idle-loop framework (found in CyBk6_3), very little has changed from the event-driven framework. In fact, the changes are simply the addition of a function (Prog_Loop), changing Message_pump, and adding a #define. In the event-driven framework, the program would wait indefinitely for a message to occur, then process the message. In the idle-loop framework, we specify a timeout value at the top of the program, like in Listing 6.3. Listing 6.3 #define MESSAGE_TIMOUT 100
This is used in the new Message_pump function (see Listing 6.4). The only other change is the addition of a Prog_Loop function, which is a void function with no parameters. Listing 6.4 bool Message_pump(struct cWinApp* ptr_win_app) { bool quit=FALSE; struct Message* ptr_msg; //while there are still messages coming, handle them while(!quit) { //grab a message ptr_msg=cWinApp_get_message(ptr_win_app,MESSAGE_TIMEOUT,1,MSG_USER); if(ptr_msg) { //handle the message quit=!Message_handle(ptr_msg); //delete the message Message_delete(ptr_msg);
Team LRN
64
Chapter 6: Program Frameworks
} }
In this framework, the program waits MESSAGE_TIMEOUT clock ticks for a message, and if a message has been received, it is processed through Message_handle. Whether a message is received or not, Message_pump calls Prog_Loop, so that the game appears to be operating in “real time.” One of the problems with this framework is that you can get a considerable amount of latency with user input if you are responding to MSG_KEYDOWN and MSG_KEYUP for your input. For example, you might press and hold down the KEY_UP key, which will send a series of MSG_KEYDOWN messages, and after each message, Prog_Loop will execute. When you release the KEY_UP key, there are still a number of MSG_KEYDOWN messages left in the queue that haven’t been processed, and each of these messages will be processed in the order received, so even after you have released the key, the screen will still scroll up (or whatever your program does when processing KEY_UP). This is latency—input that isn’t processed immediately. Fortunately, there are ways around this. To avoid input latency, you simply make use of the DirectKeyboard functions, which I show in a later chapter.
Dialog-Based Framework In the dialog-based framework, you make use of the Cybiko GUI (graphical user interface) objects to build your application. This winds up looking almost nothing like any of the other frameworks shown before now. The Cybiko GUI objects are complex and difficult to use for a beginner, so I will cover the dialog-based framework in a later chapter.
Multi-threaded Framework In the multi-threaded framework, you have more than one thread of execution operating at the same time. This is like having several programs operating as though it were a single program. One thread might be keeping music playing, another thread might be calculating artificial intelligence, and yet another thread might be maintaining a list of opponents playing on the local network with you. As you might imagine, creating multi-threaded programs is quite complex (since you have to have otherwise independent processes working in sync with one another), and there is no simple framework that works for all multi-threaded needs. I’ll cover multi-threading in a later chapter.
Team LRN
64
Chapter 6: Program Frameworks
} }
In this framework, the program waits MESSAGE_TIMEOUT clock ticks for a message, and if a message has been received, it is processed through Message_handle. Whether a message is received or not, Message_pump calls Prog_Loop, so that the game appears to be operating in “real time.” One of the problems with this framework is that you can get a considerable amount of latency with user input if you are responding to MSG_KEYDOWN and MSG_KEYUP for your input. For example, you might press and hold down the KEY_UP key, which will send a series of MSG_KEYDOWN messages, and after each message, Prog_Loop will execute. When you release the KEY_UP key, there are still a number of MSG_KEYDOWN messages left in the queue that haven’t been processed, and each of these messages will be processed in the order received, so even after you have released the key, the screen will still scroll up (or whatever your program does when processing KEY_UP). This is latency—input that isn’t processed immediately. Fortunately, there are ways around this. To avoid input latency, you simply make use of the DirectKeyboard functions, which I show in a later chapter.
Dialog-Based Framework In the dialog-based framework, you make use of the Cybiko GUI (graphical user interface) objects to build your application. This winds up looking almost nothing like any of the other frameworks shown before now. The Cybiko GUI objects are complex and difficult to use for a beginner, so I will cover the dialog-based framework in a later chapter.
Multi-threaded Framework In the multi-threaded framework, you have more than one thread of execution operating at the same time. This is like having several programs operating as though it were a single program. One thread might be keeping music playing, another thread might be calculating artificial intelligence, and yet another thread might be maintaining a list of opponents playing on the local network with you. As you might imagine, creating multi-threaded programs is quite complex (since you have to have otherwise independent processes working in sync with one another), and there is no simple framework that works for all multi-threaded needs. I’ll cover multi-threading in a later chapter.
Team LRN
Chapter 6: Program Frameworks
65
Summary So, as you can see, there are a number of choices as far as what framework you use as the skeleton of your programs. In most of the future program examples for this book, the framework will be either event-driven, idle-loop, or real-time, based on the nature of the application. Once you get into the GUI part of Cybiko programming, I will present the dialogbased framework again. It’s not terribly hard, but it does look strange to a person who is new to it. By now, you’ve got enough basic knowledge to be able to put together simple Cybiko applications, which means it is time to build upon this knowledge and start making some real applications and games!
Team LRN
Chapter 7
Messages Overview In Chapter 6, we took a look at the various types of program frameworks that you might commonly use for your Cybiko applications and games. Throughout, you undoubtedly noticed the use of the Message struct. However, while dumping pages upon pages of code on you, I didn’t do a whole lot in the way of explaining what these messages are and how they are used. You probably picked up that they are used for input handling. That’s good. It means you’ve been paying attention. In this chapter, I will be exploring basic message handling, with details on what makes up a message, how to retrieve messages, how to handle messages, and how to define your own messages. We aren’t going to fully cover the topic. There are some advanced aspects to the Cybiko messaging system, including sending messages to other Cybikos, and attaching blocks of information (called buffers) to a message, which allows the sending of large amounts of data from one Cybiko to another.
Event-Driven Operating Systems For the Cybiko, there is one primary method of input: the keyboard. Things can happen when you press a key, release a key, or hold down a key. The act of pressing or releasing a key is the most concrete example of what is called an “event.” In the context of the Cybiko, an event is just “something that has happened.” Events are not limited to key presses and releases, of course, but those are the easiest to demonstrate, and thus, are the best examples to use to understand what events are. In order to understand an event-driven operating system, it is customary to talk about how a non-event-driven operating system works, and that means we talk about what happens, exactly, when a key is pressed or released. Every key on the keyboard is part of a circuit. If the circuit is closed, then electricity will flow. If the circuit is open, no electricity flows. This is sort of like a light
66
Team LRN
Chapter 7: Messages
67
switch. The key acts as the switch; pressing the key turns the light on, and releasing the key turns the light off again. The natural position of a key is off. We have to actually apply force in order to turn it on and to keep it on. In the absence of force applied to the key, it will return to the “off” state. There are a number of ways we might get input from such a system. One way would be to check periodically to see if a key is currently pressed, and if it is, then do something about it. The problem with this method is that we will miss input if we take too much time between checking the state of the key. If we were, say, loading a file between checks, the user might press and release a key. When we check again, the switch will be in the off position, so it appears to the computer as though nothing has happened. So, naturally, this method of processing the keyboard just won’t do for us. We need something more reliable. Another way to get input is to have an input device interrupt the computer operation (if only for a fraction of a second) when a state change occurs. This steals a small amount of time from us, but it is much more reliable, as we get all of the state changes recorded and stored for later viewing. However, this does steal away some time from other things, and information about state changes can pile up until there is no room for anything else. The manner in which the Cybiko receives input is similar to this method. The actual method of keyboard input is not documented, but we can assume that there is a “watcher” thread running in the background that monitors key events. So, when you press a key, the Cybiko registers it immediately and places information where we can get to it later. This happens almost without our knowing it. The same thing happens after a key has been held for a period of time. We get another “key is pressed” event. After a “key is pressed” event, we also get a “character has been typed” event. This allows us to easily deal with text input without relying on key codes, which are not the same as the letters and numbers written on those keys. When we release a key, we get a “key is released” message. When any event occurs, it is our responsibility to do something about it. Taking action in response to an event takes many forms. One common example is that pressing the Esc key causes the program to exit. Another might be when pressing an arrow key, we move a cursor around the screen. The possibilities are endless. We let the operating system handle messages that we don’t have to do anything about. This has two benefits. One, we don’t have to duplicate what the operating system handles in our own code. Two, it allows us to pick and choose the events we wish to handle. The basic formula is: if this happens, then do that, where “this” is an event of some sort, and “that” is an action we are taking in response to that event. However, we need a way to represent an event, and a consistent way of doing so for all events, not just keyboard events.
Team LRN
68
Chapter 7: Messages
The Message Class The Message class is the package into which information from an event is stored. It contains not only what kind of event occurred, but may also contain extended information about the event. For example, the extra information in a keyboard event is what key is being pressed, the state of the Shift and function keys, and so on. The Message structure is shown in Listing 7.1. Listing 7.1 struct Message { struct Message* next; char* dst_name; cyid_t cyid_from; cyid_t cyid_to; bool deleted; short msgid; long param [2]; };
The member called next has to do with linking messages together. Since the operating system has no idea how many messages it might be called upon to store at any particular time, it stores them in a linked list and uses pointers to move between them. This member is not terribly important to message handling itself, but more important to message storage. The dst_name member is a char*, so it’s a string. This string stores the name of the process for which this message is bound. For the most part, this is unimportant to message handling. It only becomes important when sending messages. When you are handling messages for your application, only those intended for your process will be read, so you really don’t have to worry about this member much. The cyid_from and cyid_to members deal with the source and destination of the message. Because the Cybiko uses radio communications, your application may get messages coming from other Cybikos in the area. Each Cybiko has its own unique ID number called the CyID. I will talk more about CyIDs in a later chapter. In most cases, the cyid_from and cyid_to members of the Message struct will have the same value. The deleted member has to do with message management on the operating system side. You won’t have to deal with it. The msgid and param members are the two primary means by which you will access message data. The msgid member stores what type of message you are dealing with, and the param array has the “extended” information needed for messages. When working with messages, these are the only members you will access most of the time.
Team LRN
Chapter 7: Messages
69
Types of Messages Now that we’ve established what a message is, we need to talk about the kinds of messages we might encounter. Listing 7.2 shows all of the messages for the Cybiko. Listing 7.2 MSG_KEYDOWN MSG_KEYUP MSG_CHARTYPED MSG_TIMER MSG_POWERDOWN MSG_QUIT MSG_PAINT MSG_FILES MSG_LOSTFOCUS MSG_GOTFOCUS MSG_LAUNCH MSG_DEVICE MSG_PING MSG_SHUTUP MSG_USER
If you were to look up API reference/communications/messaging/defines in the SDK docs, you’ll see all of these messages listed, and if you looked up a particular message, you would see the lack of documentation that accompanies them. You could then scour the example programs that came with the SDK, and notice that very few of these messages are used in examples. The only well-documented messages are MSG_KEYDOWN, MSG_KEYUP, MSG_QUIT, and MSG_SHUTUP. For the rest, your guess is as good as mine. MSG_USER is for defining new messages that only your application handles. I have learned a thing or two about MSG_GOTFOCUS, and how to use it to properly respond to the help key, but that’s really about it. The rest of the messages are mostly a mystery. I will be ignoring undocumented messages. Undocumented usually means “reserved,” and their implementation can change at any time.
Retrieving Messages The first part of message processing is checking for a message, naturally. This is done with either the cWinApp_get_message or the cWinApp_peek_message function. Most of the time, you will want to use cWinApp_get_message unless you really need cWinApp_peek_message. Listing 7.3 shows the prototype for cWinApp_get_message.
Team LRN
70
Chapter 7: Messages
Listing 7.3 struct Message * cWinApp_get_message ( struct cWinApp * ptr_win_app, long timeout, int min, int max);
This function takes four parameters and returns a pointer to a Message object. The first parameter, ptr_win_app, is a pointer to a cWinApp object, or main_module.m_ process in our examples. The second parameter is a timeout value. If zero, this function will wait forever or until an event happens. If it specifies a positive number, it will wait for that many 10 ms intervals to pass, and return 0 if no event occurred in that time. The min and max parameters specify the minimum and maximum value for the messages that should be checked for. You may only wish to look for particular messages. Most of the time, you won’t, so just put 1 for min and WM_USER (or the value of the highest user-defined message) into this parameter. The cWinApp_peek_message function, on the other hand, can be used in one of two ways. Its prototype is shown in Listing 7.4. Listing 7.4 struct Message * cWinApp_peek_message ( struct cWinApp * ptr_win_app, int min, int max );
bool remove,
It has mostly the same parameter list as cWinApp_get_message, and the same return type. The difference is in the second parameter. For cWinApp_peek_message, this is a bool. If this value is TRUE and a message is found, then the message will be removed from the list of messages waiting to be processed. If FALSE, it will leave that message in the waiting list. If there is no message waiting, this function will return 0. So, there are two ways to use this function: n
Call it to determine if there are any messages currently in the queue. Call this with a remove value of FALSE. If the returned value is non-zero, there are messages waiting. This is useful for when you are doing some long task, like loading in a file or something. You can periodically check for messages in the queue; if you receive some, run a small loop that does nothing more than empty out the message queue.
n
Call it to attempt to read messages immediately, then go on to do something else. Whether a message exists or not, it will immediately return. This is not suggested for most applications. If you spend a lot of time doing other things, and not enough time looking for messages, then the messages will pile up quickly and you’ll run out of memory in short order.
Handling Messages Once you’ve read a message, from whatever method, you then have to do something about it, or do nothing about it and let the default handler process it. You will almost never care about every single message that is being received by your program. You might care about whether the Enter key is pressed, but you might not care if the J key
Team LRN
Chapter 7: Messages
71
is pressed, so you would write code for the Enter key, but let the default message handler deal with the J key.
Keyboard Messages Keyboard messages are the best documented messages in the Cybiko SDK. This is due to their great importance in applications and games. Indeed, the keyboard is the only means through which a user can interact with an application. There are three keyboard messages: MSG_KEYDOWN, MSG_CHARTYPED, and MSG_KEYUP. Each of these messages are rather similar in the information that they contain. To best make use of these messages, you’ll need a little understanding of when and how they occur, and what information they contain. Also, you’ll need to learn about a new class, the KeyParam class.
MSG_KEYDOWN There are two situations that cause a MSG_KEYDOWN to fire. First, when a key is initially pressed, one of these messages is sent. Second, after the key has been held down for a certain period of time, another MSG_KEYDOWN is fired, and so you can make use of this “key repeat” feature for entering text. You will usually respond to MSG_KEYDOWN for your own customized controls. However, you should be aware of something. Look at the configuration of your Cybiko keyboard. If it’s like mine, it looks a great deal like the QWERTY keyboard that has become common. Or maybe it looks quite different. This is my point. Since Cybiko, Inc., is an international company, not all of their keyboards will look the same. However, there are some things they all have in common, most notably the arrow keypad, the Esc key, and the five keys to the side of the arrow pad that don’t usually correspond to a particular text character (technically, Enter does, but that’s an exception). If you are using MSG_KEYDOWN to respond to the “1” key, you should probably rethink that. On one keyboard, the “1” key might be its own key. On another keyboard, to type a “1” might require a Shift+key or Fn+key combination, so your response to the “1” key won’t work on that machine. So for the “big buttons” you can feel free to use MSG_KEYDOWN without much danger of not being compatible. For the “little buttons” you are probably better off using MSG_CHARTYPED.
MSG_CHARTYPED After every MSG_KEYDOWN event, a MSG_CHARTYPED event occurs in response to it. The machine uses a keyboard driver to map the key that has been pressed and the current state of the Shift and Fn keys to generate a character. For example, Shift+= will generate a plus sign (+), at least on my keyboard.
Team LRN
72
Chapter 7: Messages
The reason for a separate message like MSG_CHARTYPED is to help counteract what I warned about in the discussion of MSG_KEYDOWN. It helps to achieve hardware independence. All languages of the world have adopted the Arabic system of representing numbers (using the digits 0 through 9), so nearly all keyboards will have some way of generating these characters. Also, most European alphabets use the Roman alphabet or some variation of it (notable exceptions—Greece and Eastern European nations that use the Cyrillic alphabet). Also, foreign keyboards don’t necessarily use the same punctuation that we do. The main idea I want to drive home here is that you cannot assume that, if you are an American or Canadian, that the other users of your application will necessarily be Americans or Canadians. The same thing if you live in Europe. The Cybiko is localized, and has a keyboard appropriate for your area. Even small items like replacing the “#” with the “£” is common for British keyboards. Also, keep in mind your primary method of distribution for your applications: the Internet. The world has gotten very small lately. I commonly talk to people all over the world about programming, and let me tell you, reading code that has been commented in Portuguese is an interesting experience. So, use the “big buttons” when at all possible, and leave the rest of the keyboard for text input only. Sometimes this is not possible, but do try. It makes it easier to distribute your application to the world.
MSG_KEYUP Finally, MSG_KEYUP occurs when a key has been released. Only one MSG_KEYUP will occur. So, even if you’ve had 50 MSG_KEYDOWN and MSG_CHARTYPED events occur, you will get one final MSG_KEYUP for that key. You will rarely, if ever, respond to the MSG_KEYUP event.
The KeyParam Struct We talked a little earlier about the extended information stored within a Message object, in the Param array. This is true of keyboard messages as well, but there is a special utility struct for accessing this information for keyboard events only. Listing 7.5 shows the structure of the KeyParam struct. Listing 7.5 struct KeyParam { int scancode; int mask; char ch; };
The scancode member contains one of the key identifiers. These all begin with KEY_, and there’s a nice long list of them in the SDK docs. Most of them correspond loosely
Team LRN
Chapter 7: Messages
73
to ASCII codes for the characters that the keys represent, but this is not a hard and fast rule. For the “big buttons,” the KEY_* identifiers are >256 (0x100). The mask member contains zero or more bit flags. These are part of an enumeration called keymask_t. There are three different bit flags: KEYMASK_SHIFT, KEYMASK_CONTROL, and KEYMASK_AUTOREPEAT. KEYMASK_SHIFT will be set if the Shift key was being held down during a MSG_KEYDOWN event, and the bit will also be set in the corresponding MSG_CHARTYPED event. KEYMASK_CONTROL is a similar idea, but is instead in response to the Fn key. KEYMASK_AUTOREPEAT is sent with MSG_KEYDOWN and MSG_CHARTYPED events, after the initial pressing of the key. This is how you can filter out the repeated key messages. The ch member is the ASCII character code corresponding to the event. It is mapped based on the character being pressed, the Shift key, and the Fn key. So, how do we grab this information out of a Message object? Quite simply, we use the Message_get_key_param function, shown in Listing 7.6. Listing 7.6 struct KeyParam* Message_get_key_param(struct Message* ptr_message);
This function takes a single parameter, a pointer to a Message object, and returns a KeyParam pointer. From here, you can check the members of the KeyParam struct as shown in Listing 7.7 Listing 7.7 //check for KEY_ESC if(Message_get_key_param(msg)–>scancode==KEY_ESC) { //do something in response to KEY_ESC } //check for status of Shift in key mask if(Message_get_key_param(msg)–>mask&KEYMASK_SHIFT) { //do something in response to shift mask } //check for a generated '0' character if(Message_get_key_param(msg)–>ch=='0') { //do something in response to a generated '0' }
Hopefully, you get the idea of how to handle keyboard messages now.
Focus Messages The focus messages (MSG_GOTFOCUS and MSG_LOSTFOCUS) are also quite important, but not nearly as important as the keyboard messages. Since CyOS is a multitasking operating system, there may be times in which your application will have
Team LRN
74
Chapter 7: Messages
to surrender control to another. Primarily, this occurs when you have called up the Cybiko help system by pressing the help key. If your application continues to draw to the screen while the help system is active, you will get garbled results. Therefore, when the help system comes up, and your application receives a MSG_LOSTFOCUS, your application should “go to sleep.” The easiest way to implement this is with a global Boolean variable that you set to TRUE. When the user exits the help system, your application will receive a MSG_GOTFOCUS, and you can set that variable back to FALSE, redraw the screen, and carry on with whatever the application was doing.
Quit and Shut Up No, I didn’t mean you... Hey! Come back! In the examples with the SDK, the MSG_QUIT and MSG_SHUTUP are given roughly equal strength. When one is received, the program should shut down as quickly as possible. MSG_QUIT generally comes as part of user input. MSG_SHUTUP, I believe, comes from other sources, although I don’t know what. However, to be safe, we should obey the conventions set before us, and treat MSG_SHUTUP and MSG_QUIT the same, i.e., get out of the program, ASAP. The manner in which you do this depends on the structure of your application. If you work on programs based on the frameworks in this book, you don’t really have to do anything, because the framework handles MSG_QUIT and MSG_SHUTUP by exiting the app. If you start making your own frameworks, then your response to these messages might be as simple as a call to the exit function, or it might set some global Boolean variable to TRUE. There are a hundred different ways to do it.
Sending Messages There are a couple of different ways to send messages, and plenty of reasons you might want to send them. The primary method is to use the send_msg function, shown in Listing 7.8. Listing 7.8 bool send_msg(char* app_name, int msgid, long d0, long d1, void* data, size_t size);
This is a pretty cool and flexible function. It will set up and send a message for you, and you just supply the important parameters. The app_name parameter corresponds to the dst_name member of the Message struct. The msgid parameter is used for the msgid member of the Message struct. The d0 and d1 correspond to param[0] and param[1]. The data and size parameters are for buffers. The data parameter is a pointer to some memory containing data you want
Team LRN
Chapter 7: Messages
75
to send with the message, and size is how many bytes are in that memory block. I talk more about buffers in a later chapter. When using send_msg, the rest of the members of the Message struct are taken care of for you. This is a nice, simple way to send messages to your own app. The most commonly sent message will be MSG_QUIT. Listing 7.9 shows an example of how you might send it. Listing 7.9 send_msg(cWinApp_get_name(main_module.m_process),MSG_QUIT,0,0,0,0);//send a quit message
Don’t let the cWinApp_get_name function call throw you. This is just a function that, given a pointer to a cWinApp (i.e., main_module.m_process), will spit out the name of that application. That way, you don’t have to worry about what the application’s name is, and you will save time in coding.
User-Defined Messages The predefined messages for the Cybiko will serve you in good stead. However, at some point, you will probably want to define your own messages. For this, Cybiko has given us MSG_USER. It has a value of 0x2000. Below this value, the msgids are reserved for use by Cybiko, Inc. Above this number, we are free to define them for ourselves as we wish. Creating new messages is no big deal. You can simply make a number of defines, like the one in Listing 7.10. Listing 7.10 #define MSG_MYMSG (MSG_USER) #define MSG_YOURMSG (MSG_USER+1)
You can start with the value of MSG_USER, and go from there. Once you start doing this, however, you will probably want to have an extra #define in there, called MSG_LAST, so that you can use this value in your calls to cWinApp_get_message and only have to update the #define when new message IDs are created. The meaning of the values in param[0] and param[1] for your new messages are entirely up to you. A lot of messages don’t need any sort of extra parameters, such as MSG_QUIT. Others do, like the keyboard messages.
Summary In this chapter, we have explored the basics of Cybiko message handling. This is perhaps the most fundamental aspect of Cybiko programming. Without it, there is hardly a program at all. (This is not to say that those utilities that don’t process messages aren’t programming, but they are not interactive in any sort of meaningful way.)
Team LRN
76
Chapter 7: Messages
This ends the topic of basic message handling. Naturally, there’s a lot more to it than what I have presented here. Indeed, we will have more to cover later on. But you should now have the gist of it, at least, and be able to work with messages.
Team LRN
Chapter 8
Basic Graphics Overview Now that you have the fundamentals of Cybiko programming down, it is time for something a little more fun and productive, namely, graphics. By far, the most noticeable part of any Cybiko application is what is shown on the screen. The screen is the primary method by which a program gets information to the user. Whether those graphics are in the form of text, icons, lines, pixels, or rectangles is immaterial. As long as the program is successful in giving the user feedback on what he is doing (whether he is writing an e-mail or playing a game), then graphics have served their purpose. This chapter serves as an introduction into Cybiko graphics. Mainly, we will be doing simple stuff like clearing the screen and drawing simple graphical primitives like pixels, lines, and rectangles. In the next chapter, we get to bitmaps and fonts.
TGraph, Graphics, and DisplayGraphics The Cybiko graphics system, like everything else, is an object-oriented, class-based system, like the ones we discussed in Chapter 5. There are three classes for dealing with graphics: TGraph, Graphics, and DisplayGraphics. These are shown in Table 8.1, along with a brief description of what these classes encompass. Table 8.1 Cybiko graphics classes Class
Purpose
TGraph
Base implementation of raster/2d graphics (abstract class).
Graphics
Implementation for drawing on Bitmap objects.
DisplayGraphics
Implementation for drawing on the main display.
77
Team LRN
78
Chapter 8: Basic Graphics
As noted in the table, TGraph is an abstract class, which means it isn’t actually useful. You will never have a variable of type TGraph. Its purpose is to provide base functionality for its derived classes, Graphics and DisplayGraphics. The Graphics class is the first non-abstract, or “concrete,” class in the graphics hierarchy. We won’t be using it in this chapter, but we will later on when we get into drawing bitmaps. The DisplayGraphics class is the main class we are concerned with in this chapter, since it allows us to write onto the Cybiko’s display. It is derived from Graphics, and contains all of the functionality from both TGraph and Graphics.
Creating a DisplayGraphics Object You don’t really “create” a DisplayGraphics object. One always exists, and it is shared by all applications running on the Cybiko. Because of this, there is no DisplayGraphics_ctor or DisplayGraphics_dtor. Instead, you just grab it from the module you initialize using init_module. Since we are using the variable main_module, we can access the DisplayGraphics object through main_module.m_gfx. This is a pointer to the DisplayGraphics object. Another way we might grab the DisplayGraphics pointer is to call cWinApp_ get_display. This function takes no parameters and returns a pointer to the DisplayGraphics object. This would really only be useful in a program that was run from the console that, for example, saved a screen shot of whatever application is running.
Clearing the Screen Now that you’ve got the DisplayGraphics object’s pointer, you can begin to do things with it. The simplest operation you’d want to do is to clear the screen. Before we get to that, however, I need to tell you just a little bit about Cybiko colors.
color_t Within the include file cyber-types.h, there are a number of type aliases (typedefs). One of these is the color_t typedef, shown in Listing 8.1. Listing 8.1 typedef int color_t
This isn’t particularly significant in itself. I just wanted to show you that in all of the functions that take a color_t as a parameter, you can use an int just as easily. There are lots of functions that take color_t parameters, and I wanted to save you the hassle of thinking to yourself, what the heck is a color_t?
Team LRN
Chapter 8: Basic Graphics
79
In the include file cyber-graph.h, there are four #defines, one for each of the colors that can be shown on the Cybiko. These #defines are shown in Listing 8.2. Listing 8.2 #define #define #define #define
CLR_WHITE CLR_LTGRAY CLR_DKGRAY CLR_BLACK
0 85 171 255
There are a few things to note here. First, Cybiko colors increase from white to black, which is opposite of how graphics are done on most other platforms. Second, these values are not sequential, and using values between these cause unpredictable results. As for the reason that these values are opposite of other platforms, consider how an LCD display works versus how a cathode ray tube (a monitor) works. On an LCD, electricity is used to darken a given pixel, which is “white” when no energy is supplied. On a monitor, energy is taken to lighten a pixel, which is black without any energy. As for why the values are non-sequential, I’m not sure. I theorize that at some point in the future, there will be more colors on the Cybiko, and because of the need to be backward compatible with older software, these values were chosen.
DisplayGraphics_fill_screen Once you’ve got a handle on color_t, filling the screen is a piece of cake. The function for this is DisplayGraphics_fill_screen, and the syntax for it is shown in Listing 8.3. Listing 8.3 void DisplayGraphics_fill_screen ( struct DisplayGraphics * ptr_gfx, color_t fc );
This function takes two parameters and returns no value. The first parameter is the pointer to your DisplayGraphics object (i.e., main_module.m_gfx), and the second parameter is a color_t value, i.e., CLR_WHITE.
DisplayGraphics_show After you have performed all of the operations that you wish to on the DisplayGraphics object, you must then send it to the actual display, using DisplayGraphics_show. The syntax for this function is shown in Listing 8.4. Listing 8.4 void DisplayGraphics_show ( struct DisplayGraphics * );
ptr_gfx
Team LRN
80
Chapter 8: Basic Graphics
This function takes a single parameter (main_module.m_gfx), and returns no value. This function updates the display. The reason you have to call this function is so that the user of your application doesn’t have to see the graphics actually being drawn on the screen, which means that any animations shown will be smooth.
Screen Filling Example Example CyBk8_1 is the screen filling example. It can be found on the companion CD in the Examples folder. This example is built using the event-driven framework, so you can refer back to CyBk6_2 for details on how that framework operates. Only two of the many functions from the event-driven framework need to be modified to create CyBk8_1: OnGotFocus (shown in Listing 8.5.1) and OnKeyDown(shown in Listing 8.5.2). Listing 8.5.1 bool OnGotFocus() { //clear the screen white DisplayGraphics_fill_screen(main_module.m_gfx,CLR_WHITE); //show the screen DisplayGraphics_show(main_module.m_gfx); //handled, return true return(TRUE); }
OnGotFocus simply clears the screen to white, so that there are no remnants of the Intro.pic or Root.spl files left on the display. (I consider leaving artifacts unprofessional.) Listing 8.5.2 bool OnKeyDown(int scancode,int mask,char ch) { struct Message* ptr_msg; switch(scancode) { case KEY_ESC://escape key { //create a new message ptr_msg=Message_new(sizeof(struct Message)); //make it a quit message ptr_msg–>msgid=MSG_QUIT; //send this message to the message queue Message_post(ptr_msg,cWinApp_get_name(main_module.m_process),get_own_id() );
Team LRN
Chapter 8: Basic Graphics
//the message has been handled, so return true return(TRUE); }break; case KEY_1: { //fill the screen DisplayGraphics_fill_screen(main_module.m_gfx,CLR_WHITE); //show the screen DisplayGraphics_show(main_module.m_gfx); //the message has been handled, so return true return(TRUE); }break; case KEY_2: { //fill the screen DisplayGraphics_fill_screen(main_module.m_gfx,CLR_LTGRAY); //show the screen DisplayGraphics_show(main_module.m_gfx); //the message has been handled, so return true return(TRUE); }break; case KEY_3: { //fill the screen DisplayGraphics_fill_screen(main_module.m_gfx,CLR_DKGRAY); //show the screen DisplayGraphics_show(main_module.m_gfx); //the message has been handled, so return true return(TRUE); }break; case KEY_4: { //fill the screen DisplayGraphics_fill_screen(main_module.m_gfx,CLR_BLACK); //show the screen DisplayGraphics_show(main_module.m_gfx); //the message has been handled, so return true return(TRUE); }break; }
Team LRN
81
82
Chapter 8: Basic Graphics
//if message hasn't been handled, return FALSE return(FALSE); }
In the OnKeyDown function, depending on the value of scancode, different things occur. If the scancode is KEY_ESC, MSG_QUIT is posted to the queue. If the number 1, 2, 3, or 4 is pressed, then the screen is cleared to the appropriate color, and the screen is updated with DisplayGraphics_show. This application may be extremely simple, but it demonstrates one of the uses of the event-driven framework. The application responds to user input by giving visual feedback. Also, it took very little code to change the framework into an actual example that does something.
Plotting Pixels Pixel is short for picture element. At least, that’s what all of the books on graphics say. I’ve never been sure how the “x” got in there, but so it goes. It is the smallest unit of graphics, making it the equivalent of the atom as far as graphics are concerned. The basic idea here is that if you can plot a pixel, you can do anything graphically. This is true, sort of. In more advanced graphical operations, doing everything by plotting the individual pixels would be too inefficient. Often you will plot multiple pixels at the same time, but that doesn’t change the fact that you can do anything if you can plot a pixel. The pixel plotting function is called DisplayGraphics_set_pixel, and the syntax for it is shown in Listing 8.6. Listing 8.6 void DisplayGraphics_set_pixel ( struct DisplayGraphics * ptr_gfx, int fx, int fy, color_t fc );
This function takes four parameters and returns no value. The first parameter is the pointer to the DisplayGraphics object. The next two parameters are the x and y location for the pixel. The last parameter is the color.
Pixel Plotting Example For the pixel plotting demo, I started with an idle-loop framework (see example CyBk6_3). I chose this framework because, in the absence of user input, I wanted random pixels to be displayed on the screen. The pixel plotting demo can be found on the CD in CyBk8_2. The only function I had to change for this example was Prog_Loop, which is shown in Listing 8.7.
Team LRN
Chapter 8: Basic Graphics
83
Listing 8.7 void Prog_Loop() { int x; int y; color_t col; //random x position x=(int)random(160); //random y position y=(int)random(100); //random color col=(color_t)random(4); //convert to cybiko color switch(col) { case 0: col=CLR_WHITE; break; case 1: col=CLR_LTGRAY; break; case 2: col=CLR_DKGRAY; break; case 3: col=CLR_BLACK; break; } //plot the pixel DisplayGraphics_set_pixel(main_module.m_gfx,x,y,col); //show the screen DisplayGraphics_show(main_module.m_gfx); }
Figure 8.1 shows the output of this application, as seen on the Cybiko. I placed a black border around the image, so that it would stand out against this white page.
Team LRN
84
Chapter 8: Basic Graphics
Figure 8.1: Output of CyBk8_2
The code in Prog_Loop is pretty simple. A random (x,y) value is chosen, and then a random color 0 through 3, which is converted into the Cybiko equivalent color by the switch. The pixel is then written and the screen is shown. One function that I used in Prog_Loop that I hadn’t covered prior to now is the random function. In other versions of C and C++, the function for generating “random” numbers is called rand, and it takes no parameters. On the Cybiko, the function is called random, and it takes a single parameter, the number that is the highest value you want from the function plus one. The lowest random number is zero. So, if you wanted a random value from 0 to 9, you would call random(10).
Retrieving Pixels On the flip side of DisplayGraphics_set_pixel is DisplayGraphics_get_pixel, which retrieves the value of a pixel on the screen. The syntax for this function is shown in Listing 8.8. Listing 8.8 color_t DisplayGraphics_get_pixel( struct DisplayGraphics * ptr_gfx, int fx, int fy );
This function takes three parameters, and returns a color_t. The first parameter is the pointer to the DisplayGraphics object. The second and third parameter are the x and y coordinate of the pixel that you wish to retrieve. I don’t have an example program for DisplayGraphics_get_pixel, but I wanted to include it around the same time as DisplayGraphics_set_pixel, so that you have it at your disposal should you need it.
Team LRN
Chapter 8: Basic Graphics
85
Drawing Lines One step up from plotting pixels is line drawing. A line is an approximation of the inifinitely thin mathematical line between two (x,y) coordinates. Lines can be used together to create more complicated shapes, like triangles and rectangles, and can even be used to approximate circles. Because of the way that a graphical display is laid out, some types of lines are easier to draw than others, easier meaning faster and with fewer calculations. The easiest lines are horizontal and vertical, since in a horizontal line, the y value remains constant and the x value changes, and in a vertical line, the x remains constant while the y changes. In other lines, the x and y values both change, so there are more calculations that the line drawing function has to do to make it look right. Because of this, there are two special case functions for horizontal and vertical lines for the Cybiko, DisplayGraphics_draw_hline and DisplayGraphics_draw_vline.
Setting the Current Color Before we draw any lines, we first must inform Cybiko of which color we intend to use for line drawing. This is done with DisplayGraphics_set_color. The syntax is shown in Listing 8.9. Listing 8.9 void DisplayGraphics_set_color( struct DisplayGraphics * ptr_gfx, color_t fc );
This function takes two parameters and returns no value. The first parameter is the pointer to the DisplayGraphics object, and the second is a color_t. So, if you wanted to write black lines, you would use CLR_BLACK as the second parameter.
Horizontal Lines As stated earlier, horizontal lines are much easier to draw than diagonals, since you can leave y constant and make an x loop that sets the pixels. For this reason, Cybiko has a special function called DisplayGraphics_draw_hline for drawing horizontal lines. The syntax for this function is shown in Listing 8.10. Listing 8.10 void DisplayGraphics_draw_hline( struct DisplayGraphics * ptr_gfx, int x, int y, int xx );
Team LRN
86
Chapter 8: Basic Graphics
This function takes four parameters and returns no value. The first parameter is the pointer to the DisplayGraphics object, and the next two parameters are x and y values indicating where to start the horizontal line. The last parameter is the x coordinate of the other end of the line. This function draws a horizontal line between (x,y) and (xx,y).
Vertical Lines Similar to horizontal lines, vertical lines are easy to draw, since you can just loop the y value while keeping the x value constant. The vertical line function is called DisplayGraphics_draw_vline, and the syntax is shown in Listing 8.11. Listing 8.11 void DisplayGraphics_draw_vline( struct DisplayGraphics * ptr_gfx, int x, int y, int yy );
Like DisplayGraphics_draw_hline, this function takes four parameters and returns no value. The only difference between this function and the horizontal line function (other than the name of the function, of course) is the meaning of the last parameter, which in this function is the y coordinate of the other end of the line. This function draws a vertical line between (x,y) and (x,yy).
Free-Form Lines The final line drawing function applies to all lines that are neither horizontal nor vertical. It is called DisplayGraphics_draw_line. The syntax for this function is shown in Listing 8.12. Listing 8.12 void DisplayGraphics_draw_line( struct DisplayGraphics * ptr_gfx, int x, int y, int xx, int yy );
This function takes five parameters and returns no value. The first parameter is the pointer to the DisplayGraphics object. The next two parameters are the values of the starting (x,y) coordinate. The last two parameters are the values of the ending (x,y) coordinate. This function draws a line from (x,y) to (xx,yy).
Team LRN
Chapter 8: Basic Graphics
87
Random Lines Example Now that you’ve got a handle on lines, it is time for another example. CyBk8_3 on the companion CD contains the code for the line drawing demo. Like the pixel plotting demo, this example is built using the idle-loop framework, so the only function that has changed is Prog_Loop, which is shown in Listing 8.13. Listing 8.13 void Prog_Loop() { int x; int y; int xx; int yy; color_t col; //starting position //random x position x=(int)random(160); //random y position y=(int)random(100); //ending position //random x position xx=(int)random(160); //random y position yy=(int)random(100); //random color col=(color_t)random(4); //convert to cybiko color switch(col) { case 0: col=CLR_WHITE; break; case 1: col=CLR_LTGRAY; break; case 2: col=CLR_DKGRAY; break; case 3: col=CLR_BLACK; break; }
Team LRN
88
Chapter 8: Basic Graphics
//set the color DisplayGraphics_set_color(main_module.m_gfx,col); //draw the line DisplayGraphics_draw_line(main_module.m_gfx,x,y,xx,yy); //show the screen DisplayGraphics_show(main_module.m_gfx); }
If you’re thinking this looks a lot like the pixel plotting demo, you’re right. I took the code for CyBk8_2 and just adapted it into CyBk8_3. The only real differences are the addition of another (x,y) coordinate pair and which functions are called to actually draw the line. Figure 8.2 shows the output of CyBk8_3. Figure 8.2: Output of CyBk8_3
That’s all there is to line drawing. It’s not really that much more difficult than pixel plotting. Most of the time, line drawing is unimportant (games and applications tend to be bitmap based rather than line based), but it is still a good introduction into drawing primitives.
Rectangles The last primitive type for the chapter is the rectangle. Rectangles on the Cybiko come in two flavors, framed and filled. A framed rectangle just draws the border (i.e., the horizontal and vertical lines that make up the outside of the rectangle. A filled rectangle fills in all of the pixels inside as well. Like lines, you need to call DisplayGraphics_set_color before drawing a rectangle of a certain color.
Framed Rectangles A framed rectangle just draws the border pixels of a rectangle. The function for doing this is DisplayGraphics_draw_rect, and the syntax is shown in Listing 8.14.
Team LRN
Chapter 8: Basic Graphics
89
Listing 8.14 void DisplayGraphics_draw_rect( struct DisplayGraphics * ptr_gfx, int fx, int fy, int fw, int fh );
This function takes five parameters and returns no value. The first parameter is the pointer to the DisplayGraphics object. The next two parameters are the values of the (x,y) coordinate for the upper-left corner of the rectangle. The last two parameters are the width and height of the rectangle (not the position of the lower-right corner, as you might expect). Many times when drawing a rectangle, you know the lower-right corner, but you don’t know what the width and height are, and you don’t have a piece of scrap paper or a calculator handy. For example, you might want to draw a rectangle from (40,40) to (80,80). There is a nice simple formula for calculating the width and height shown in Listing 8.15. Listing 8.15 //left and top are the left and top values //right and bottom are the right and bottom values //width and height are self explanatory. //calc width and height width=right+1–left; height=bottom+1–top; //draw rectangle DisplayGraphics_draw_rect(main_module.m_gfx,left,top,width,height);
Filled Rectangles The function used for filling rectangles is called DisplayGraphics_fill_rect. The syntax is shown in Listing 8.16. Listing 8.16 void DisplayGraphics_fill_rect( struct DisplayGraphics * ptr_gfx, int fx, int fy, int fw, int fh );
The parameter list is exactly the same as DisplayGraphics_draw_rect, so I won’t repeat it here. The same set of calculations for width and height can be used here as well as for drawing a framed rectangle.
Team LRN
90
Chapter 8: Basic Graphics
rect_t Besides the functions DisplayGraphics_draw_rect and DisplayGraphics_fill_rect, there are two other functions that you can use to draw rectangles. These are the _Ex functions DisplayGraphics_draw_rect_Ex and DisplayGraphics_fill_rect_Ex. The syntax for both of these functions is shown in Listing 8.17. Listing 8.17 void DisplayGraphics_draw_rect_Ex( struct DisplayGraphics * ptr_gfx, struct rect_t * ptr_rectangle ); void DisplayGraphics_fill_rect_Ex( struct DisplayGraphics * ptr_gfx, struct rect_t * ptr_rectangle );
Both of these functions return no values and have two parameters. The first parameter is the pointer to the DisplayGraphics object, and the second parameters is a pointer to a rect_t. A rect_t is nothing more than a simple struct. It is shown in Listing 8.18. Listing 8.18 struct rect_t { short x short y short w short h };
As you might imagine, rect_t is just a shorthand way of describing a rectangle. If you had many rectangles that you needed to draw, you might be better off with a rect_t array to keep track of them. The four data members of rect_t are the x, y pair for the upper-left corner, and the width (w) and height (h) of the rectangle.
The rect_t Functions The rect_t struct can come in quite handy, and Cybiko C has a few functions to help you make the best use of them. There are only three: rect_set, rect_and, and rect_or.
rect_set This function is really great for setting up the dimensions of a rectangle without having to do it manually on four different lines. For example, to manually set up a rect_t to encompass the entire screen, you would have to use the code in Listing 8.19.
Team LRN
Chapter 8: Basic Graphics
91
Listing 8.19 rect_t rcScreen; rcScreen.x=0; rcScreen.y=0; rcScreen.w=160; rcScreen.h=100;
But if you use rect_set, all of this can be done on a single line, and it makes the intent of your code much easier to see. An example of rect_set is shown in Listing 8.20. Listing 8.20 rect_t rcScreen; rect_set(&rcScreen,0,0,160,100);
The parameters for rect_set should be pretty obvious: a pointer to a rect_t followed by x, y, w, and h. The function returns no value. Using rect_set can save you quite a bit of typing.
rect_and This function is good for collision detection. If you had two objects in a game and were keeping track of them using rect_t’s, you can use rect_and to determine not only if those rectangles overlap, but by how much they overlap. The syntax for rect_and is shown in Listing 8.21. Listing 8.21 bool rect_and( struct rect_t * ptr_rectangle, struct rect_t * ptr_rectangle_1, struct rect_t * ptr_rectangle_2 );
This function returns TRUE if an intersection exists, and FALSE if no intersection exists. The first parameter is a pointer to a rect_t in which the intersection will be stored. The second and third parameters are the two rect_t’s that you are testing for intersection.
rect_or The rect_or function figures out the smallest rectangle into which two source rectangles can be joined. This function has its uses, especially for creating update regions. The syntax is shown in Listing 8.22. Listing 8.22 bool rect_or( struct rect_t * ptr_rectangle, struct rect_t * ptr_rectangle_1, struct rect_t * ptr_rectangle_2 );
Team LRN
92
Chapter 8: Basic Graphics
Similar to rect_and, this function returns TRUE if the union exists, and FALSE if it does not. The first parameter is filled in with details of the resulting union, and the other two parameters are the source rect_t’s.
Rectangle Drawing Example Naturally, a discussion of rectangles would not be complete without a rectangle drawing demo. This example can be found in CyBk8_4 on the companion CD. This example was created with the idle-loop framework, and has the same code base as CyBk8_2 and CyBk8_3. The only function that changed was Prog_Loop, which is shown in Listing 8.23. Listing 8.23 void Prog_Loop() { int x; int y; int w; int h; color_t col; //starting position //random x position x=(int)random(160); //random y position y=(int)random(100); //width/height //random width w=(int)random(160–x); //random height h=(int)random(100–y);
//159+1–x //99+1–y
//random color col=(color_t)random(4); //convert to cybiko color switch(col) { case 0: col=CLR_WHITE; break; case 1: col=CLR_LTGRAY; break; case 2: col=CLR_DKGRAY; break; case 3:
Team LRN
Chapter 8: Basic Graphics
93
col=CLR_BLACK; break; } //set the color DisplayGraphics_set_color(main_module.m_gfx,col); //draw the line DisplayGraphics_draw_rect(main_module.m_gfx,x,y,w,h); //show the screen DisplayGraphics_show(main_module.m_gfx); }
By now, this code should be pretty easy to follow. It simply picks a random x and y coordinate, and based on them, it picks a random width and height for the rectangle. The color selection function is the same as in the line drawing demo. Figure 8.3 shows a screen shot of CyBk8_4 in action. Figure 8.3: Output of CyBk8_4
If you are interested in seeing a filled rectangle demo, you can replace the draw function with a fill function, and remake the project. Similarly, you could adapt the program to use rect_t and the draw_rect_Ex function, rather than the normal draw_rect function.
Summary That’s all there is to drawing simple primitives on the screen of the Cybiko. You should now have a good grasp of the DisplayGraphics object, and how to use it to draw pixels, lines, and rectangles. Admittedly, the examples in this chapter aren’t very fancy, but do play around with them a bit. Make a demo that does all three types of primitives. Play around with it, and gain some Verstehen. Next up, we’re moving on to bitmaps and fonts, or at least the rudiments of them.
Team LRN
Chapter 9
Bitmap and Font Basics Overview Pixels, lines, and rectangles are swell and all that, but they aren’t well suited to communicate with the user, at least, not all by themselves. In the modern age, users are accustomed to pictograms and text. Not too long ago, communication with the user was performed strictly through text interfaces, and still is on some systems. However, with the popularization of icon-based user interfaces, text has become less important, although you will still use it quite a bit. Whether you are using text or graphical icons, you are simply using a pattern of pixels which approximate something, be it the letter “F” or a picture of a pencil. These are all glyphs—pixellated approximations of real-world symbols or objects that (in theory) have meaning to you. On the Cybiko, all of these are bitmap objects. Even letters are bitmap objects (or rather, a series of bitmap objects representing letters).
Bitmap Objects If you’ve worked with bitmaps before on other platforms, feel free to skip down to “Creating a Bitmap Resource.” This is the bit where I explain what bitmaps are. A bitmap is an array of bit data that represents pixel data. It is simply a block of memory that is treated as though it were a two-dimensional array of pixel colors. On the Cybiko, there are two types of bitmaps, monochrome and four-color (xpic is a totally different story). A monochrome bitmap has one bit for every pixel, and a four-color bitmap has two bits for every pixel. The number of bits required for each pixel is called the bpp (bits per pixel) or, the color depth. In this text, I will use bpp. When I refer to a 1 bpp image, I will call it a monochrome bitmap or a mono bitmap. When I speak of a 2 bpp image, I will just use the word “bitmap.” In other words, you can assume I’m talking about the four-color bitmap, unless I specify otherwise.
94
Team LRN
Chapter 9: Bitmap and Font Basics
95
You’ve already been working with bitmaps, in the form of Root.ico and Intro.pic. However, you haven’t been loading and manipulating these yourself, and that’s what you’re here to learn.
Creating a Bitmap Resource In order to use a bitmap on the Cybiko, you must first have it on the Cybiko. This should go without saying. In order to get it onto your Cybiko, usually in the company of a game or application that uses that bitmap, you must make it into a resource of that application. Most of the time, I just make normal Windows .bmp files and use 2pic to convert them to the Cybiko format. From there, I put the name of the resource into Filer.list, and from there, it gets compressed and stored in my application archive. All of this stuff is covered back in Chapter 4. The code for loading in a bitmap resource is pretty simple. First, you must have a Bitmap object variable in your program, as in Listing 9.1. Listing 9.1 struct Bitmap bmp1;
The Bitmap struct is just another class we use to abstract objects. The actual struct is mostly hidden to us, and more importantly, we do not and should not care that much about the actual way that the Bitmap class is implemented. The Bitmap class has four constructors, named Bitmap_ctor, Bitmap_ctor_ Ex1, Bitmap_ctor_Ex2, and Bitmap_ctor_Ex3. The names aren’t particularly helpful in telling you which individual constructor you should use for what purpose, but it is a good example of the interface standard for constructors.
Bitmap_ctor This is the simplest constructor. The syntax is shown in Listing 9.2. Listing 9.2 struct Bitmap * Bitmap_ctor( struct Bitmap * ptr_bitmap );
This function takes a pointer to a Bitmap object that has already been allocated and returns the same pointer. This function creates an empty bitmap. Why in the world you would use it is beyond me. I provide it here for completeness only.
Bitmap_ctor_Ex1 Unlike Bitmap_ctor, Bitmap_ctor_Ex1 actually does something useful, and you will wind up using this function a great deal in your code. The syntax is shown in Listing 9.3.
Team LRN
96
Chapter 9: Bitmap and Font Basics
Listing 9.3 struct Bitmap * Bitmap_ctor_Ex1( struct Bitmap * ptr_bitmap, char * filename );
This function returns a pointer to the newly constructed bitmap object. It takes two parameters. The first is a pointer to a Bitmap object, and the second is the name of the resource you wish to load into that bitmap. When using resources that contain only a single bitmap, this is the function I use. I’ve gotten a lot of mileage out of it.
Bitmap_ctor_Ex2 The third constructor, _Ex2, isn’t quite as commonly used as _Ex1, although it still comes in pretty handy, so it’s a good function to have. The syntax for it is shown in Listing 9.4. Listing 9.4 struct Bitmap * Bitmap_ctor_Ex2( struct Bitmap * ptr_bitmap, int width, int height, int bpp );
Like the other constructors, this function returns a pointer to the newly constructed bitmap. It has four parameters, which are listed and explained in Table 9.1. Table 9.1 Bitmap_ctor_Ex2 parameters Parameter
Purpose
ptr_bitmap
A pointer to a Bitmap object that you are constructing.
width
The desired width of the bitmap.
height
The desired height of the bitmap.
bpp
The desired bits per pixel of the bitmap.
This function creates a blank bitmap with all color bits set to zero. This is quite useful for creating temporary bitmap storage areas in memory, for use as a double buffer or a dozen other reasons. There will be more on this in Chapter 14.
Bitmap_ctor_Ex3 The last constructor, _Ex3, can be pretty handy as well. It makes a duplicate of an already existing bitmap. The syntax for this function is shown in Listing 9.5.
Team LRN
Chapter 9: Bitmap and Font Basics
97
Listing 9.5 struct Bitmap * Bitmap_ctor_Ex3( struct Bitmap * ptr_bitmap, struct Bitmap * templ );
Like the other constructors, this function returns a pointer to the newly constructed Bitmap object. The two parameters are the pointer to the bitmap that is going to be constructed, and a Bitmap object pointer to have the information copied from.
Drawing a Bitmap on Screen Once you’ve constructed a Bitmap object all you need to do is draw it on the screen, and then show the screen using DisplayGraphics_show. The function to use here is DisplayGraphics_draw_bitmap. The syntax is shown in Listing 9.6. Listing 9.6 void DisplayGraphics_draw_bitmap( struct DisplayGraphics * ptr_gfx, struct Bitmap * bmp, int left, int top, int fm );
This function returns no value, and takes five parameters, which are listed and explained in Table 9.2. Table 9.2 DisplayGraphics_draw_bitmap parameters Parameter
Purpose
ptr_gfx
The pointer to the DisplayGraphics object.
bmp
A pointer to a bitmap that you wish to draw.
left
The x coordinate for the left of the bitmap.
top
The y coordinate for the top of the bitmap.
fm
A bitmap writing mode.
The bitmap drawing function is pretty simple: you essentially just say what you want drawn, where you want it drawn, and how you want it drawn. The “how” part is the fm parameter. If you just want to draw the Bitmap object as it appears, you use BM_NORMAL. However, there are some special effects you can achieve using different BM_* constants. These constants are listed and explained in Table 9.3.
Team LRN
98
Chapter 9: Bitmap and Font Basics
Table 9.3 BM_* constants Constant
Value
Meaning
BM_NORMAL
0
Write the bitmap as normal.
BM_INVERSE
1
Invert the colors of the bitmap.
BM_FLIPX
2
Mirror the bitmap horizontally (draw it backward).
BM_FLIPY
4
Mirror the bitmap vertically (draw it upsidedown).
NOTE: The BM_* constants, other than BM_NORMAL, represent individual bits, so you can combine these values to achieve more than one effect at the same time. You can, for example, use BM_FLIPX | BM_INVERSE, to get a backward, color-inverted bitmap drawn on the screen.
Draw Modes Another thing you have to think about when creating bitmaps is what your current draw mode is. Up until now, I haven’t made mention of draw modes, but since you can use them to create special effects with bitmaps, they bear mentioning during a Bitmap object discussion. drawmode is a property of the DisplayGraphics object, which by now you should be pretty familiar with. There are two functions dealing with drawmode, DisplayGraphics_set_draw_mode and DisplayGraphics_get_draw_mode. The syntax for these functions are shown in Listings 9.7.1 and 9.7.2, respectively. Listing 9.7.1 void DisplayGraphics_set_draw_mode( struct DisplayGraphics * ptr_gfx, drawmode_t dm );
Listing 9.7.2 drawmode_t DisplayGraphics_get_draw_mode( struct DisplayGraphics * ptr_gfx );
These functions are pretty simple to understand just by looking at them, so I won’t insult your intelligence by giving you a parameter breakdown. You may, however, notice the use of drawmode_t. This is just a typedef of int, like color_t. The drawmode_t type is used only for the two functions above. There are three different draw modes, and each has its own peculiar properties. The three draw modes are DM_PUT, DM_OR, and DM_XOR. I’m going to explain them now, but I’ll be returning to them in Chapter 14.
Team LRN
Chapter 9: Bitmap and Font Basics
99
DM_PUT In DM_PUT mode, the source pixel of the bitmap is written to the destination without regard to what is already on the destination. This means that however your Bitmap object looks in the file is how it will show up on the display. You’ll use this quite a bit for backgrounds. For now, this is the only draw mode we will use. DM_OR In DM_OR mode, the source pixel of the bitmap is checked against the destination’s background color (set using DisplayGraphics_set_bkcolor and retrieved using DisplayGraphics_get_bkcolor, which you can look up in the SDK; they are simple functions). If the source pixel matches the background color, nothing is written. If it does not match, the source pixel is written. This give the effect of transparency, but requires you to give up a color. There is a way to have transparency without giving up any colors, but we won’t get into that until Chapter 17. DM_XOR DM_XOR is a non-destructive way to write a bitmap onto a destination. The cool part about DM_XOR is that if you write the same bitmap in the same position twice while using DM_XOR, it will appear as though the bitmap was never there. Also, DM_XOR guarantees that the bitmap will contrast with whatever is on the display, making it useful for cursors and other things of that nature. Being “Draw-Mode Friendly” I’ll add this final note about draw modes, and then I’ll shut up about them until later. In a real game or application, you are likely going to need to change draw modes and background colors frequently. To keep glitches to a minimum, I recommend that when you are changing either the draw mode or the background color, you should first store the old value of those properties, and restore them after you are finished drawing. An example of this is shown in Listing 9.8. Listing 9.8 color_t old_bkcolor; drawmode_t old_drawmode; //retrieve current settings old_bkcolor=DisplayGraphics_get_bkcolor(main_module.m_gfx); old_drawmode=DisplayGraphics_get_draw_mode(main_module.m_gfx); //set new settings DisplayGraphics_set_bkcolor(main_module.m_gfx,new_bkcolor); DisplayGraphics_set_draw_mode(main_module.m_gfx,new_drawmode); //do whatever //restore settings DisplayGraphics_set_bkcolor(main_module.m_gfx,old_bkcolor); DisplayGraphics_set_draw_mode(main_module.m_gfx,old_drawmode);
Team LRN
100
Chapter 9: Bitmap and Font Basics
Doing this ensures that your changing the draw mode or background color won’t adversely affect another part of the application. Prior planning prevents poor performance.
Cleaning Up after a Bitmap The Bitmap object has a standard destructor, Bitmap_dtor. The syntax is shown in Listing 9.9. Listing 9.9 void Bitmap_dtor( struct Bitmap* ptr_bitmap, int memflag );
This function returns no value. The first parameter is the pointer to the bitmap that is being destructed. The second parameter is either LEAVE_MEMORY or FREE_ MEMORY, which depends on how you created the bitmap.
Bitmap Example I’ve thrown a lot of stuff at you, without any really solid examples to go along with it. Well, now I’m going to correct that situation. CyBk9_1 on the companion CD contains the workspace for a simple bitmap demo. Unlike all of the previous examples, this one actually interacts with the user, even if it still doesn’t do a whole lot. The basic idea of the bitmap demo is a small 10x10 picture of a person (named Dude.pic in the Res folder, and Dude.bmp in Res/src) that moves around the screen in response to the arrow keys. I based CyBk9_1 on CyBk6_3, the idle-loop framework, although the real-time or event-driven framework would have been just as good. Most of the code remains the same as in CyBk6_3. There are a few extra globals (Listing 9.10.1), and Prog_Init (Listing 9.10.2), Prog_Done (Listing 9.10.3), and Prog_Loop (Listing 9.10.4) have changed. In addition, OnKeyUp (Listing 9.10.5) has the code to move the dude around. Listing 9.10.1 struct Bitmap bmp; int dude_x,dude_y;
//bitmap for the dude //position of the dude
The additional globals in Listing 9.10.1 simply add what we need to make the program work, i.e., a Bitmap object named bmp, and two integers to keep track of the dude’s position (dude_x and dude_y). Listing 9.10.2 bool Prog_Init() { //initialization
Team LRN
Chapter 9: Bitmap and Font Basics
101
//construct the bitmap from a resource Bitmap_ctor_Ex1(&bmp,"dude.pic"); //start dude at 0,0 dude_x=0; dude_y=0; //return TRUE if program initialized, and FALSE if it did not return(TRUE); }
Prog_Init, shown in Listing 9.10.2, initializes all of our global data (that’s its job, after all). So, a call to Bitmap_ctor_Ex1 constructs our bitmap and loads into it the data stored in the resource named dude.pic. After that, dude_x and dude_y are initialized to zero, setting up our dude to be in the upper-left corner of the display. Listing 9.10.3 void Prog_Done() { //cleanup //destroy the bitmap Bitmap_dtor(&bmp,LEAVE_MEMORY); }
During Prog_Done, shown in Listing 9.10.3, we clean up the Bitmap object by calling Bitmap_dtor, using LEAVE_MEMORY as the memory flag, since we did not dynamically allocate the space for our Bitmap object. Listing 9.10.4 void Prog_Loop() { //idle loop //clear the screen white DisplayGraphics_fill_screen(main_module.m_gfx,CLR_WHITE); //draw the dude DisplayGraphics_draw_bitmap(main_module.m_gfx,&bmp, dude_x*10,dude_y*10,BM_NORMAL); //show the screen DisplayGraphics_show(main_module.m_gfx); }
Prog_Loop, in Listing 9.10.4, performs the showing of our display. Since this is a simple demo, Prog_Loop clears the screen to white, draws the bitmap at the proper position using DisplayGraphics_draw_bitmap, and shows the screen. See Figure 9.1.
Team LRN
102
Chapter 9: Bitmap and Font Basics
Figure 9.1
Listing 9.10.5 bool OnKeyUp(int scancode,int mask,char ch) { //switch on the scan code switch(scancode) { case KEY_UP: //move up { if(dude_y>0) dude_y—; return(TRUE); }break; case KEY_DOWN: //move down { if(dude_y<9) dude_y++; return(TRUE); }break; case KEY_LEFT: //move left { if(dude_x>0) dude_x—; return(TRUE); }break; case KEY_RIGHT: //move right { if(dude_x<15) dude_x++; return(TRUE); }break; } //if message hasn't been handled, return FALSE return(FALSE); }
The OnKeyUp function (Listing 9.10.5) does the work of moving the dude whenever one of the arrow keys is released. I could have put this in the OnKeyDown function, and the behavior of the little person would have been a bit different. In this example, however, the dude doesn’t move until you release the arrow key. This example is still pretty simple at this point, but I thought you might appreciate something where you aren’t just watching things being drawn on the screen.
Team LRN
Chapter 9: Bitmap and Font Basics
103
Font Objects Bitmap objects are pretty cool, and you are going to use them a great deal in your programs. However, they are not the only way to give information to the user. You will also make use of text, and to use text, you must make use of fonts. The word “font” has a long history behind it, going all the way back to the Gutenberg Bible printing. A font is nothing more than a collection of letters, numbers, and symbols used for some sort of textual output. On a computer (Cybiko included) a letter is represented as a monochrome bitmap as a general rule, so on the Cybiko, a font is nothing more than a series of bitmaps that represent letters, numbers, and symbols.
Global Fonts You can, if you wish, make your own fonts. However, when starting out with writing text on the Cybiko, you might want to start out with the fonts already there. Cybiko provides you with four general purpose fonts: cool_normal_font, cool_bold_font, mini_normal_font, and mini_bold_font. These fonts are listed in Table 9.4, along with the width and height of the characters, and the numer of characters in these built-in fonts. Table 9.4 Built-in Cybiko fonts Font Name
Width
Height
Characters
cool_normal_font
10
12
221
cool_bold_font
11
12
221
mini_normal_font
8
9
221
mini_bold_font
9
9
221
Keep in mind that most of the characters in the font don’t take up all of the full width and height, and many characters in these fonts are blank. These fonts are shown in Figures 9.2.1 through 9.2.4.
Figure 9.2.1: “Cool Normal” font
Team LRN
Figure 9.2.2: “Cool Bold” font
104
Chapter 9: Bitmap and Font Basics
Figure 9.2.4: “Mini Bold” font
Figure 9.2.3: “Mini Normal” font
Setting the Current Font In order to draw text on the DisplayGraphics object, you must first select which font you want to use. To do this, you use DisplayGraphics_set_font, which is shown in Listing 9.11. Listing 9.11 void DisplayGraphics_set_font( struct DisplayGraphics * ptr_gfx, struct Font * font );
This function returns no value and takes two parameters. The first parameter is the pointer to the DisplayGraphics object, and the second parameter is a pointer to the font you wish to use. To retrieve the font currently in use, you use DisplayGraphics_get_font, shown in Listing 9.12. Listing 9.12 struct Font * DisplayGraphics_get_font( struct DisplayGraphics * ptr_gfx );
This function takes a single parameter, the pointer to the DisplayGraphics object, and returns a pointer to the current font in use. It is good practice to be “font friendly,” so before switching fonts, save the old font pointer somewhere and restore it when you are done. This helps avoid problems in other parts of the program that may use different fonts.
Team LRN
Chapter 9: Bitmap and Font Basics
105
Writing Text Once you have the proper font selected, you simply set the color using DisplayGraphics_set_color and draw the text. There are two text drawing functions, DisplayGraphics_draw_text and DisplayGraphics_draw_text_Ex. These are shown in Listings 9.13.1 and 9.13.2 respectively. Listing 9.13.1 void DisplayGraphics_draw_text( struct DisplayGraphics * ptr_gfx, char * text, int left, int top );
Listing 9.13.2 void DisplayGraphics_draw_text_Ex( struct DisplayGraphics * ptr_gfx, char * text, int left, int top, int flen );
Neither function returns a value. The only difference in the parameter list is that the _Ex function has an extra one. These parameters are listed and explained in Table 9.5. Table 9.5 DisplayGraphics_draw_text(_Ex) parameters Parameter
Purpose
ptr_gfx
Pointer to the DisplayGraphics object.
text
A character pointer to the string you wish to write.
left
x coordinate of the left of the text.
top
y coordinate of the top of the text.
flen
(_Ex only) The maximum length (in pixels) of the string that you wish to write.
If you recall Chapter 2, we used DisplayGraphics_draw_text to write “Hello, world!” onto the screen in the upper-left corner.
Formatting Text There is more to presentation than simply drawing text, of course. We may wish to center a string on the screen, or have it flush against the right side or against the bottom, or have it positioned within a rectangle. For this, we need to know how tall a
Team LRN
106
Chapter 9: Bitmap and Font Basics
string is, how wide a string is, and so on. Luckily, Cybiko C has functions for this. These functions are shown in Listing 9.14. Listing 9.14 int int int int
DisplayGraphics_get_char_height (struct DisplayGraphics* ptr_gfx) ; DisplayGraphics_get_char_width (struct DisplayGraphics* ptr_gfx); DisplayGraphics_string_width (struct DisplayGraphics* ptr_gfx, char *string); DisplayGraphics_string_width_Ex (struct DisplayGraphics* ptr_gfx, char *string, int flen);
DisplayGraphics_get_char_height and DisplayGraphics_get_char_width are pretty obvious. They return the width and height of the currently selected font of the DisplayGraphics object. DisplayGraphics_string_width and DisplayGraphics_string_width_Ex both return the width (in pixels) of a string (the second parameter). In the case of DisplayGraphics_string_width_Ex, only the first flen number of characters are used for calculating this width. To left justify or top justify (i.e., have your text flush against the left or top of some rectangle), you simply use the left and/or top coordinate for x and y. To center something horizontally, you take the leftmost x coordinate of the area in which you are centering, add half of the width of the area you are centering in, and subtract half of the string’s width. Similarly, to center vertically take the top y value, add half of the height of the area, and subtract half of the character height. To right justify something, you take the rightmost x value and subtract the width of the string. To bottom justify, you take the bottommost y value, and subtract the character height. Ergo, if you had four values, left, top, width, and height (i.e., the same information as a rect_t), you can do any text justification you wish using the formulas shown in Listing 9.15. Listing 9.15 //left,top,width, and height are given values //textx and texty is where the text should be drawn //text is a character pointer to the desired string //left justify textx=left; //horizontal center justify textx=left+width/2–DisplayGraphics_string_width(main_module.m_gfx,text)/2; //right justify textx=left+width–DisplayGraphics_string_width(main_module.m_gfx,text); //top justify texty=top;
Team LRN
Chapter 9: Bitmap and Font Basics
107
//vertical center justify texty=top+height/2–DisplayGraphics_get_char_height(main_module.m_gfx)/2; //bottom justify texty=top+height/–DisplayGraphics_get_char_height(main_module.m_gfx);
Naturally, you can combine any horizontal (left, center, right) with any vertical (top, center, bottom) justification to suit your needs.
Font Demo CyBk9_2 on the companion CD contains the workspace for the font demo. This demo is based on the idle-loop framework (CyBk6_3). There was quite a bit that I needed to add for this demo, so be prepared for several code listings. First off, I needed a few constants for the justifications (Listing 9.16.1). Next, I needed some extra global variables for printing text (Listing 9.16.2). The functions that needed modification were Prog_Init (Listing 9.16.3), Prog_Loop (Listing 9.16.4) and finally, OnKeyUp (Listing 9.16.5). Listing 9.16.1 //horizontal justification #define J_LEFT 0 #define J_CENTER 1 #define J_RIGHT 2 //vertical justification #define J_TOP 0 #define J_VCENTER 1 #define J_BOTTOM 2
Listing 9.16.1 shows all of the #defines I needed for the font demo. I made three for horizontal (left, center, right) and three for vertical (top, vcenter, bottom). The meanings should be relatively clear. Listing 9.16.2 //current font struct Font* ptr_current_font; //justification int justify_h; //horizontal int justify_v; //vertical //justification rectangle struct rect_t justify_rect; //text buffer char text[20];
I don’t like to hard code anything (it’s bad code, and it’s hard to read). So, in Listing 9.16.2, I added a number of globals. Since this font demo works with all four of the Cybiko fonts, I decided to have a global store the font that is currently being used (ptr_current_font). Also, since I’m using justification to format the font, I made two little variables to keep track of that: justify_h and justify_v. Then, I made a rect_t
Team LRN
108
Chapter 9: Bitmap and Font Basics
variable named justify_rect, which stores the rectangle in which we are justifying the text. Last, I created a buffer for text, called text. Listing 9.16.3 bool Prog_Init() { //initialization //set the current font ptr_current_font=cool_normal_font; //set the text sprintf(text,"%s","cool_normal_font"); //set the current justification justify_h=J_LEFT; justify_v=J_TOP; //set the justification rect rect_set(&justify_rect,0,0,160,100); //return TRUE if program initialized, and FALSE if it did not return(TRUE); }
Listing 9.16.3 shows the Prog_Init for the font demo. This function does nothing more than initialize the global variables to their starting values. The font is set to cool_normal_font, the text is set to “cool_normal_font”, justification is set to left and top, and the justify rectangle is set to the entire screen. Listing 9.16.4 void Prog_Loop() { //idle loop int textx; int texty; //clear the screen DisplayGraphics_fill_screen(main_module.m_gfx,CLR_WHITE); //set the font DisplayGraphics_set_font(main_module.m_gfx,ptr_current_font); //set the color DisplayGraphics_set_color(main_module.m_gfx,CLR_BLACK); //calculate textx and texty switch(justify_h) { case J_LEFT: //left justify { textx=justify_rect.x; }break; case J_RIGHT: //right justify { textx=justify_rect.x+justify_rect.w–DisplayGraphics_string_width (main_module.m_gfx,text); }break;
Team LRN
Chapter 9: Bitmap and Font Basics
109
case J_CENTER: //center justify { textx=justify_rect.x+justify_rect.w/2–DisplayGraphics_string_width (main_module.m_gfx,text)/2; }break; } switch(justify_v) { case J_TOP: //top justify { texty=justify_rect.y; }break; case J_BOTTOM: //bottom justify { texty=justify_rect.y+justify_rect.h–DisplayGraphics_get_char_height (main_module.m_gfx); }break; case J_VCENTER: //vertically center justify { texty=justify_rect.y+justify_rect.h/2–DisplayGraphics_get_char_height (main_module.m_gfx)/2; }break; } //draw the text DisplayGraphics_draw_text(main_module.m_gfx,text,textx,texty); //show the screen DisplayGraphics_show(main_module.m_gfx); }
Listing 9.16.4 shows the Prog_Loop function of the font demo. It does all of the drawing for the program. It clears the screen to white, calculates where to place the text using the calculations I talked about earlier, and finally, it draws the text and shows the screen. Listing 9.16.5 bool OnKeyUp(int scancode,int mask,char ch) { //switch on the scancode switch(scancode) { case KEY_1: //set font to cool_normal_font { sprintf(text,"%s","cool_normal_font"); ptr_current_font=cool_normal_font; return(TRUE); }break; case KEY_2: //set font to cool_bold_font { sprintf(text,"%s","cool_bold_font"); ptr_current_font=cool_bold_font;
Team LRN
110
Chapter 9: Bitmap and Font Basics
case
case
case
case
case
case
case
case
return(TRUE); }break; KEY_3: //set font to mini_normal_font { sprintf(text,"%s","mini_normal_font"); ptr_current_font=mini_normal_font; return(TRUE); }break; KEY_4: //set font to mini_bold_font { sprintf(text,"%s","mini_bold_font"); ptr_current_font=mini_bold_font; return(TRUE); }break; KEY_T: //top justify { justify_v=J_TOP; return(TRUE); }break; KEY_V: //vertical center justify { justify_v=J_VCENTER; return(TRUE); }break; KEY_B: //bottom justify { justify_v=J_BOTTOM; return(TRUE); }break; KEY_L: //left justify { justify_h=J_LEFT; return(TRUE); }break; KEY_C: //center justify { justify_h=J_CENTER; return(TRUE); }break; KEY_R: //right justify { justify_h=J_RIGHT; return(TRUE); }break;
} //if message hasn't been handled, return FALSE return(FALSE); }
Team LRN
Chapter 9: Bitmap and Font Basics
111
Listing 9.16.5 shows the OnKeyUp function, which is the longest function of the bunch because it does all of the work. Depending on what key was pressed, the program changes fonts or changes justifications. The number keys 1 through 4 change the fonts. The L, C, and R keys change the horizontal justification, and the T, V, and B keys change the vertical justification. The OnKeyUp function is just there to make these changes. You might want to experiment with the font demo a little bit, since it demonstrates most of what you would want to do with a font. You could, for example, change the values sent to the justify_rect, and have the justifications be elsewhere on the screen. We will return to fonts in greater detail in a later chapter.
Summary Bitmaps and fonts give real meaning to your graphical displays. You almost can’t make a program without one or both of these (sure, you might have a text-only program, or you might have a bitmap-only program, but usually you will use both). As of right now, you have enough knowledge to make yourself a game. Of course, you wouldn’t have any sounds yet, and you wouldn’t be able to save your top ten score list, and a handful of other little details that make a program feature rich. However, the visual component is 70%, with the other senses making up the other 30%, so you are well on your way. This is by no means the final word on bitmaps and fonts. This is just the basics. There is a lot more you can do and will do with bitmaps than the simple examples we have here, but that’s a little bit later.
Team LRN
Chapter 10
Sounds, Music, and Vibration Overview If 70% of our input is from sight, i.e., the display, I would say about 20% is from hearing, so while not as important as the visual display, good sounds and music are important. Unfortunately, the sound hardware of the Cybiko isn’t exactly topnotch. It is a small speaker that can make little beeps and boops. Despite its lack of quality, it’s all we’ve got, so we should make the best use of it that we can.
Beeps The simplest way to make sounds on the Cybiko is to use one of the predefined “beeps” that Cybiko provides with CyOS. This is done, to no one’s surprise, with a function named beep, shown in Listing 10.1. Listing 10.1 void beep( int beep_type );
This function returns no value, and has one parameter. The parameter takes one of the predefined BEEP_* constants, listed in Table 10.1.
112
Team LRN
Chapter 10: Sounds, Music, and Vibration
113
Table 10.1 BEEP_* Constants Constant
Use
BEEP_OK
A generic beep for when things are OK.
BEEP_INFO
A generic beep for information dialog boxes.
BEEP_QUESTION
A generic beep for when you need to confirm something.
BEEP_ERROR
A generic beep for when you report an error to the user.
I made a simple beep demo in CyBk10_1 on the companion CD. In this simple little demo, you can hit the 1, 2, 3, or 4 key to get the different beeps. I won’t bother with the code. The only function that changed was the OnKeyUp function.
Tone Generation Beeps are nice, I suppose, but I’m certain that at some point you will want more than just the four tones that the beep function makes available to you. On the Cybiko, there are 68 chromatic tones available to you through a function called play_tone. This function is shown in Listing 10.2. Listing 10.2 void play_tone( int index );
This function returns no value, and takes a single parameter. The index parameter is the number of the tone you wish to play (0 through 67), or a negative number to turn off the currently playing tone. This begs the question of what do these tones mean? Well, without a long diatribe on music theory, these tone indexes are musical notes. The number 0 is an E, the number 1 is an F, the number 2 is an F-sharp/G-flat, and so on, all the way up to 67, which is a B. Table 10.2 shows these tones, split into octaves. Table 10.2 Note equivalents of play_tone indices Octave Note
Octave2
Octave3
Octave4
Octave5
Octave6
C
8
20
32
44
56
C#/Db
9
21
33
45
57
D
10
22
34
46
58
D#/Eb
11
23
35
47
59
12
24
36
48
60
E
Octave1
0
Team LRN
114
Chapter 10: Sounds, Music, and Vibration
Octave Note
Octave1
Octave2
Octave3
Octave4
Octave5
Octave6
F
1
13
25
37
49
61
F#/Gb
2
14
26
38
50
62
G
3
15
27
39
51
63
G#/Ab
4
16
28
40
52
64
A
5
17
29
41
53
65
A#/Bb
6
18
30
42
54
66
B
7
19
31
43
55
67
With this in mind, you can generate any of these notes with a simple call to play_tone. To stop tones, you can use the stop_tone function, which returns no value and takes no parameter. Using stop_tone() has the same effect as using play_tone(–1). I wrote a little example of using play_tone that can be found in CyBk10_2 on the companion CD. This program is based on the event-driven framework (CyBk6_2). This little example turns your Cybiko into a mini-musical keyboard. Three complete octaves are located on the keyboard. The row starting with the “1” key is a row of “black keys,” the row starting with “Q” is a row of “white keys,” the row starting with “A” is another row of black keys, and the row starting with “`” is another row of white keys. The keys are situated (to the best of my ability) as a piano would be. Full instructions can be found in the Root.spl file. With this example, I’m not going to bother with the code. Essentially, all it does is respond to the OnKeyDown and OnKeyUp messages. During an OnKeyDown, depending on the key, a tone is played, and when a key is released (during OnKeyUp), all tones are stopped. You can take a look at the code on your own, but there isn’t that much to it.
Raw Tone Generation The third method of generating tones on the Cybiko is to use the play_raw function, shown in Listing 10.3. Listing 10.3 void play_raw( int divisor );
This function returns no value and takes a single parameter. The divisor is a little difficult to explain, and has to do with how speaker technology works. The speaker is nothing more than a little magnet that moves back and forth, causing the air in the area to vibrate, which travels to your ear and vibrates your eardrum, which moves little bones inside your inner ear, which compress liquid, which is changed to electrical signals that are read by your brain as sound. The sound wave generated depends on
Team LRN
Chapter 10: Sounds, Music, and Vibration
115
how quickly the magnet is moved back and forth. That’s what the divisor does. It is based on the internal timer of the Cybiko. If you were to use 1 as the divisor, you would have the magnet move as fast as the timer, in theory. This would produce a sound audible only to rodents and bats. The hardware can’t actually handle this, of course, so the range for the divisor is anywhere from 1356 (producing a sound at 4075Hz) through 6505 (producing a sound at 85Hz). The higher the divisor, the lower the tone generated. A lower divisor will generate a higher sound. What good is this? Well, using play_raw, you get the full (albeit a bit limited) capabilities of the Cybiko sound hardware at your disposal. To stop raw tones, you can use the function stop_tone. CyBk10_3 on the companion CD contains an example workspace that makes use of play_raw. Try not to run it around anybody who you would like to remain friends with. The code is pretty simple, as only Prog_Loop and Prog_Done have changed, and only one line was added to each, so you should be able to understand it without me placing all of the code here.
MSequence The M in MSequence stands for “music.” An MSequence object is little more than a series of tones and durations that make up a piece of music. You can make MSequences from MIDI files using the 2mus utility in the Bin directory of the SDK. I personally use a hex editor to make mine, since I do not have a MIDI editor, but that’s just me. Music files are stored in .mus files, and most of the time, you’ll add them into your .app archive using Filer. Once you’ve got a music sequence as a resource, your job becomes a great deal easier (making the music is the hard part).
MSequence_ctor An MSequence object, like every other Cybiko object, must first be constructed using a constructor function. MSequence has only one constructor, MSequence_ctor, which is shown in Listing 10.4. Listing 10.4 struct MSequence* MSequence_ctor( struct MSequence* ptr_msequence, char* sz_file_name );
This function returns a pointer to the newly constructed MSequence object. It takes two parameters. The first is a pointer to the MSequence object that you wish to construct, and the second is the name of the resource that you wish the MSequence to be loaded from. You can construct your MSequence object at any time, although you will
Team LRN
116
Chapter 10: Sounds, Music, and Vibration
usually do this either at the beginning of a program or at the beginning of a level (if, for example, different levels in your game have different music).
MSequence_dtor On the flip side of MSequence_ctor is MSequence_dtor, shown in Listing 10.5. Listing 10.5 void MSequence_dtor( struct MSequence* ptr_msequence, int memory_flag );
This destructor looks and behaves just like any other destructor. You pass the pointer of the object you wish to destroy, along with either LEAVE_MEMORY or FREE_ MEMORY, depending on whether or not you dynamically allocated the object in question. You would normally destroy an MSequence object at the end of the program or the end of the level (i.e., whenever you no longer need that MSequence).
Playing and Stopping Now that you can construct and destruct this object, how about playing it? There are actually two different ways to play an MSequence on the Cybiko. One way is to play it in the foreground, using MSequence_play, which is shown in Listing 10.6. Listing 10.6 void MSequence_play( struct MSequence* ptr_msequence );
This function returns no value and takes a single parameter, a pointer to the MSequence object that you wish to play. The other way to play an MSequence object is in the background, using the MSequence_play_background function shown in Listing 10.7. Listing 10.7 void MSequence_play_background( struct MSequence* ptr_msequence );
Like MSequence_play, this function returns no value and takes a pointer to the MSequence object you wish to play. The difference between MSequence_play and MSequence_play_background is mostly a matter of priority. Whenever an MSequence is playing in the foreground, the background MSequence will not be heard. Other sounds, such as the system alarm, have an even higher priority, and will override any MSequence you may have playing at the time, foreground or background.
Team LRN
Chapter 10: Sounds, Music, and Vibration
117
To stop an MSequence, you use MSequence_stop. This function is shown in Listing 10.8. Listing 10.8 void MSequence_stop( struct MSequence* ptr_msequence );
This function returns no value and takes as a parameter the MSequence object you wish to stop playing. MSequence_stop does not differentiate between foreground and background MSequence objects, it just stops it no matter what.
Information Functions The MSequence class also provides a couple of functions for retrieving information about a playing MSequence object. These functions are MSequence_is_playing (shown in Listing 10.9) and MSequence_get_curr_pos (shown in Listing 10.10). Listing 10.9 bool MSequence_is_playing( struct MSequence* ptr_msequence );
This function returns TRUE if the MSequence object in question is playing, and FALSE if it is not. This function doesn’t care whether the MSequence object is playing in the foreground or the background. Listing 10.10 int MSequence_get_curr_pos( struct MSequence* ptr_msequence );
This function returns how far into the MSequence object the playing has progressed. The value 0xFFFF(–1) is returned if it is right at the beginning, before the first note. The MSequence_is_playing function is of great use for looping MSequence objects (if you want to have a song playing in the background all the time). The code for this is pretty simple, and is shown in Listing 10.11. Listing 10.11 //mseq is an MSequence object if(!MSequence_is_playing(&mseq)) MSequence_play_background(&mseq);
Just make sure this code gets called often enough so that the MSequence doesn’t get to the end and just stop.
Team LRN
118
Chapter 10: Sounds, Music, and Vibration
Muting You can mute (or unmute) background, foreground, or both types of MSequence objects, by using a set of functions that I’m lumping together as the muting functions. The three functions, MSequence_global_mute, MSequence_mute_foreground, and MSequence_mute_background, are all shown in Listing 10.12. Listing 10.12 void MSequence_global_mute(bool mute_flag); void MSequence_mute_foreground(bool mute_flag); void MSequence_mute_background(bool mute_flag);
All of these functions return no value and take a single parameter, TRUE or FALSE, specifying whether or not you want muting on or off.
MSequence Example CyBk10_4 on the companion CD contains a workspace with the MSequence demo. In this simple demo, an MSequence is constructed during Prog_Init, destroyed during Prog_Done, and played if not already playing during Prog_Loop. It’s a pretty simple demo, but it does illustrate how you make use of the MSequence object. I’m not going to bother with the code here.
The Voice of Sanity I’ll show you one last little function you might want to use for MSequences, and then we’re moving on. It is entirely possible to make a .mus file that is completely unplayable. By the time you are ready to distribute your game or application, you should ensure that all of your .mus files contain valid information (preferably by actually playing them and testing them out first). However, during development, you may well wind up with an unplayable .mus file. The SDK has a function to test whether a given MSequence is “sane” or not. This function is called MSequence_is_sane, and is shown in Listing 10.13. Listing 10.13 bool MSequence_is_sane(struct MSequence* ptr_msequence);
This function returns TRUE if the sequence is valid, and FALSE if it is not. The parameter is a pointer to the MSequence object whose sanity is in question.
Enabling and Disabling Tone Generation Now that you can add sounds, you will probably want to add some sort of sounds to all of your applications and games, even if you just have a beep or two. However, you will probably at some point want to be able to check to see if a sound is playing, check to
Team LRN
Chapter 10: Sounds, Music, and Vibration
119
see if sounds are enabled, and possibly even enable or disable sounds programmatically. This isn’t strictly required. There is, after all, a key combination used by Cybiko to enable or disable sounds, but it’s good stuff to know anyway, just in case.
is_tone_playing If you are using play_tone or play_raw, you may wish to check to see if a tone is playing. To do this, you use the is_tone_playing function, shown in Listing 10.14. Listing 10.14 bool is_tone_playing();
This function takes no parameters. It returns TRUE if a tone is playing, and FALSE if there are no tones playing.
get_sounds_enabled and enable_sounds There are times when you might want to provide an alternate key command to turn on and off the sounds in your game or application. (Hitting the Fn key and one of the Section keys is something of a pain, and I never remember which Section key does what, and for some reason, I always wind up changing the contrast of my display when I intend to toggle sounds). To retrieve whether or not sounds are currently enabled, you use the get_sounds_ enabled function, shown in Listing 10.15. To set whether or not sounds should be enabled or disabled, you use the enable_sounds function, shown in Listing 10.16. Listing 10.15 bool get_sounds_enabled();
This function takes no parameters. It returns TRUE if sounds are enabled, and FALSE if they are disabled. Listing 10.16 void enable_sounds(bool enable_flag);
This function returns no value, and takes either TRUE or FALSE, to enable or disable sounds respectively. Listing 10.17 shows a very simple line of code that will toggle sound from off to on, or on to off, depending on the initial state of sound enabling. Listing 10.17 enable_sounds(!get_sounds_enabled());
Team LRN
120
Chapter 10: Sounds, Music, and Vibration
Key Clicks Unless you have some of the older Cybiko hardware (those with the actual on/off switch rather than the reset button), your device has the capability of beeping when a key is pressed, and probably does so unless you have your sound turned off. This feature is known as “key clicking.” You can control, programmatically, whether or not the Cybiko on which your program is running beeps when a key is clicked, and what kind of beep is emitted when a key is pressed. Personally, I find key clicks to be distracting and somewhat annoying.
Enabling and Disabling Key Clicks To check whether or not key clicks are enabled, you use the function get_clicks_ enabled, which is shown in Listing 10.18. To set whether or not you want key clicks enabled, you use function set_clicks_enabled, shown in Listing 10.19. Listing 10.18 bool get_clicks_enabled (void)
This function takes no parameters and returns either TRUE or FALSE, indicating whether or not key clicks are enabled on the Cybiko device. Listing 10.19 void set_clicks_enabled (bool enable)
This function takes a bool, which specifies whether or not you want key clicks to be enabled on the Cybiko device. These two functions are rather similar to the get_sounds_enabled and enable_sounds functions, which is why they are covered here, rather than in the chapter on keyboard input.
Advanced Disables There may be a time when you would like some keys that have key clicks, and some keys that don’t. The Cybiko SDK has a function that allows you to do this, called disable_key_click, shown in Listing 10.20. Listing 10.20 void disable_key_click (int* ptr_key_list, int key_count)
This function returns no value and takes two parameters. The ptr_key_list parameter is a pointer to an int, which points to an array of keys that you wish to disable, and key_count is the number of keys in that array. This function isn’t quite as intuitive as some of the others, so I’ve provided a small snippet of code that shows how to disable key clicks for the arrow keys. This snippet can be found in Listing 10.21.
Team LRN
Chapter 10: Sounds, Music, and Vibration
121
Listing 10.21 int Arrows[4]; //fill in the array Arrows[0]=KEY_LEFT; Arrows[1]=KEY_RIGHT; Arrows[2]=KEY_UP; Arrows[3]=KEY_DOWN; disable_key_click(Arrows,4);
//there are four arrow keys
//disable key clicks for the arrows
Unfortunately, there is no function to retrieve what keys have the key click disabled. To turn key clicks back on for all keys, you use the code shown in Listing 10.22. Listing 10.22 disable_key_click(0,0);
//turns all key clicks back on
Kinds of Clicks Besides whether or not key clicks are enabled, and for which keys the key click is enabled, you may also set what kind of key click occurs when a key is pressed. This is done through the function set_click_kind, shown in Listing 10.23. Listing 10.23 void set_click_kind (click_kind_t kind)
This function returns no value, and takes a click_kind_t value called kind. The click_kind_t type is nothing more than a typedeffed int. There are three valid values for click_kind_t, which are shown in Table 10.3. Table 10.3 click_kind_t values Constant
Meaning
CK_NO
No beep
CK_BEEP
Long beep
CK_BEEP_SHORT
Short beep
As you can see, there aren’t many options here. The key click isn’t exactly a complicated part of the SDK. An alternate way of turning off key clicks is just to set the click kind to CK_NO. To retrieve what kind of key click is currently set, you use the get_click_kind function, shown in Listing 10.24. Listing 10.24 click_kind_t get_click_kind (void)
This function takes no parameters and returns the kind of key click that is currently selected.
Team LRN
122
Chapter 10: Sounds, Music, and Vibration
Key clicks are pretty simple, so I’ll leave you to experiment with them on your own.
Vibration The last topic of the chapter is vibration. Built into every Cybiko device, there is a little motor that is connected to an off-balance weight. When it is given power, it spins the weight around in circles, causing a vibration effect. The vibration section of the SDK is the simplest of all. It consists of two functions, vibrate (shown in Listing 10.25) and enable_vibration (shown in Listing 10.26). Listing 10.25 void vibrate (int index)
This function returns no value and takes a number between 0 and 255 as a parameter. The number indicates the strength of vibration you desire, with a higher number meaning a harder vibration. Listing 10.26 void enable_vibration (bool enable)
This function returns no value and takes either TRUE or FALSE as a parameter, indicating whether or not you wish for vibrations to be enabled. There is no function for determining whether or not vibration is enabled.
Summary The section of the SDK for sounds, key clicks, and vibrations is pretty simple to understand and use, which is why I didn’t spend a lot of time on it. All of the information you need is here in this chapter, and we won’t need to revisit this topic. The sound generating capabilities of the Cybiko are by today’s standard rather primitive. With a little creativity, though, you should be able to incorporate these features to enhance gameplay.
Team LRN
Chapter 11
Basic Dialogs Overview What we have looked at thus far in the Cybiko SDK has been rather simple. Starting simple is a good way to present anything that is unfamiliar, and hopefully you now have a solid foundation of how things are done on the Cybiko. The Cybiko SDK is not altogether simple like this. Indeed, the part that I’ve come to call the user interface, or UI, section, is rather complicated and difficult to understand at a glance. Taken in pieces, however, it’s not incomprehensible. We’re going to take it slow. In this chapter, I’m going to present you with the simplest component of the Cybiko UI, cDialog. cDialog is pretty easy to learn, and it packs a lot of functionality. You can make message boxes and input boxes similar in style to those in Win32. That, however, is the limit of what cDialog can do, as we shall see shortly.
Cybiko Dialogs The cDialog class is based on the cCustomForm class, which in turn is based on the cClip class, which is in turn based on the cObject class. Translation: cDialog is a pretty high-level user interface element. If you were to look up the cDialog class in the SDK help files, you would see a big list of constants and functions dealing with cDialog. This list might seem daunting at first. Don’t worry; getting started with cDialog only requires a handful of those constants, and a mere five of the functions.
cDialog_ctor The first of the functions we are concerned with here is cDialog_ctor, which, naturally enough, constructs a new cDialog. This function is shown in Listing 11.1.
123
Team LRN
124
Chapter 11: Basic Dialogs
Listing 11.1 struct cDialog * cDialog_ctor ( struct cDialog * ptr_dialog, char * caption, char * text, long style, int edit_size, struct cWinApp * ptr_win_app )
This function returns a pointer to the newly created cDialog object. The parameters are listed and explained in Table 11.1. Table 11.1 cDialog_ctor parameters Parameter
Purpose
ptr_dialog
Pointer to the cDialog object being constructed.
caption
Caption for the cDialog object.
text
Prompt to use in the cDialog object.
style
Style of the dialog (see section on styles below).
edit_size
Size of the edit box buffer.
ptr_win_app
Pointer to a cWinApp (use main_module.m_process).
Most of these parameters are self-explanatory. Your dialogs can have title bars (with the caption parameter) and text prompts (with the text parameter). A cDialog object needs a message queue (such as the ptr_win_app parameter) from which it receives input. The only mysterious parameters are style and edit_size.
cDialog Styles The fourth parameter of cDialog_ctor, style, specifies how you want the dialog to look. There are a limited number of options, and more complicated forms will require a more complicated user interface element than cDialog can provide. For most simple cases, however, cDialog has what you need. The style parameter consists of a number of flags, that specify how the dialog should look. These are all members of the tDialogStyle enum. For convenience and ease of presentation, I have divided these flags into several groups of related flags. These groups are button flags, sound flags, edit box flags, and other flags. Button Flags The first group of flags specifies which buttons you want to appear on the dialog. There are 16 different buttons you can have on your dialogs. The flag constants and what button appears when each flag is used are shown in Table 11.2.
Team LRN
Chapter 11: Basic Dialogs
125
Table 11.2 Button flags Flag
Button Text
mbNone
No buttons
mbOk
OK button
mbYes
YES button
mbNo
NO button
mbDraw
DRAW button
mbChat
CHAT button
mbQuit
QUIT button
mbNew
NEW button
mbUpload
UPLOAD button
mbRename
RENAME button
mbDelete
DELETE button
mbView
VIEW button
mbPlay
PLAY button
mbRestart
RESTART button
mbSet
SET button
mbLater
LATER button
mbCancel
CANCEL button
mbReverseOrder
Reverses the order of the buttons in the dialog
There is one oddball flag thrown into this group, mbReverseOrder. Normally, when you show a dialog, it will display a lower numbered flag, like mbOk, to the left of a higher numbered flag, like mbCancel. Using mbReverseOrder switches this around so that the CANCEL button will appear to the left of the OK button. Keep in mind that while there are 16 different buttons you can choose from, only two of them can appear on a dialog, so your grand plans for a 16-button dialog box will not come to be using the cDialog class. You would instead have to use a cCustomForm to do that. Sound Flags Besides buttons, you can also specify what sound you want to play when a dialog comes up. All of these flags start with the letters “mbs.” They are shown in Table 11.3.
Team LRN
126
Chapter 11: Basic Dialogs
Table 11.3 Dialog sound style flags Flag
Meaning
mbsDefault
Play default music (same as mbs0)
mbsNone
Play no music (same as mbs0)
mbs0
Play music 0
mbs1
Play music 1
mbs2
Play music 2
mbs3
Play music 3
mbs4
Play music 4
mbs5
Play music 5
mbs6
Play music 6
mbs7
Play music 7
mbsAbout
Same as mbs6
mbsDraw
Same as mbs4
mbsEsc
Same as mbs5
mbsLinkOff
Same as mbs1
mbsNoDraw
Same as mbs7
mbsWelcome
Same as mbs2
There are eight different pieces of music that can be played when your dialog is first shown. mbs0, mbsDefault, and mbsNone are all the same, indicating that no music is to be played. You can only use one of the eight pieces of music. Mixing these flags will likely not play the music you request. Edit Box Flags Table 11.4 shows the edit box flags. You use these when you wish to have an edit box appear on your form so that you can retrieve a textual response from the user. Table 11.4 Edit box flags Flag
Meaning
mbEdit
You wish to have an edit box on the dialog.
mbPasswd
The edit box is for passwords, so all text will show as *.
If you want a password dialog, both flags must be specified. Using the mbEdit flag means that you must put a non-zero number in the edit_size parameter of cDialog_ctor.
Team LRN
Chapter 11: Basic Dialogs
127
Other Flags Table 11.5 shows the rest of the flags. These flags affect the appearance or behavior of the dialog. Table 11.5 Other flags Flag
Meaning
mbNoEsc
The dialog must ignore the Esc key.
mbLeft
The dialog’s text will be left justified rather than centered.
mbNoShutup
The dialog will ignore MSG_SHUTUP.
Normally, hitting Esc during a dialog will cause the dialog to close, and a result of mrCancel (which we’ll talk about a little later) will be returned. Specifying the mbNoEsc flag in your call to cDialog_ctor will prevent this behavior from happening. Also, a dialog’s text is usually centered. Using the mbLeft flag will cause the text to be left justified. And finally, when you want your dialog to ignore the MSG_SHUTUP message, use mbNoShutup. While I can’t think of a reason for using this flag, I’m sure one will come up eventually.
cDialog_dtor Like any other Cybiko SDK object, you must call cDialog’s destructor, which in this case is named cDialog_dtor (shown in Listing 11.2). Listing 11.2 void cDialog_dtor(struct cDialog* ptr_dialog,int mem_flag);
Like all destructor functions, this one takes two parameters (the pointer to the object being destroyed and a memory flag with either the value LEAVE_MEMORY or FREE_MEMORY) and returns no value.
cDialog_ShowModal The function responsible for getting all of the work done for a cDialog object is cDialog_ShowModal. This function is shown in Listing 11.3. Listing 11.3 int cDialog_ShowModal (
struct cDialog * ptr_dialog );
This function takes a pointer to the cDialog object you wish to show modally, and returns an int, which specifies the state in which the cDialog object was exited. Something that might be a little confusing to you is the term “modal.” It does kind of sound like a word from an alien language. Dialogs come in two flavors: modal and modeless. In a modal dialog, you can do nothing else in the program until the dialog
Team LRN
128
Chapter 11: Basic Dialogs
has been taken care of (i.e., push a button or cancel the dialog by pressing Esc). In a modeless dialog, you can do other things outside of the dialog while it is up. Most of the time, you will want simple message boxes and input boxes to be modal. The return value of cDialog_ShowModal is one of a number of mr* constants. The “mr” stands for “modal result.” Table 11.6 lists the predefined modal result constants. Table 11.6 Modal result constants Value
Meaning
mrNone
No result available
mrOk
OK button pressed
mrYes
YES button pressed
mrNo
NO button pressed
mrDraw
DRAW button pressed
mrChat
CHAT button pressed
mrQuit
QUIT button pressed
mrNew
NEW button pressed
mrUpload
UPLOAD button pressed
mrRename
RENAME button pressed
mrDelete
DELETE button pressed
mrView
VIEW button pressed
mrPlay
PLAY button pressed
mrRestart
RESTART button pressed
mrSet
SET button pressed
mrLater
LATER button pressed
mrCancel
CANCEL button pressed
mrMessage
Message received
mrUser
Beginning of the user’s definitions
As you can see, most of these constants correspond to the mb* flags, like mbOk and mrOk, and mbCancel and mrCancel. Each button has its own modal result constant. For now, we aren’t going to worry about mrMessage or mrUser.
cDialog_GetEditText and cDialog_SetEditText If you use the mbEdit flag to construct your dialog, there will be an edit box on it, with a buffer allocated for a string equal to the value passed in the edit_size parameter. Once you have constructed your dialog, you can initialize the string stored along with
Team LRN
Chapter 11: Basic Dialogs
129
the dialog. After you have called cDialog_ShowModal, you can then move whatever is stored in that string into another buffer before destroying the dialog. To set the text in the edit box, you use the function cDialog_SetEditText. This function is shown in Listing 11.4. To retrieve the text from the edit box, you use the function cDialog_GetEditText, shown in Listing 11.5. Listing 11.4 void
cDialog_SetEditText (struct cDialog* ptr_dialog, char* text)
This function returns no value and takes two parameters. The first parameter is a pointer to the cDialog object for which you are setting the edit text. The second parameter is a pointer to a string that you wish to be copied into the cDialog’s edit box. Listing 11.5 void
cDialog_GetEditText (struct cDialog* ptr_dialog, char* ptr_buffer)
This function returns no value and takes two parameters. The first parameter is a pointer to the cDialog object from which you are retrieving the edit text. The second parameter is a pointer to a string buffer into which you want the edit text to be copied.
Simple Message Box Now that we have spent some time becoming familiar with the cDialog class, it is time to put our knowledge into effect. We shall start by making a simple message box. Since we might make a message box for many different occasions, we will use the ubiquitous “Are you sure you want to quit (y/n)?” message box that seems to be the standard on every application written since time began. I’m going to present this task two ways. The first way is the “Cybiko way,” and is the way you will see it done in every sample Cybiko application in the SDK. The second way is “My Way,” which really isn’t that different other than I wrap it up inside a function so you only have to write one line instead of several. Either way, the steps are still the same: 1.
Construct a dialog with the appropriate values for parameters.
2.
Show the dialog modally and save the return value.
3.
Destroy the dialog.
4.
Take appropriate action based on the value returned from cDialog_ShowModal.
The Cybiko Way The message box demo can be found on the companion CD in CyBk11_1. This program is based on CyBk6_2; the only thing I changed was the OnKeyDown function, which is shown in Listing 11.6.
Team LRN
130
Chapter 11: Basic Dialogs
Listing 11.6 bool OnKeyDown(int scancode,int mask,char ch) { struct Message* ptr_msg; //pointer to a message struct cDialog* ptr_dlg; //pointer to a dialog int mrResult; //modal result //if the escape key has been pressed, quit if(scancode==KEY_ESC) { //allocate dialog ptr_dlg=(struct cDialog*)malloc(sizeof(struct cDialog)); //construct the dialog cDialog_ctor(ptr_dlg,NULL,"Are you sure you want to quit?",mbYes | mbNo,0,main_module.m_process); //show the dialog modally mrResult=cDialog_ShowModal(ptr_dlg); //destroy the dialog cDialog_dtor(ptr_dlg,FREE_MEMORY); //examine the result returned by the dialog if(mrResult==mrYes) //check for mrYes { //create a new message ptr_msg=Message_new(sizeof(struct Message)); //make it a quit message ptr_msg–>msgid=MSG_QUIT; //send this message to the message queue Message_post(ptr_msg,cWinApp_get_name(main_module.m_process), get_own_id()); } //repaint the screen OnPaint(); //the message has been handled, so return true return(TRUE); } //if message hasn't been handled, return FALSE return(FALSE); }
The code should be pretty easy to follow. I just went through each of the steps I outlined earlier. If the result stored in mrResult is equal to mrYes, I send a MSG_QUIT to the application, which shuts it down. In any other case, I call OnPaint, which restores
Team LRN
Chapter 11: Basic Dialogs
131
the old image to the screen (otherwise, there would be a residual dialog image still on the screen, since the program still does essentially nothing). Figure 11.1 shows what this message box looks like. Figure 11.1: A simple message box
My Way The downside (at least in my opinion) to creating dialogs the Cybiko way is that it takes three lines of code, and two of them are exactly the same no matter what your message box looks like. The only difference is in the call to the constructor. It seems to me that we would be much better off just having a single function with the necessary parameters to make a simple message box that will create a cDialog object and return whatever value we retrieve from it. That way, we can do a message box with a single line of code, and worry about the return value afterward. So, submitted for your approval, I present you with the MessageBox function, shown in Listing 11.7. Listing 11.7 int MessageBox( char *title, //(in)title of the message box char *message, //(in)message for this message box long style, //(in)style (combination of mbXXXX constants) struct cWinApp *ptr_win_app //(in)pointer to application(main_module.m_process) ) { //pointer only, cut down on stack space struct cDialog* pDialog; //return value(modal result) int mr; //allocate dialog pDialog=(struct cDialog*)malloc(sizeof(struct cDialog)); //construct dialog cDialog_ctor(pDialog,title,message,style,0,ptr_win_app); //show the dialog modally mr=cDialog_ShowModal(pDialog);
Team LRN
132
Chapter 11: Basic Dialogs
//destroy dialog cDialog_dtor(pDialog,FREE_MEMORY); //return the modal result return(mr); }
This function takes four parameters: the title, the message, the style, and a pointer to an application for message reading. It returns whatever modal result is returned from the dialog that is created within the function. Notice that edit_size is not one of the parameters. With just a simple message box, there will never be an edit box so this parameter is unnecessary. (In the function itself, the value of zero is always passed to cDialog_ctor for this parameter.)
Simple Input Box The other primary use for the cDialog class is for creating simple input boxes. A simple input box is just like a simple message box, except for the inclusion of an edit box for retrieving some sort of textual response from the user. There are plenty of uses for input boxes, including logins, passwords, and any other small piece of text information. For more involved input, you would want to use a different kind of form, which we will explore in a later chapter.
The Cybiko Way The input box example can be found in CyBk11_2 on the companion CD. This program is based on CyBk11_1. I added functionality so that when the N key is pressed and released, an input box asking for your name is displayed. You can change the name in the edit box. When you hit the OK button, it will be saved in a global variable called szName. If you hit the Esc key or press the CANCEL button, the name will not be saved. To add this input box, the only function I changed was the OnKeyUp function, which is shown in Listing 11.8. Listing 11.8 bool OnKeyUp(int scancode,int mask,char ch) { struct cDialog* ptr_dlg;//pointer to a dialog int mrResult;//modal result if(scancode==KEY_N) { //allocate dialog ptr_dlg=(struct cDialog*)malloc(sizeof(struct cDialog));
Team LRN
Chapter 11: Basic Dialogs
133
//construct the dialog cDialog_ctor(ptr_dlg,NULL,"What is your name?",mbOk | mbCancel | mbEdit,20,main_module.m_process); //set the edit text of dialog cDialog_SetEditText(ptr_dlg,szName); //show the dialog modally mrResult=cDialog_ShowModal(ptr_dlg); //check the modal result for mrOk if(mrResult==mrOk) { //copy the edit text cDialog_GetEditText(ptr_dlg,szName); } //destroy the dialog cDialog_dtor(ptr_dlg,FREE_MEMORY); //repaint the display OnPaint(); //message handled, so return TRUE return(TRUE); } //if message hasn't been handled, return FALSE return(FALSE); }
This code is rather similar to the code for a simple message box with a few additions. First, calls to cDialog_SetEditText and cDialog_GetEditText occur before and after the call to cDialog_ShowModal, respectively. Second, the code checks the value of mrResult before the destructor is called, and the edit text is only copied into the buffer if cDialog_ShowModal returns mrOk. Figure 11.2 shows a snapshot of the input box in action. Figure 11.2: A simple input box
My Way Again, in my opinion, the Cybiko way takes too many lines of code, so I wrap this up into a single function called InputBox. This function is shown in Listing 11.9.
Team LRN
134
Chapter 11: Basic Dialogs
Listing 11.9 //input box function(returns a modal result (mrXXXX constant) int InputBox( char* title, //(in)title of the input box char* message, //(in)message for this input box long style, //(in)style (combination of mbXXXX constants) int edit_size, //(in)max length of the string for input box char* edit_text, //(in/out)input box text struct cWinApp *ptr_win_app //(in)pointer to application(main_module.m_process) ) { //pointer only, cut down on stack space struct cDialog* pDialog; //return value(modal result) int mr; //allocate dialog pDialog=(struct cDialog*)malloc(sizeof(struct cDialog)); //construct dialog cDialog_ctor(pDialog,title,message,style | mbEdit,edit_size,ptr_win_app); //set the edit text cDialog_SetEditText(pDialog,edit_text); //show the dialog modally mr=cDialog_ShowModal(pDialog); //retrieve the edit text cDialog_GetEditText(pDialog,edit_text); //destroy dialog cDialog_dtor(pDialog,FREE_MEMORY); //return the modal result return(mr); }
This function is a lot like my MessageBox function. You specify the title, message, and style in the first three parameters. The next two parameters are a text buffer for initializing and saving the results of the edit box text and the size of that buffer. The last parameter is a pointer to an application (main_module.m_process) so the input box can retrieve messages. The function returns the mr* constant returned from the cDialog_ShowModal call.
Team LRN
Chapter 11: Basic Dialogs
135
Interface Standards Now for a word about interface standards. Cybiko, Inc., has drawn up a number of standards, including messages, dialog boxes, and input boxes that should be used for many common tasks, to keep the same look and feel across all Cybiko applications and games. These are listed in the Cybiko SDK help files, and you should give them a good read. Neither I nor Cybiko, Inc., can make you follow these standards, but I strongly suggest that you use them.
Summary At this point, you should now be able to make message boxes and input boxes for any occasion. These tasks may seem trivial right now, but believe me, they are very important when it comes to polishing up your applications and games. Later on, we will be revisiting the Cybiko UI classes in greater detail. I just wanted you to be familiar with the cDialog class because it is used with such frequency that you practically cannot avoid using it. Next up, we’re going to take a look at a few miscellaneous Cybiko programming basics. Then, we’re going to make an actual game with just the basic stuff we’ve covered thus far.
Team LRN
Chapter 12
Some Miscellaneous Basics Overview At this moment, you are very close to having enough knowledge to make a simple Cybiko application or game. There are just a couple of little things that don’t really belong in any other category that need to be covered just to round out the body of knowledge we have already discussed. This will be a short chapter, and then I promise we’ll make a game.
Input Files We have talked about bitmaps and music sequences, and how to load them from resources. Later, we will talk about more things dealing with loading and saving these objects to files, but just being able to load them in is enough to get started with them. However, not all information your program will need will necessarily be in the form of a bitmap or music sequence. You will have level files, text files that are shown between levels, and various other types of miscellaneous files that your program needs to read. In order to do that, we need to be able to generically open a resource or external file and read them in. There are two different classes dealing with binary input. The classes are Input and FileInput. Both of these classes will work for most of what you want to do. FileInput just adds some functionality to allow you to specify filenames in the constructor, and also gives a function to open files. However, Input does all of the actual reading, and the corresponding FileInput functions are nothing more than #defines that use the Input functions. To read information from resources, we will make use of the Input class. To read from external files, we will use the FileInput class. There is really very little difference in the code for each other than what class is used.
136
Team LRN
Chapter 12: Some Miscellaneous Basics
137
Opening a Resource For opening resources, use the Input class. There are two functions that you can use to open a resource and return a pointer to an Input object. There is no Input constructor, so these functions are all you have to open up resources for input. The functions are shown in Listing 12.1. Listing 12.1 struct Input* struct Input*
open_resource (int index) ; open_resource_Ex (char* sz_file_name);
Both of these functions return a pointer to an Input object. The open_resource function takes an index into the resource list. Indices start at 0 for the first resource in the app file, and go up from there. The open_resource_Ex function takes the name of the resource you wish to open. In most cases, I wouldn’t suggest using open_resource since the resource number can change from one build to another and it makes maintaining your code more difficult. The only time you would want to use it is if you had many resources and did not store the filenames in the application archive (using some of the more advanced features of Filer.exe). Each resource’s name is stored in your application archive, and having many resources will cause more space to be taken up, and not storing the name might just shave off enough bytes in the archive to allow you to fit some more stuff in there.
Opening an External File For opening external files, use the FileInput class. Unlike the Input class, FileInput has constructors. These constructors are shown in Listing 12.2. Listing 12.2 struct FileInput* struct FileInput*
FileInput_ctor (struct FileInput* ptr_file_input) ; FileInput_ctor_Ex (struct FileInput* ptr_file_input, char* sz_file_name);
Both of these functions return pointers to the newly constructed FileInput object. The first parameter (the only parameter in the case of FileInput_ctor) is a pointer to the object you wish to construct. The second parameter of FileInput_ctor_Ex is a string that tells the constructor which file you wish to open. FileInput_ctor constructs a FileInput object, but does not open a file. If you use this function to construct your FileInput object, you will need to use the FileInput_ open function (shown in Listing 12.3) to actually open the file. Listing 12.3 bool FileInput_open (struct FileInput* ptr_file_input, char* sz_file_name);
This function returns TRUE or FALSE, indicating whether or not the file was successfully opened. The first parameter is a pointer to a FileInput object, and the second parameter is a string that tells what filename you wish to open.
Team LRN
138
Chapter 12: Some Miscellaneous Basics
Destroying an Input or FileInput Object To destroy an Input or FileInput object, you use the destructor Input_dtor or FileInput_dtor, respectively. These destructors are just like any other destructor, and you should be used to using them by now. In all cases, opening an Input object with open_resource or open_resource_Ex will require the FREE_MEMORY flag to be passed to the destructor. The FileInput object may or may not be dynamically allocated, so you should use the appropriate memory flag when destroying them.
Retrieving Information about an Input Stream There are plenty of times when you need some information about an Input or FileInput object, including how big it is, what position it is at, whether you are at the end of the stream, if the stream is bad, and so on. The information retrieval functions for the Input class are shown in Listing 12.4. The information retrieval functions for FileInput are shown in Listing 12.5. Listing 12.4 long Input_tellg (struct Input* ptr_input); long Input_get_size (struct Input* ptr_input); int Input_get_flags (struct Input* ptr_input) ; bool Input_is_eof (struct Input* ptr_input) ; bool Input_is_bad (struct Input* ptr_input) ; bool Input_is_good (struct Input* ptr_input);
Listing 12.5 long FileInput_tell (struct FileInput* ptr_file_input); long FileInput_tellg (struct FileInput* ptr_file_input); long FileInput_get_size (struct FileInput* ptr_file_input); int FileInput_get_flags (struct FileInput* ptr_file_input); bool FileInput_is_eof (struct FileInput* ptr_file_input); bool FileInput_is_bad (struct FileInput* ptr_file_input); bool FileInput_is_good (struct FileInput* ptr_file_input);
Even though the function names are pretty self-explanatory, I’m going to explain them anyway. Input_tellg, FileInput_tellg, and FileInput_tell all return the current position within the input stream. If the stream’s position cannot be determined, it returns –1. Input_get_size and FileInput_get_size both return the size of their respective objects. If the size cannot be determined, it returns a –1. Input_get_flags and FileInput_get_flags return an int that contains a number of flags that give information about the file. There are three different flags, FLAG_EOF, FLAG_BAD, and FLAG_EXCEPTIONS. You won’t have to worry about FLAG_EXCEPTIONS, and retrieving the values of FLAG_EOF and FLAG_BAD can be done through other functions. Input_is_eof and FileInput_is_eof check the flags to see if FLAG_EOF is set. This indicates that you are at the end of the stream.
Team LRN
Chapter 12: Some Miscellaneous Basics
139
Input_is_bad and FileInput_is_bad check the flags to see if FLAG_BAD is set. A stream is bad if it is invalid or otherwise corrupted. You probably won’t ever use this function. Input_is_good and FileInput_is_good check to make sure that FLAG_BAD is not set. Again, you are unlikely to use this function.
Reading from an Input Stream It wouldn’t be an input stream unless we wanted to read something from it, now would it? Each of the Input and FileInput classes can read data in four ways. The four functions are shown in Listing 12.6. Listing 12.6 long Input_read (struct Input* ptr_input, void* buffer, long length); int Input_read_byte (struct Input* ptr_input); short Input_read_word (struct Input* ptr_input); long Input_read_dword (struct Input* ptr_input);
The functions for FileInput are the same, except FileInput replaces Input. Technically, the Input functions will work for a FileInput object, but mixing names like this tends to make code more confusing, so when using a FileInput object, use the FileInput version of the functions. With Input_read_byte, you can read a single byte. With Input_read_word, you can read a word, or two bytes. With Input_read_dword, you can read a double word, or four bytes. For sizes other than 1, 2, and 4 bytes, you can just use Input_read, which allows you to read a variable number of bytes (specified by the length parameter) into a buffer (specified by the buffer parameter). Input_read returns the number of bytes actually read from the file.
Maneuvering through an Input Stream Many times, you will want to input from a stream sequentially, meaning you read it in once, from start to end, without skipping over parts or moving back to other parts of the file. Other times, you may wish to move to a specific part of the file or resource, and read data from there. This is called random access, which is rather a misnomer, since it has nothing to do with randomness. You can move to any part of the file or resource at any time, using Input_seekg, FileInput_seekg, or FileInput_seek. These three functions are exactly the same and can be used interchangeably. The syntax for Input_seekg is shown in Listing 12.7. Listing 12.7 long
Input_seekg (struct Input* ptr_input, long pos, seek_t mode);
The syntax for the other two functions is exactly the same other than the name and the first parameter, so I will narrow my discussion to just Input_seekg, but keep in mind that everything I say here applies to the other functions as well.
Team LRN
140
Chapter 12: Some Miscellaneous Basics
The first parameter is the Input or FileInput object of which you are changing the stream position. The second parameter is either an absolute or relative position to which you wish the stream position to move. The third parameter is a “seek mode,” which changes the meaning of pos depending on its value. There are three seek modes: SEEK_SET, SEEK_CUR, and SEEK_END. If you use SEEK_SET, the stream position is changed to be the absolute position in the stream based on the beginning of the file, and you should only specify positive positions. If, for example, you read the 10th byte of a file, you would use 10 as pos and SEEK_SET as the seek mode, and then read from there. If you use SEEK_CUR, the stream position is changed relative to the current position. If pos is a positive value, it moves the current position toward the end of the stream. If pos is negative, the position moves closer to the beginning of the file. If you wanted to move 10 bytes ahead of the current position, you would use a pos of 10 and a seek mode of SEEK_CUR. If you wanted to move 10 bytes backward, you would instead use –10 in the pos parameter. Finally, SEEK_END references the end of the file, and only non-positive values should be used. If you wanted to move to the last 10 bytes of a stream, you would put a 10 in pos and use SEEK_END. Input_seekg returns a long. This is the new absolute position in the file, or –1 if the operation for some reason failed. There is now nothing you can’t do as far as reading in files to a Cybiko program. I have covered every single function dealing with the Input and FileInput classes. Later, when we get to Output and FileOutput, you will be reminded of Input and FileInput, because the output stream functions are just the opposite of the input stream functions.
The Wait Icon You have, I’m certain, noticed the spinning Cybiko icon that shows up whenever a lengthy process has begun, such as loading in a file or starting an application. You can also make use of this feature, with a simple function call to set_hourglass, shown in Listing 12.8. Listing 12.8 void set_hourglass (
bool enable );
To turn on the wait icon, you call set_hourglass with TRUE. To turn it off, call it with FALSE. Be warned: using the wait icon starts a special thread that takes care of rendering and spinning, so there is a slight performance hit when using it. However, having your Cybiko do nothing and look like it is locked up during a long operation isn’t a good idea. Anytime something takes longer than a second or two, you probably want to use the wait icon, regardless of the performance implications.
Team LRN
Chapter 12: Some Miscellaneous Basics
141
Summary Now you’ve got the goods on input streams and wait icons. You have enough knowledge at this point to make a halfway decent Cybiko application or game, as we shall see in the next chapter. There is still quite a bit left to learn, of course, but you now have enough of a solid foundation to get started with some actual fun stuff.
Team LRN
Chapter 13
A Simple Cybiko Game Overview So! Let’s make a game! That is what we’re all here for, after all. In this chapter, I’m going to take you through all of the steps required to make a game, just in case you’ve never made one before. It’s not like any other kind of programming on the planet. The game I’m going to show you is rather simple, but it does have all of the elements required in a game of any size.
Game Theory It is said that the human race should not be judged by its acts of good or evil, but by the games it plays, i.e., what we do in our leisure time. Indeed, games have fascinated many people for millenia. Every culture plays games, from tribes in Africa playing mancala to someone playing Quake III. The Cybiko was built for the primary purpose of playing games. Sure, there are other applications that you can find for this hand-held little wonder, but by and large, it’s the games you want. So, what is a game? What purpose do they serve?
What is a Game? A game consists of two things: game states and game mechanics. These are interrelated concepts. A game state is just a set of information that describes the current configuration of the game. For example, in chess, the initial game state is where the pieces are set up on an 8x8 grid in the appropriate positions. If you start out with the pieces in any other configuration, you are not playing chess. You are, at most, playing a variation of chess.
142
Team LRN
Chapter 13: A Simple Cybiko Game
143
A game mechanic (also known as a rule) allows you to move from one game state into another, or to remain at the current game state. Game mechanics specify what moves are legal in a given game state and manage things like changing players in a multi-player game. There are a few game states that are special. One is the initial game state, which exists whenever you start a new game. The other is the end game state. In the end game state, the game is either in a state where you can no longer use a game mechanic to move to another state or there is no point in doing so (like a stalemate). Please notice that I have made no mention of “winning” or “losing” yet, because the presence of these states depends on the game you are playing. Not all games have a “win” condition, although many do. All games have a “lose” condition, however. Winning depends on whether the game you are playing is “finite” or “infinite.” An infinite game can theoretically be played forever, with no players ever winning. This is true of most arcade-type games. A finite game will, at some point, end. A good example of this is Reversi. There are 64 squares on a Reversi board; four of them are occupied during the initial game state. During each player’s turn, if possible, another square is occupied. When no more moves are possible (usually when the board is filled up), the game is over. So, Reversi lasts at most 60 turns, and can end sooner. One last category I’m going to talk about is “semi-finite.” Chess is a good example of this. In some circumstances, a game of chess could go on forever, for example when each player only has his king left. Sure, you could both move the king around until kingdom come, but you would never reach an end game state. However, in that circumstance, it is obvious that the game will continue forever with neither player winning, so there are provisions made for such an occurrence—the stalemate or draw, where both players agree that neither of them are going to win or lose.
What Purpose Do Games Serve? This is a tricky question. There have been many pundits who have suggested that people play games to relax. If this is so, why are many games frustrating? We can say that game playing is generally a leisure-time activity, although not in all cases. There are professional football and basketball players who get paid to play the game. There are also tournaments for chess and Reversi and other classic games that have cash prizes. In addition, there are game shows on television where money can be won. Our culture is so inundated with games of various types that they must serve some sort of purpose (according to one theory of sociology). My own theory on this matter is that a game serves the same purpose as a book or a TV show—escapism. Your life might be completely filled with stress from your job, your family, your debts, and so on, but when you play a game, you can forget about all of your concerns and just play a game, or watch a game being played. So, it is entertainment.
Team LRN
144
Chapter 13: A Simple Cybiko Game
Where Do Games Come From? For the most part, modern games are usually just a variation on a theme. They are based on some other game that already exists. Very few games are totally and completely original. As a creator of games, I can tell you that most of my games are either “covers” or “re-dos” of older games, or a variation on an older game where I ask a few “what-if” questions. Game creation is perhaps the most interesting activity that you could ever do. It requires both logic and creativity, and requires that you be creatively logical and logically creative at the same time. Both hemispheres of your brain are engaged in this process. Unfortunately, no one has figured out how to teach creativity (I sure haven’t), so you’re on your own in that respect. A few tips, however: challenge conventional wisdom, think outside of the box, and try to look at things in a completely different way.
Game Design Once you have an idea for a game, you have taken your first small step into the game creation process. I accent the word “small” in the prior sentence because an idea for a game is not a game. It isn’t half of a game. It isn’t even a fourth of a game. The idea is no more than 1% of the game. It is a kernel, a seed, something that you process and work on and make into a game. Take, for example, a nugget of iron ore. A nugget of iron ore is not a sword. First you have to smelt it and purify it, then you need to take the iron and fashion it into something usable, like a sword, and finally, you have to test it to make sure it doesn’t have flaws. Game creation is much like this process. If the idea for a game is the nugget of iron ore, then game design is the smelting. You take your idea, and flesh it out into something that might be usable. Most games fit into some sort of genre: arcade, action, strategy, etc. Determine what genre your game idea best fits into, and look at other games in the same genre. This means comparing and contrasting your idea with other ideas. Look critically at these other games. What do they do that you like or dislike? Ask your friends what they like or dislike about these games. Break the games down into features. Determine whether your game needs those features; if you find yourself adding features to your game just because they are in other games of the same genre, challenge that! Just because game X has a certain feature in it doesn’t mean your game necessarily should.
Game Analysis Case Study: Let us say you are going to do an arcadish game similar to Pac-Man. This is in the arcade/action genre. I’m going to take apart the game of Pac-Man and analyze its features to show you what I mean.
Team LRN
Chapter 13: A Simple Cybiko Game
145
Pac-Man: Most noticably, Pac-Man has its namesake, the little yellow guy who eats dots. Naturally, this guy could be of any color and look like anything you want. This is the “main character,” the avatar for the player of the game. Is he necessary? Well, in order to be playable, the player has to control something, although it need not be the little yellow guy. So, yes, we need the player to control something, but we’re just not sure what yet. Monsters: The next most obvious feature is the monsters. The monsters attempt to eat the Pac-Man. Are the monsters necessary? Not as such, but we do need some sort of adversary that tries to keep the main character from achieving his goal. However, these need not take the form of monsters. The dots: Pac-Man must eat all of the dots on the screen in order to complete the level (we’ll get to the concept of levels in a bit). This is Pac-Man’s main objective, to eat all of the dots. Without a need for Pac-Man to do this, there would be nothing for Pac-Man to do, and no point in playing the game. The objective of the main character need not be dot-eating, but there should be something for the main character to do. Power pellets: On each level, there are four power pellets. When Pac-Man eats one, for a short period of time the tables are turned on the monsters, and instead of them eating him, he can eat them. The idea of power pellets is not essential to the game of Pac-Man. Indeed, the game is playable without them (albeit much more difficult). The main concept behind the power pellets is that Pac-Man gets a brief respite from the monsters. Bonuses: In Pac-Man, you can eat fruit, which gives a boost to your score. We haven’t talked about score yet, but we will in a bit. The fruits are not strictly necessary to the game play. They do, however, give that “little something extra” that enhances the game playing experience, and takes some skill and cunning to acquire, especially when monsters are chasing you. Score: The score in Pac-Man is your measure of success. Pac-Man is an infinite game. With enough skill, you could play it forever, although I think you would die of hunger and dehydration and sleep deprivation first, but it is possible. In an infinite game, you generally do want some sort of success metric like a score. However, it need not actually be a score. You might have a success metric of how long the game lasted, or how many levels you surpassed. Without a success metric, the player doesn’t know how well he did, so he is not interested in doing better next time, since he doesn’t know what “better” means. Levels: The Pac-Man levels are mazes filled with dots, with power pellets at the four corners. Pac-Man must quickly and efficiently navigate this environment while avoiding the monsters. Once a level is completed, he moves on to the next level. This brings up a topic I have not yet discussed: episodic versus progressive games. PacMan is an episodic game. You complete one level, then move on to another level, then another, etc. After a level is complete, you get a small break while the next level is loaded. In a progressive game, like Centipede, there is no break in the action. The screen changes color, the mushrooms move down, and you keep playing. A Pac-Mantype game does not need to be episodic; it could be designed to be progressive.
Team LRN
146
Chapter 13: A Simple Cybiko Game
Lives: The concept of “lives” or “tries” is pretty prevalent in arcade video games. At least one “life” is required. I’m going to talk about the concept of lives not in the context of “should the player have a life” but rather “should the player have multiple lives and should he be able to earn extras.” The style of play is quite different if the player has only one life. He will be less likely to take risks, less likely to go for bonuses, and so on. In a multiple-life scenario, he is more likely to do these things, especially if he can earn extra lives under certain circumstances. So, that’s about it for breaking down Pac-Man. Here’s a list of features that I think make Pac-Man the game it is: 1.
A main character that the player controls (the Pac-Man)
2.
Adversaries that attempt to thwart the objective of the main character (the monsters)
3.
An objective of some kind (dots)
4.
Something that gives a respite from/turns the tables on the adversaries (power pellets)
5.
The bonus fruits, which gives the player an optional objective to demonstrate his skill of playing
6.
A success metric (score)
7.
Episodic play (levels)
8.
Multiple attempts, with extra attempts attainable (lives)
Of these, I rank features 1, 2, 3, and 6 to be essential for any Pac-Manish game. Without a main character, adversaries, an objective, and a success metric, there is no game. The rest of the list is negotiable and/or optional. Here’s my reasoning for both groups. Essential components: n
Without a main character that the player controls, there is no point to have a player, and thus, no game.
n
Without adversaries, the main character can achieve his objective unopposed, ergo no challenge, and therefore no game.
n
Without an objective, the main character has nothing to do, and there is no point to playing the game.
n
Without some sort of success metric, the player does not know how well he did, and therefore cannot do “better,” and this diminishes the sense of achievement, which makes the game pointless.
Non-essential components: n
The game can be played without a respite from the adversaries; it just makes the game more challenging/frustrating.
n
The game can be played without bonuses, although bonuses do add a challenging factor that many players will find enjoyable.
Team LRN
Chapter 13: A Simple Cybiko Game
147
n
The game can be designed to be played progressively rather than being episodic.
n
The game can be played with only a single try rather than multiple tries. This would make the game more challenging/frustrating.
You’ll notice I have used the words “challenging” and “frustrating” several times above. I am now going to speak of game balance. A game that no one wants to play may as well not be a game at all. Players and potential players have to want to play your game, for whatever reason: fun, challenge, etc. In order to keep people playing your game, you must not make the game too difficult or too easy. If the game is too easy, the players will become bored, and stop playing. If the game is too hard, they will become frustrated, and again, stop playing. So, as a game designer, you must make your game challenging but not too challenging, and easy enough but not too easy. This is a hard thing to do, and unfortunately, you cannot do this in the design phase. For this, you have to wait until later in the process. Despite the fact that you cannot make a balanced game in the design phase, you can identify at this point what factors you need to watch that may later turn out to be a problem.
Fleshing Out the Design Once you have a rough idea of what features you want your game to have (like the rather abstract features I talked about with Pac-Man), you can start to detail them. I’m going to continue with the Pac-Man example for now. We’ve determined that we need a main character, adversaries, an objective, and a success metric. What form do these take? What does the main character look like? What actions can he perform? What do the adversaries look like? How numerous are they? What actions can they perform? What is the main character’s objective? How can he use the actions available to him to reach that objective? How will the adversaries try to prevent him from achieving that objective? How do we measure the success of the main character? The “what does X look like” questions aren’t really ones that have to be answered right now. You can wait until later, but it helps if you can visualize the main character and the adversaries. You can just pick something for now and change it later. The “what actions can X perform” should be answered after the “what is the objective” question, since the objective will help make deciding what actions can be performed a little easier. The number of adversaries should be chosen initially, but will probably change throughout the course of the rest of the creation process since it is a balance issue. Too many adversaries will make it too hard, and too few adversaries will make it too easy. Identify potential balance issues as early as you can!
Simulation Make a mockup of the game on some sort of board or graph paper, and test out the major concepts. You can identify a number of balance issues in this step. Alternately,
Team LRN
148
Chapter 13: A Simple Cybiko Game
you can make a quick-and-dirty version of the game (nothing more than a couple of hours should be spent on this) and try it out, with the primary goal not of making the game complete, but testing out whether the game design is solid. Something that sounds good on paper isn’t necessarily going to make the transition to a game. Also, some game designs are just unworkable, so don’t be afraid to trash something or go back and rethink it.
Making the Game When you get through all of the design and simulation stuff, your game concept should be a great deal more specific than when you entered the process. That is, it should be a lot more complicated than, “I want to make a game like Pac-Man.” You should at least have several pages of scribbled notes and little drawings of what you intend to make, if not a formalized design document. Now you have the features you want, all that is left is to implement them. But where to start? This is perhaps one of the hardest things about game programming; it is often not immediately obvious. Unless you have an established base of classes from previous games you have made, you start with nothing. You could start with one of the template applications from Chapter 6, but all that does is give you a blank screen. Bringing that blank screen to life is your task, and it isn’t easy. The first thing you might think to do is make the main character. Fair enough. That was the first thing that occurred to me as well. Let us examine our main character. First, he can move in four directions—up, down, left, and right. I tend to use north, south, west, and east for these directions, but either one will do. I will use the cardinal directions rather than the relative directions because I’m more comfortable doing so. Second, the main character is animated. For each direction, there is a picture with him open mouthed and closed mouthed. Since the main character is just a circle with a piece of pie cut out, the art will be pretty simple. Third, he moves around the screen in whatever direction he is facing until he runs into a wall. These three items tell us a lot about the main character of the game. Here’s a short list of the properties I feel are important to the main character. 1.
Direction he is facing—north, east, south, or west
2.
Position on screen
3.
Mouth open or closed
4.
Images for each of the directions for each of the mouth states
So, to abstract this data, we might declare the code in Listing 13.1.
Team LRN
Chapter 13: A Simple Cybiko Game
149
Listing 13.1 //directions #define DIR_NORTH 0 #define DIR_EAST 1 #define DIR_SOUTH 2 #define DIR_WEST 3 #define DIR_COUNT 4 //mouth constants #define MOUTH_OPEN 0 #define MOUTH_CLOSED 1 #define MOUTH_COUNT 2 //main character struct struct MainCharacter { //direction he is facing int Direction; //position int X,Y; //mouth state int MouthState; //bitmaps struct Bitmap* bmpArray[DIR_COUNT][2]; };
This is a good start. We have all of the required knowledge for our main character represented here. But what about functions for dealing with this stuff? Check out Listing 13.2, which has a number of functions for generically creating, destroying, and manipulating the MainCharacter struct. Listing 13.2 //constructors struct MainCharacter* MainCharacter_ctor(struct MainCharacter* ptr_maincharacter); struct MainCharacter* MainCharacter_ctor_Ex(struct MainCharacter* ptr_maincharacter,int dir,int x,int y,int mouthstate); //destructor struct MainCharacter* Maincharacter_dtor(struct MainCharacter* ptr_maincharacter,int mem_flag); //getters int MainCharacter_get_direction(struct Maincharacter* ptr_maincharacter); int MainCharacter_get_x(struct Maincharacter* ptr_maincharacter); int MainCharacter_get_y(struct Maincharacter* ptr_maincharacter); int MainCharacter_get_mouthstate(struct Maincharacter* ptr_maincharacter);
Team LRN
150
Chapter 13: A Simple Cybiko Game
struct Bitmap* MainCharacter_get_bitmap(struct MainCharacter* ptr_maincharacter,int dir,int mouthstate); //setters void MainCharacter_set_direction(struct MainCharacter* ptr_maincharacter,int dir); void MainCharacter_set_x(struct MainCharacter* ptr_maincharacter,int x); void MainCharacter_set_y(struct MainCharacter* ptr_maincharacter,int y); void MainCharacter_set_mouthstate(struct MainCharacter* ptr_maincharacter,int mouthstate); void MainCharacter_set_bitmap(struct MainCharacter* ptr_maincharacter,int dir,int mouthstate,struct Bitmap* ptr_bitmap);
As you can see, I made two constructors, a destructor, and functions to set and get each of the properties of the MainCharacter object. This is a bare minimum for any class you create. There should always be functions to set and get every property of the object with which you are working. Why? Well, the setters are important for validation reasons. For example, setting a direction other than the ones defined above itsmeaningless. The same is true for the mouth state. At some point, the x and y values will be limited to a given range. For the bitmap setter, we must make certain that the parameters dir and mouthstate are valid values before we assign a bitmap pointer to the array. Similarly, with the getters, we may need validation. In most of the cases here, we do not, except for MainCharacter_get_bitmap, in which we have to validate dir and mouthstate again. For the others that require no validation, we can use a “faux function” instead. NOTE: Technically, even all of the getters need validation, since we might be passing it a bad MainCharacter pointer. We can handle this validation inside of our faux function instead.
Faux Functions Those functions that I am terming “faux functions” are not really functions, but they look like them and are used just like them. Consider the four functions in Listing 13.3. Listing 13.3 int int int int
MainCharacter_get_direction(struct Maincharacter* ptr_maincharacter); MainCharacter_get_x(struct Maincharacter* ptr_maincharacter); MainCharacter_get_y(struct Maincharacter* ptr_maincharacter); MainCharacter_get_mouthstate(struct Maincharacter* ptr_maincharacter);
There is virtually no need for validation here since the only parameter is the pointer to the object of which we are retrieving the value. Instead of using MainCharacter_ get_direction (pMainCharacter), we may as well use pMainCharacter–>Direction since using an actual function gives us the added overhead of a function call, and it is less typing to boot! However, one of the pillars of OOP is encapsulation and data
Team LRN
Chapter 13: A Simple Cybiko Game
151
hiding, so we generally do not want to be accessing members of objects in any form other than functions whenever possible. But, since it is less efficient to use a function to retrieve this data, how can we as programmers properly do data hiding? Well, in a constrained environment like the Cybiko, you don’t want to have more functions than you need, so you make a #define that will fake a function. This is what I mean by “faux function.” Consider Listing 13.4, which shows the four faux function equivalents of the functions in Listing 13.3. Listing 13.4 #define #define #define #define
MainCharacter_get_direction(ptr) ( (ptr) != NULL : (ptr)–>Direction : –1 ) MainCharacter_get_x(ptr) ( (ptr) != NULL : (ptr)–>X : –1 ) MainCharacter_get_y(ptr) ( (ptr) != NULL : (ptr)–>Y : –1 ) MainCharacter_get_mouthstate(ptr) ( (ptr) != NULL : (ptr)–>MouthState : –1 )
There we go! We’ve got all of our getter functions without adding the overhead of an actual function call. In each of these, if the pointer we pass into the function is NULL, we will get a negative one, so we have a limited amount of validation in effect. Plus, we are four functions fewer now. This is a mixed blessing. If these faux functions get used a great deal, the application will be larger since the code in them is placed anywhere they are used, making the application larger than it needs to be. On the plus side, since we are not calling an actual function, we will get a small speed increase, although probably not enough to be noticable.
Extending the MainCharacter Class While we can now do any operations on MainCharacter objects that we would ever want, it really doesn’t help us, because nothing is really very automated. In order to move a MainCharacter object, we must first find out where he currently is, move him, check his mouth state, and change the mouth state. Like I said . . . not very helpful. Also, while we have a nice array of bitmaps, we still have to retrieve his direction and mouth states, then we must retrieve the Bitmap object for that condition, then retrieve his x and y position, then render the bitmap. Listing 13.5 shows another list of functions that you will probably find useful for the MainCharacter class. Listing 13.5 //retrieves the next x and y coordinates of the main character int MainCharacter_get_next_x(struct MainCharacter* ptr_maincharacter); int MainCharacter_get_next_y(struct MainCharacter* ptr_maincharacter); //moves the main character void MainCharacter_move(struct MainCharacter* ptr_maincharacter,bool togglemouth,bool updateposition); //toggles the mouth state int MainCharacter_toggle_mouth(struct MainCharacter* ptr_maincharacter); //draws the main character
Team LRN
152
Chapter 13: A Simple Cybiko Game
void Graphics_draw_maincharacter(struct Graphics* ptr_gfx,struct Maincharacter* ptr_maincharacter); #define DisplayGraphics_draw_maincharacter Graphics_draw_maincharacter
Some explanation is in order. I’ve got two functions here that will tell me the next x and y position of the main character, based on his current direction. This is because I’m thinking ahead. In some circumstances, we may need this information to determine whether or not the main character is about to collide with something, like the walls, or a monster, or a dot, or a power pellet. Also, you’ll notice the last two functions, which are functions dealing with the Graphics class and the DisplayGraphics class, as well as the MainCharacter class. Why did I choose to make a Graphics_draw_maincharacter class rather than a MainCharacter_draw class? For this, I looked to other, already existing Cybiko classes, namely the Bitmap class. There is no Bitmap_draw function, but there is a Graphics_draw_bitmap. So, I have concluded that anything that should be drawn should be a Graphics function, not a function of the object being drawn. I focus on the destination, not the source, to keep my own code base consistent with how the Cybiko SDK works.
The Monster Class I’m going to leave the MainCharacter class alone for a while. Other than a few faux functions, there is no implementation for its various functions at this point. This is because I know something that will cause me to change this later. I’ll be getting to it momentarily. Now we move on to the Monster class. We give the monsters the same sort of consideration we gave the main character, picking out properties that we think are important to have in a monster. First, a monster can move in four directions. This is normally indicated somehow on the image being displayed to represent the monster. In the original, his eyes pointed in the direction he was going. Second, a monster is animated. In the original, the lower edge of the monster would move a bit. This makes the monster seem alive, and it’s probably something we want to do here. Third, a monster moves around the screen, usually attempting to eat the main character. Fourth, in reaction to the main character eating a power pellet, the monster will move away from the main character. Fifth, if he is eaten by the main character, the image changes to just his eyes, and he seeks to move to home base as quickly as possible. The image of the eyes still points in the direction he is going. Based on these items, here is a list of the properties that a monster class will need. 1.
Direction of motion
Team LRN
Chapter 13: A Simple Cybiko Game
153
2.
Position on the screen
3.
Current animation frame
4.
Some sort of indication that a power pellet has been eaten, as well as a timer for how much longer the power pellet will be in effect.
5.
Whether or not the monster has been “eaten,” and must now seek home base.
6.
Images for both animation frames as well as the directions of the eyes.
You should notice some similarities between the monster and the main character. Both of these have a direction and a position. They also both have animation, although this animation takes a slightly different form. In Listing 13.6, there is a quick struct to contain all of the information for a Monster object (except for the power pellet status, which I’ll handle outside of the class). Listing 13.6 struct Monster { int Direction; int X,Y; int Frame; bool Eaten; struct Bitmap* bmpEyes[DIR_COUNT]; struct Bitmap* bmpIcon; };
//direction monster is facing //position of the monster //current animation frame of the monster //whether or not the monster has been “eaten” //images for the eyes //bitmap for the image, used when not eaten
We could continue on from here, making all of the functions for dealing with the Monster class, but since there is a significant overlap between Monster and MainCharacter (namely Direction, X, and Y), we definitely want a base class.
Base Class for Monster and MainCharacter Since both monsters and the main character have a position and a direction, it only makes sense that they come from the same base class. I talked about this a bit in Chapter 5, but now I’m actually going to give an example of doing it. First, we are dealing with only the properties common to both classes: Direction, X, and Y. No other information will be used, and no other code should be made. Listing 13.7 shows the struct and functions for what I am calling the Character class. Listing 13.7 struct Character { int Direction;//direction the character is facing int X,Y;//position of the character }; //constructors struct Character* Character_ctor(struct Character* ptr_chr);
Team LRN
154
Chapter 13: A Simple Cybiko Game
struct Character* Character_ctor_Ex(struct Character* ptr_chr,int dir,int x,int y); //destructor void Character_dtor(struct Character* ptr_chr,int mem_flag); //getters int Character_get_direction(struct Character* ptr_chr); int Character_get_x(struct Character* ptr_chr); int Character_get_y(struct Character* ptr_chr); //setters void Character_set_direction(struct Character* ptr_chr,int dir); void Character_set_x(struct Character* ptr_chr,int x); void Character_set_y(struct Character* ptr_chr,int y);
Since this class is abstract, all of the getters and setters should be made into faux functions, since we cannot tell what values are valid for Direction, X, or Y for this class. That is up to derived classes to determine. In fact, we aren’t even going to use validation for direction, because then we could use the Character class as a base class for objects that may have four directions, six direction, eight directions, or even more. We won’t be making any objects of the Character class anyway.
Deriving MainCharacter from Character Now that we’ve got a base class, we can redesign our MainCharacter class and Monster class to make use of this. Listing 13.8 shows the new declarations for the MainCharacter class, including all functions. Listing 13.8 //main character struct struct MainCharacter: public Character { //mouth state int MouthState; //bitmaps struct Bitmap* bmpArray[DIR_COUNT][2]; }; //constructors struct MainCharacter* MainCharacter_ctor(struct MainCharacter* ptr_maincharacter); struct MainCharacter* MainCharacter_ctor_Ex(struct MainCharacter* ptr_maincharacter,int dir,int x,int y,int mouthstate); //destructor struct MainCharacter* Maincharacter_dtor(struct MainCharacter* ptr_maincharacter,int mem_flag); //getters #define MainCharacter_get_direction Character_get_direction
Team LRN
Chapter 13: A Simple Cybiko Game
155
#define MainCharacter_get_x Character_get_x #define MainCharacter_get_y Character_get_y int MainCharacter_get_mouthstate(struct Maincharacter* ptr_maincharacter); struct Bitmap* MainCharacter_get_bitmap(struct MainCharacter* ptr_maincharacter,int dir,int mouthstate); //setters void MainCharacter_set_direction(struct MainCharacter* ptr_maincharacter,int dir); void MainCharacter_set_x(struct MainCharacter* ptr_maincharacter,int x); void MainCharacter_set_y(struct MainCharacter* ptr_maincharacter,int y); void MainCharacter_set_mouthstate(struct MainCharacter* ptr_maincharacter,int mouthstate); void MainCharacter_set_bitmap(struct MainCharacter* ptr_maincharacter,int dir,int mouthstate,struct Bitmap* ptr_bitmap); //retrieves the next x and y coordinates of the main character int MainCharacter_get_next_x(struct MainCharacter* ptr_maincharacter); int MainCharacter_get_next_y(struct MainCharacter* ptr_maincharacter); //moves the main character void MainCharacter_move(struct MainCharacter* ptr_maincharacter,bool togglemouth,bool updateposition); //toggles the mouth state int MainCharacter_toggle_mouth(struct MainCharacter* ptr_maincharacter); //draws the main character void Graphics_draw_maincharacter(struct Graphics* ptr_gfx,struct Maincharacter* ptr_maincharacter); #define DisplayGraphics_draw_maincharacter Graphics_draw_maincharacter
I put in bold some of the changes so that you can pick them out a bit better. Notice the use of “: public Character” on the line where we declare the struct MainCharacter. This is an object-oriented extension of the Cybiko C compiler that makes it act in some ways like C++. If you are not familiar with C++, this little piece of code just tells the Cybiko compiler that struct MainCharacter has all of the same data members as struct Character in addition to those we are adding in the remainder of the definition of MainCharacter. Also, this allows us to use a struct MainCharacter* any place where we can use a struct Character*, which means we can get away with fewer functions, since we will reuse as many of the Character functions as we can, and I did just that with all of the getter functions.
MainCharacter Implementation Check out the program found in CyBk13_1 on the companion CD. In this example, I have implemented a portion of the functionality needed for the Pac-Man clone we’ve been discussing. There are a number of files rather than just one. There is a header and source file for the Character class, a header and a source file for the MainCharacter class, and the main source file, CyBk13_1.c.
Team LRN
156
Chapter 13: A Simple Cybiko Game
I implemented all of the functions for the Character class and the MainCharacter class. The program itself allows you to move the Pac-Man guy around the screen with the arrow keys. At the moment, the program runs rather quickly, which is a good thing, because later on, we will have more stuff to draw, and so it will be slower. None of my code is, at this point, very optimized, with the exception of faux functions for most of the getters and some of the setters. My main goal in showing you the program at this point is to see the testing progress. Each class should be thoroughly tested before moving on to another class. Sure, we may return to this class a little later on and add some things, but whatever you have at the moment should work. Working on several classes at the same time will quickly bog you down, burn you out, and make you want to never finish the game. Since we only get credit for finished games, this is a Bad Thing. In addition, we now have two classes that we might be able to reuse later. I admit that the MainCharacter class is unlikely to ever be reused, but the Character class could quite likely be reused.
Monster Implementation The implementation for the Monster class is in many ways similar to the implementation for the MainCharacter class. In CyBk13_2 on the companion CD, you can find a test-bed application for the Monster class. The controls for this program are similar to those in CyBk13_1, with a few additions. You can turn on the power pellet counter by pressing P. This will set the power pellet counter to 100, so the monster will flash for a while. Also, by pressing E, you can switch the monster from “eaten” to “non-eaten” and vice versa. This test bed is meant to try out all of the features inherent to the Monster class, and it does that nicely. If you look closely at the code from both the MainCharacter class and the Monster class, you will see that there is a bit of duplication, like in MainCharacter_set_direction and Monster_set_direction, which for all intents and purposes have the same code with some variable names differing. Also, functions like MainCharacter_get_next_x and Monster_get_next_x are rather the same. If we were being a bit more careful about the design, we might make another class that inherits from Character, but is inherited from by MainCharacter and Monster. I’m not going to do that here, but in a larger project, I would definitely consider it.
Levels So, we’ve now got components for the moving things in our games. They aren’t quite done yet since we haven’t figured out how they interact with the environment of the game, but they are at a state where we can let them be for now and bring them back up later.
Team LRN
Chapter 13: A Simple Cybiko Game
157
Now on to the environment itself—the level. What is the nature of the environment in which we find our main character and our monsters? Again, let us look to the original game we are basing our game on. In the original, the level was a maze, populated by the monsters and main character who moved around. We’ve got the mobile components made. Also, there are dots and power pellets strewn throughout this environment and a home base for the monsters to go to when they are eaten. The environment itself consists of passages and walls, and that is what we are now going to decide how to structure. The easiest way I can think of to structure such an environment is to divide it up into rectangular cells. Each cell can have walls on the north, east, south, and west sides, indicating that the main character or the monster cannot move in that direction from that cell. Since our main character and monsters are each 8x8 pixels in size, we shall make our cells 8x8 pixels as well. This will give us 20 cells across the screen and 12 cells down. I know that in the original, the screen was taller than it was wide, but due to what we are making the game on, we will reverse width and height, and the game will still have fundamentally the same gameplay. I immediately see two ways to model these cells in software. We could store the status of each wall for each cell, something kind of like Listing 13.9. Listing 13.9 struct LevelCell { bool Wall[DIR_COUNT]; };
Each wall of each cell would be governed by a TRUE or FALSE in the Wall array of the LevelCell class. This is one way of doing it. There is a small problem with this: consider two adjacent cells, stacked vertically so that one cell is directly south of the other cell. In order for the northern cell to have a wall on the south, the southern cell must also have a wall to the north, or the wall will be a “one-way” wall, where you could go through it one direction but not the other. This may not actually be a problem; it can add challenge to the game and make for interesting levels. On the other hand, you may not want this in your game. Another way to model the cells realizes that there are shared walls between cells, so we could just keep track of the walls between cells, rather than the cells themselves. With 20 cells across, we have 21 vertical positions per row where a wall might be. If we assume the edge of the screen will always have walls, this number is only 19. Similarly with 12 cells vertically, we have 13 horizontal wall positions per column, or 11 if we assume the edges are always walled off. With this scenario, we don’t have to have a LevelCell class at all; we just model the levels to have an array of horizontal walls and vertical walls, like in Listing 13.10.
Team LRN
158
Chapter 13: A Simple Cybiko Game
Listing 13.10 #define LEVEL_WIDTH 20 #define LEVEL_HEIGHT 12 struct Level { bool VerticalWall[LEVEL_WIDTH+1][LEVEL_HEIGHT]; bool HorizontalWall[LEVEL_WIDTH][LEVEL_HEIGHT+1]; };
Keep in mind, the +1 could also be a –1, depending on how you decided to do it. I don’t know about you, but I rather like the idea of having a LevelCell class. So, how can we rectify the two different styles we see here so that we aren’t doubling our data, but in a way that allows us to have LevelCells? One way I thought of was to go ahead and use a LevelCell class, but only have two walls kept in each one—east and south—like in Listing 13.11. Listing 13.11 #define LEVELDIR_EAST 0 #define LEVELDIR_SOUTH 1 #define LEVELDIR_COUNT 2 struct LevelCell { bool Wall[LEVELDIR_COUNT]; }
Now, for the east and south wall of each cell, we use the value in the object. For a north or west wall, we look to the cell to the north or west and get its south or east wall value, respectively. What about the north and west edges of the map? Well, there are two ways you might handle this. One, for the north and west edge, you might assume that the north and west edges always have walls. Two, you might check the cell on the opposite end of the level. For a cell on the left edge, you look at the cell on the right edge, and for a cell on the top, you look at the cell on the bottom to determine whether or not you can move there. This would allow you to pass from one edge of the map to the other if you so desired. This approach makes the most sense to me, so it’s what I’m going to use. Now that we have a LevelCell class, constructing the actual level is easy; it is simply a 2D array of LevelCells, as shown in Listing 13.12. Listing 13.12 #define LEVEL_WIDTH 20 #define LEVEL_HEIGHT 12 struct Level { struct LevelCell lcArray[LEVEL_WIDTH][LEVEL_HEIGHT]; };
Team LRN
Chapter 13: A Simple Cybiko Game
159
That’s all you need to represent a level, with the exception of dots and power pellets, which we’ll take care of in a moment. There is one last thing to talk about concerning the LevelCell and Level classes. We need to somehow indicate the home base of the monsters. We might put a bool member into LevelCell, but since only one LevelCell is the monsters’ home, this creates too many extra variables. We don’t want to waste space. So, instead, we will keep an x,y coordinate of the monsters’ home. This coordinate will reference a LevelCell in the Level object’s array. In CyBk13_3 on the companion CD, I have implemented LevelCell and Level in the source files of the same names. Also, the program itself makes a random level and draws it onto the screen every time Prog_Loop is fired. One thing you’ll probably notice about CyBk13_3 is that it is slow to process the Esc key when it is pressed. Drawing the level takes a considerable amount of time. Because of this, we will want to, if at all possible, avoid redrawing the entire level every frame. Listing 13.13 shows the various LevelCell declarations. Listing 13.13 //defines for walls #define WALL_EAST 0 #define WALL_SOUTH 1 #define WALL_COUNT 2 //levelcell struct struct LevelCell { bool Wall[WALL_COUNT]; struct LevelCell* ptr_north; struct LevelCell* ptr_west; }; //bitmaps for walls struct Bitmap* ptr_bmpLevelCellWall[DIR_COUNT]; //width and height of level cells int LevelCellWidth; int LevelCellHeight; //constructor struct LevelCell* LevelCell_ctor(struct LevelCell* ptr_lvlcell); //destructor void LevelCell_dtor(struct LevelCell* ptr_lvlcell,int mem_flag); //setters void LevelCell_set_wall(struct LevelCell* ptr_lvlcell,int wall,bool wallstatus); void LevelCell_set_bitmap(int direction,struct Bitmap* ptr_bmp); //void LevelCell_set_width(int width); #define LevelCell_set_width(n) LevelCellWidth=(n) //void LevelCell_set_height(int height); #define LevelCell_set_height(n) LevelCellHeight=(n) //void LevelCell_set_north(struct LevelCell* ptr_lvlcell,struct LevelCell* ptr_lvlcellnorth); #define LevelCell_set_north(ptr1,ptr2) (ptr1)–>ptr_north=(ptr2)
Team LRN
160
Chapter 13: A Simple Cybiko Game
//void LevelCell_set_west(struct LevelCell* ptr_lvlcell,struct LevelCell* ptr_lvlcellwest); #define LevelCell_set_west(ptr1,ptr2) (ptr1)–>ptr_west=(ptr2) //getters bool LevelCell_get_wall(struct LevelCell* ptr_lvlcell,int wall); struct Bitmap* LevelCell_get_bitmap(int direction); int LevelCell_get_width(); int LevelCell_get_height(); //void LevelCell_get_north(struct LevelCell* ptr_lvlcell); #define LevelCell_get_north(ptr) (ptr)–>ptr_north //void LevelCell_get_west(struct LevelCell* ptr_lvlcell); #define LevelCell_get_west(ptr) (ptr)–>ptr_west //draw a cell void Graphics_draw_levelcell(struct Graphics* ptr_gfx,struct LevelCell* ptr_lvlcell,int cellx,int celly); #define DisplayGraphics_draw_levelcell Graphics_draw_levelcell
First, let’s look at the struct itself. The LevelCell class has three members: an array of bools for the walls and two pointers to other LevelCell objects for the north and west walls (this is to aid in drawing the LevelCell object). Next, there are some external variables (the Cybiko equivalent of a “static member”): LevelCellWidth, LevelCellHeight, and ptr_bmpLevelCellWall. These keep track of the width, height, and images for all LevelCell objects. Since all LevelCells are the same dimensions and have the same images, it would be wasteful to put this information into each LevelCell object. After the struct and the variables are the constructor and destructor. The constructor makes a blank LevelCell, with all walls set to FALSE, and the north and west pointers set to NULL. The destructor is like any other. Then come the getters and setters. There is a getter and setter for each of the members of the LevelCell class, as well as a getter and setter for the extra variables like LevelCellWidth, LevelCellHeight, and ptr_bmpLevelCellWall. A few of the getters and setters are faux functions. Finally, we have the drawing function, Graphics_draw_levelcell, and a #define for the DisplayGraphics version of that function. My coding style should be, by now, pretty predictable to you by now. Predictable is good. Listing 13.14 contains the declarations for the Level class. Most of the functionality for the level is actually taken care of in the LevelCell class, so there isn’t much here. Listing 13.14 //level defines #define LEVEL_WIDTH #define LEVEL_HEIGHT //level struct struct Level {
20 12
Team LRN
Chapter 13: A Simple Cybiko Game
161
struct LevelCell Cell[LEVEL_WIDTH][LEVEL_HEIGHT]; }; //constructor struct Level* Level_ctor(struct Level* ptr_lvl); //destructor void Level_dtor(struct Level* ptr_lvl,int mem_flag); //getters struct LevelCell* Level_get_cell(struct Level* ptr_lvl,int cellx, int celly); //drawing functions void Graphics_draw_level_section(struct Graphics* ptr_gfx,struct Level* ptr_lvl,int x,int y,int w,int h); void Graphics_draw_level(struct Graphics* ptr_gfx,struct Level* ptr_lvl); #define DisplayGraphics_draw_level_section Graphics_draw_level_section #define DisplayGraphics_draw_level Graphics_draw_level
The Level struct is pretty much as we said it would be—a two-dimensional array of LevelCells. There are only three functions dealing with Level: a constructor, a destructor, and a getter. The rest of the functions deal with drawing either a portion of (Graphics_draw_level_section) or the entire (Graphics_draw_level) level. Also, there are #defines for the DisplayGraphics versions of these functions. So now we have three components that are reusable (technically four, but LevelCell isn’t very useful on its own). We still have to do something about the dots and the power pellets, and then we have to make these things interact. There’s still a bit of work to do, but after all the components are developed you will be startled at how quickly they all come together.
Dots and Power Pellets I am lumping these two things together under the category of “things that don’t move that the main character can eat.” It seems a good enough category name, but it’s too long. So, we’ll refer to dots and power pellets as “non-moving eatables.” For a dot, there is no need to actually have a Bitmap object. It’s a dot; a dot is a pixel, enough said. For a power pellet, I do have some images, two of them. This will attract attention to the power pellets, which is what we want. The questions we are faced with are, how do we record what dots exist, and how do we represent these on our level? At first, we might think that we could put a single dot or power pellet into a LevelCell. This could work, but in my opinion, we need more dots than that. The main character and ghost don’t move an entire LevelCell at a time. Currently, they move a single pixel at a time (which we will change later). Let’s think ahead for a moment as to how the main character and monster are going to interact with the level. Since we have walls, and presumably the walls cannot be walked through (otherwise why have the walls?), they will be forced to move along the horizontal or vertical center of each LevelCell to move to a different LevelCell. Only when in the center of a LevelCell will they be able to change from a vertical motion to a horizontal motion, and then only when the LevelCell does not have a wall
Team LRN
162
Chapter 13: A Simple Cybiko Game
in that direction. Otherwise, the monster and main character will be stuck moving in either a horizontal or vertical line, but may reverse directions if they wish. Since the main character is stuck along the horizontal or vertical centerlines of a LevelCell, it only makes sense that we should place the non-moving eatables there. How far apart are we going to make the dots? We have already considered and discarded the idea that they be one per LevelCell, which would be 8 pixels away from one another. Well, since we are on a base unit of 8, and we definitely want a nice even spread of dots on the board, we should take a factor of 8. The factors of 8 are 1, 2, 4, and 8. Both 1 and 8 are out, leaving us with either 2 or 4. Personally, I think 4 would be a little too sparse, so I’m choosing 2. Each dot will be 2 pixels away from the one next to it or above it. Now, we can divide our LevelCell into 2x2 chunks. Since a LevelCell is 8x8, there is a total of 16 different dot positions. Of course, we’ll never have that many, since most of the dot positions are not along the center of the LevelCell. We will only actually require seven of these positions (four vertically, four horizontally, with one dot in common between these two). So, to model this in software, we could make a variable that contains flags for each of these dots, or we could make an array of bools to represent the horizontal row and vertical column of possible dots. We run into a bit of a problem, however. The lines of dots within a LevelCell will cross in the middle. Modeling our dots like this would be something of a programming nightmare. However, not all is lost. We can reference our lines of dots from the center of the LevelCell and extend them to the east and south outside of our LevelCell. This way, the lines of dots cross at the corner, which is a lot easier to program than lines crossing somewhere in the middle. With this in mind, we shall amend our LevelCell class to include two arrays of dots, as shown in Listing 13.15. Listing 13.15 #define NUMDOTS 4 //levelcell struct struct LevelCell { bool Wall[WALL_COUNT]; struct LevelCell* ptr_north; struct LevelCell* ptr_west; bool DotRow[NUMDOTS]; bool DotCol[NUMDOTS]; };
But what about the overlap? Both DotRow[0] and DotCol[0] reference the same dot. Both can be TRUE at the same time. We could just not set one of them to TRUE; however, since the Cybiko is a limited memory environment, we don’t want to waste bytes we don’t have to. Another solution is to make one of the arrays have one less dot. This makes the computations more difficult than I want, though.
Team LRN
Chapter 13: A Simple Cybiko Game
163
We can make use of the extra bool to represent something else. For example, we could say that when both DotRow[0] and DotCol[0] are TRUE, there is a power pellet at that intersection. Now it’s no longer a waste. CyBk13_4 on the companion CD has some code that will place dots appropriately in LevelCells. I added a bunch of functions to LevelCell.h and LevelCell.c to accommodate the dots.
Where Do We Go from Here Well, I’ve got you as far as having a monster, a main character, a level, and the dots. Due to time and space limitations, I’m not going to bring this project to completion. I’m instead going to assign it as homework for you. There are still power pellets to implement, the monster and main character have to interact with the level, and other minor things need to be added like score, lives, and so on. Have fun with it. Be sure to send me a copy of what you have made.
Summary On the presumption that you finish this game, your job is not yet over. Next you have to test it, do bug fixing, retest it, and let other people test it before you can finally release it. The process of creating games is a long and arduous one, fraught with difficulties at every turn. For example, you are probably reading this chapter all in one sitting, so you probably have a hard time judging how much work went into it. This chapter took me about a week and a half to complete (eight actual work days), doing around 1,500-2,500 words per day, plus the sample programs. I work approximately six hours a day writing, so you are just finishing in a couple of hours what took me 48 hours to generate. Similarly, in a game, you can play through in a matter of minutes what took weeks to create. Anyway, you should now have a good idea of how to design and implement ideas for games and apps on the Cybiko. It’s not just a matter of having an idea and then making a game. You really have to think about what you are doing, and you have to know how to do it, otherwise you will face problems.
Team LRN
Chapter 14
Intermediate Graphics Overview We’re going to pick up where we left off in Chapters 8 and 9, exploring in further detail the Cybiko Bitmap class and the Graphics class. In addition, we will explore the BitmapSequence and Font classes, and examples of using drawing modes and raster operations on bitmaps will abound.
Writing to Bitmaps Back in Chapter 8, we explored the DisplayGraphics class, and used it to draw primitives like pixels, lines, and rectangles onto the screen. I spoke briefly about the Graphics class (from which DisplayGraphics is derived). This class is used to write to bitmaps. In fact, the DisplayGraphics class also works in this manner. The screen itself is nothing more than a bitmap.
Constructing/Destructing a Graphics Object If you are planning on drawing to a bitmap other than the main display, you will need to construct an object of the Graphics class. Graphics has two constructors (ctor and ctor_Ex). These are shown in Listing 14.1. Listing 14.1 struct Graphics* struct Graphics*
Graphics_ctor (struct Graphics* ptr_gfx) ; Graphics_ctor_Ex (struct Graphics* ptr_gfx, struct Bitmap* bitmap) ;
Both of these functions return pointers to the newly created Graphics object. The first parameter of each is a pointer to a Graphics object you would like to construct. The second parameter of Graphics_ctor_Ex is a bitmap to which you would like to write. This bitmap can come from anywhere. It can be a resource loaded into memory, from an external file, and even to the main display bitmap, if you so desire.
164
Team LRN
Chapter 14: Intermediate Graphics
165
The following piece of code (Listing 14.2) creates a Bitmap object (bmp1), and sets up a Graphics object (gfx1) to write to it. Listing 14.2 struct Graphics gfx1; struct Bitmap bmp1; //construct the bitmap 160 wide, 100 tall, and 2 bits per pixel Bitmap_ctor_Ex2(&bmp1,160,100,2); //construct the graphics object Graphics_ctor_Ex(&gfx1,&bmp1);
Once this is done, you can use any of the Graphics class functions to draw to the bitmap. After you no longer need to write to the bitmap, you can destruct the Graphics object with a call to Graphics_dtor, shown in Listing 14.3. Listing 14.3 void
Graphics_dtor (struct Graphics* ptr_gfx, int memory_flag)
This destructor is like any other, and you use it just like all of the other destructors we have covered so far. Extending the example above, we would destroy the Graphics object in the following manner (see Listing 14.4). Listing 14.4 //destroy the graphics object Graphics_dtor(&gfx1,LEAVE_MEMORY);
Setting and Getting the Bitmap You can use a Graphics object to write to however many different bitmaps you wish, and you don’t have to make a Graphics object for each one. You can swap different Bitmap objects into the Graphics object with ease using the Graphics_set_bitmap function, shown in Listing 14.5. Listing 14.5 void
Graphics_set_bitmap (struct Graphics* ptr_gfx, struct Bitmap* bmp) ;
This prototype is pretty self-explanatory; you send a pointer to the Graphics object in the first parameter, and a pointer to a Bitmap object you wish to have associated with that Graphics object as the second parameter. The function returns no value. With Graphics_set_bitmap, you can move from bitmap to bitmap, writing to each one in turn if you so desire. This is probably a good idea if you are trying to conserve memory. To retrieve which Bitmap object is associated with your Graphics object, you use Graphics_get_bitmap, shown in Listing 14.6. Listing 14.6 struct Bitmap*
Graphics_get_bitmap (struct Graphics* ptr_gfx) ;
Team LRN
166
Chapter 14: Intermediate Graphics
For this function, you send in a pointer to a Graphics object, and it returns a pointer to the Bitmap object that is associated with that Graphics object. Here’s a secret: you can associate a Bitmap object with more than one Graphics object, and use each Graphics object to draw differently onto your Bitmap object. The ramifications of this won’t be immediately obvious, but I’ll show you in a short bit how this can be cool.
Attributes of a Graphics Object While the Bitmap object associated with a Graphics object is by far the most important of all, there are a number of other attributes that you can set for a Graphics object that affect how the things you draw to the Bitmap object are rendered. There are five attributes: Font, Color, Draw Mode, Background Color, and Clipping Rectangle.
Font You can set and retrieve the current font being used by a Graphics object to render text. The functions are Graphics_set_font and Graphics_get font, shown in Listing 14.7. Listing 14.7 void Graphics_set_font (struct Graphics* ptr_gfx, struct Font* font) struct Font* Graphics_get_font (struct Graphics* ptr_gfx)
These functions should be self-explanatory. We have already used the DisplayGraphics version of the set_font function in Chapter 9.
Color You can set and retrieve the current foreground color used for rendering primitives such as lines and rectangles. The functions are Graphics_set_color and Graphics_get_color, and are shown in Listing 14.8. Listing 14.8 color_t Graphics_get_color (struct Graphics* ptr_gfx) void Graphics_set_color (struct Graphics* ptr_gfx, color_t fc)
These functions either set or get the foreground color. We used the DisplayGraphics version of set_color back in Chapter 7.
Draw Mode To set and retrieve the current draw mode, you use Graphics_set_draw_mode and Graphics_get_draw_mode. These are shown in Listing 14.9. Listing 14.9 drawmode_t Graphics_get_draw_mode (struct Graphics* ptr_gfx) void Graphics_set_draw_mode (struct Graphics* ptr_gfx, drawmode_t dm)
Team LRN
Chapter 14: Intermediate Graphics
167
We talked a little about draw modes in Chapter 9, and we will do more with them in this chapter.
Background Color We spoke briefly about background color in Chapter 9. It is used in conjunction with the draw mode to accomplish transparency. There is only a set function, no get function, so you have to keep track of the background color in some other manner. The function for setting the background color is shown in Listing 14.10. Listing 14.10 void
Graphics_set_bkcolor (struct Graphics* ptr_gfx, color_t fc)
Clipping Rectangle We haven’t spoken about clipping rectangles before. A clipping rectangle limits where on a bitmap you can write, rather than just allowing you to write on the entire thing. These are extremely useful when you are making viewports. There are two ways to set the clipping area for a Graphics object. The functions are called Graphics_set_clip and Graphics_set_clip_Ex. These functions are shown in Listing 14.11. Listing 14.11 void void
Graphics_set_clip (struct Graphics* ptr_gfx, int fx, int fy, int fw, int fh) ; Graphics_set_clip_Ex (struct Graphics* ptr_gfx, struct rect_t* ptr_rectangle) ;
In Graphics_set_clip, you send five parameters: the pointer to the Graphics object for which you are setting the clipping area, and the values for the left, top, width, and height of the clipping area you wish to have on that Graphics object. This function returns no value. In Graphics_set_clip_Ex, you send two parameters: the pointer to the Graphics object for which you are setting the clipping area, and a pointer to a rect_t which describes the clipping area you desire. This function returns no value. So, in order to make it so that you are only writing to a portion of the bitmap bound by (0,0) and (99,99), you use code like that shown in Listing 14.12 or 14.13. Listing 14.12 //gfx is a Graphics object variable Graphics_set_clip(&gfx,0,0,100,100);
Listing 14.13 //gfx is a Graphics object variable rect_t rc; rect_set(&rc,0,0,100,100);//set up the clipping rectangle Graphics_set_clip_Ex(&gfx,&rc);
Finally, to retrieve the clipping area associated with a Graphics object, you use Graphics_get_clip, shown in Listing 14.14.
Team LRN
168
Chapter 14: Intermediate Graphics
Listing 14.14 void
Graphics_get_clip (struct Graphics* ptr_gfx, struct rect_t* ptr_rectangle)
This function has the same parameter list as Graphics_set_clip_Ex. In the case of this function, however, the rectangle pointed to by ptr_rectangle is filled with information about the current clipping rectangle. Using rect_t is a little awkward, especially if you are used to the WIN32 RECT structure, which stores the left, top, right, and bottom of a rectangle. Here’s a simple way to determine what values you should place into a rect_t or Graphics_set_clip function in order to get the desired clipping area: First, you decide what portion of the screen you wish to write to. Determine the leftmost pixel column, the topmost pixel row, the rightmost pixel column, and the bottommost pixel row. Add one to the right and the bottom. The width will now be right-left, and the height will be bottom-top.
Drawing Primitives As for the primitives you can draw using a Graphics object, they are exactly the same as the ones you can use with a DisplayGraphics object. I’m not going to spend any time rehashing these functions since the same information about them applies here. You can take a look at these functions in Listing 14.15. Listing 14.15 color_t Graphics_get_pixel (struct Graphics* ptr_gfx, int fx,int fy) void Graphics_set_pixel (struct Graphics* ptr_gfx, int fx,int fy, color_t fc) void Graphics_draw_hline (struct Graphics* ptr_gfx, int x, int y, int xx) void Graphics_draw_vline (struct Graphics* ptr_gfx, int x, int y, int yy) void Graphics_draw_line (struct Graphics* ptr_gfx, int x, int y, int xx, int yy) void Graphics_draw_rect (struct Graphics* ptr_gfx, int fx, int fy, int fw, int fh) void Graphics_draw_rect_Ex (struct Graphics* ptr_gfx, struct rect_t* ptr_rectangle) void Graphics_fill_rect (struct Graphics* ptr_gfx, int fx ,int fy, int fw, int fh) void Graphics_fill_rect_Ex (struct Graphics* ptr_gfx, struct rect_t* ptr_rectangle)
Text Metrics Like primitive drawing, we have already covered text metrics in Chapter 9. Still, for a complete discussion of the Graphics object, they should be included. I have listed the applicable functions in Listing 14.16. Listing 14.16 int int int int
Graphics_string_width (struct Graphics* ptr_gfx, char* str) Graphics_string_width_Ex (struct Graphics* ptr_gfx, char* str, int len) Graphics_get_char_height (struct Graphics* ptr_gfx) Graphics_get_char_width (struct Graphics* ptr_gfx, char chr)
These functions all work with the Font object associated with the Graphics object that you can set or retrieve with Graphics_set_font and Graphics_get_font.
Team LRN
Chapter 14: Intermediate Graphics
169
Drawing Bitmaps and Text Though a Graphics object’s main purpose is to write to a Bitmap object, you can still draw other Bitmap objects onto it. (Drawing the same bitmap onto itself can have unpredictable results.) Listing 14.17 shows the various bitmap and text drawing functions available to the Graphics object. These functions operate in the same manner as the corresponding DisplayGraphics functions that we covered in Chapter 9. Listing 14.17 void
Graphics_draw_bitmap (struct Graphics* ptr_gfx, struct Bitmap* bmp, int left, int top, short fm) int Graphics_draw_char (struct Graphics* ptr_gfx, int x, int y, char fc) void Graphics_draw_text (struct Graphics* ptr_gfx, char* text, int left, int top) void Graphics_draw_text_Ex (struct Graphics* ptr_gfx, char* str, int left, int top, int len)
Other Functions These functions didn’t really seem to fit anywhere else. They all modify the appearance of the Bitmap object that is associated with the Graphics object you are using. There are three of these: Graphics_put_background, Graphics_fill_screen, and Graphics_scroll.
Graphics_put_background This function loads an image from a resource and puts it onto the Bitmap object associated with the Graphics object. In some circumstances, you would rather be able to just get a background image onto your Bitmap object without having to create and load in another Bitmap object. This function is perfect for that. It is shown in Listing 14.18. Listing 14.18 void
Graphics_put_background (struct Graphics* ptr_gfx, char* fp)
This function returns no value. The first parameter is a pointer to the Graphics object for which you are loading an image. The fp parameter is a pointer to a string that contains the name of the image you wish to load.
Graphics_fill_screen This function is just like DisplayGraphics_fill_screen. It clears the entire bitmap with a solid color. Listing 14.19 shows the syntax. Listing 14.19 void
Graphics_fill_screen (struct Graphics* ptr_gfx, color_t fc)
Team LRN
170
Chapter 14: Intermediate Graphics
Graphics_scroll The last function for the Graphics object that I’ll be explaining in this chapter is Graphics_scroll, which is shown in Listing 14.20. Listing 14.20 void
Graphics_scroll (struct Graphics* ptr_gfx, struct rect_t* ptr_rectangle, int dx, int dy)
This function is pretty cool. With it, you can scroll a portion of the bitmap up, down, left, or right. The first parameter is a pointer to a Graphics object that you wish to scroll. The second parameter is a pointer to a rect_t which describes the area of the Graphics object you want to have scrolling occur in. The last two parameters are how far you want to scroll horizontally and vertically. A positive dx will scroll to the right, a negative dx will scroll to the left, a positive dy will scroll down, and a negative dy will scroll up.
More Functions to Come... There are still two functions of the Graphics object that I haven’t covered yet. I’ll be covering these functions in a later chapter. You now have an almost complete knowledge of the Graphics object, with the exception of direct memory access.
Draw Modes As promised earlier, I am now going to discuss in greater detail the uses of draw modes. There are three of these: DM_PUT, DM_OR, and DM_XOR. DM_PUT puts the image onto the destination without filter. The image being drawn will be drawn as it appears on the source, destroying whatever was on the destination. DM_OR works with the background color (set by Graphics_set_bkcolor). Any pixel in the source image that corresponds to the background color will not be transferred to the destination. This allows a transparency of sorts, but at the same time requires that you give up a color (there are ways around this). DM_XOR combines source and destination pixels with an XOR. This is a non-destructive way to write images, but the images might not show up quite as you want them to. Writing the same source image to the destination in the same exact location will restore the original image on the destination. Now, I can talk about drawing modes all day long, and it won’t do a bit of good until you see them in action. So, I’ve provided a small demo about bitmap drawing modes in CyBk14_1 on the companion CD. This is a pretty simple little demo. There is a little man positioned on the screen (you can move him with the arrow keys), and a tiled background image. You can switch the draw modes by hitting 1, 2, or 3. The current draw mode is displayed on screen at the upper-left corner.
Team LRN
Chapter 14: Intermediate Graphics
171
For your viewing pleasure, a picture of DM_PUT, DM_OR, and DM_XOR can be found in Figures 14.1, 14.2, and 14.3, respectively. Figure 14.1: DM_PUT
Figure 14.2: DM_OR
Figure 14.3: DM_XOR
The character is rather small, so you have to look pretty closely to notice the difference between these drawing modes. In DM_PUT, the background has been completely obliterated. In DM_OR, we are using CLR_WHITE as the background color, so only the black portions of the image containing the character can be seen, and the rest of the background is untouched. In DM_XOR, the image of the character is sort of mixed in with the background. We can still see him, but he looks a little strange. The times to use the different draw modes are fairly obvious. You want to use DM_PUT for backgrounds, DM_OR for foreground images or partially transparent images, and DM_XOR for cursors. Something I want to point out here about the code for CyBk14_1 is that you’ve seen or dealt with most of the code before, but the OnPaint function contains a bunch of calls to the Graphics class functions. See Listing 14.21.
Team LRN
172
Chapter 14: Intermediate Graphics
Listing 14.21 bool OnPaint() { int x,y; //redraw the back buffer Graphics_fill_screen(&gfxDoubleBuffer,CLR_WHITE); //put the background Graphics_set_draw_mode(&gfxDoubleBuffer,DM_PUT); for(y=0;y<10;y++) { for(x=0;x<16;x++) { //draw a tile Graphics_draw_bitmap(&gfxDoubleBuffer,&bmpBkgd,x*10,y*10,BM_NORMAL); } } //put the dude Graphics_set_bkcolor(&gfxDoubleBuffer,CLR_WHITE); Graphics_set_draw_mode(&gfxDoubleBuffer,dmCurrent); Graphics_draw_bitmap(&gfxDoubleBuffer,&bmpDude,dudex*10,dudey*10,BM_NORMAL); //write the drawmode to the screen if(dmCurrent==DM_PUT) { Graphics_set_color(&gfxDoubleBuffer,CLR_BLACK); Graphics_draw_text(&gfxDoubleBuffer,"DM_PUT",0,0); } if(dmCurrent==DM_OR) { Graphics_set_color(&gfxDoubleBuffer,CLR_BLACK); Graphics_draw_text(&gfxDoubleBuffer,"DM_OR",0,0); } if(dmCurrent==DM_XOR) { Graphics_set_color(&gfxDoubleBuffer,CLR_BLACK); Graphics_draw_text(&gfxDoubleBuffer,"DM_XOR",0,0); } //write double buffer to screen DisplayGraphics_draw_bitmap(main_module.m_gfx,&bmpDoubleBuffer,0,0,BM_NORMAL); //show the screen DisplayGraphics_show(main_module.m_gfx);
Team LRN
Chapter 14: Intermediate Graphics
173
//message has been handled return(TRUE); }
As you can see, we are setting various attributes of the Graphics object, including draw mode, color, background color, and so on. In Prog_Init, we set the font of this graphics object a single time, and that value never changes throughout the program. We could have done the same with Graphics_set_color and Graphics_set_bkcolor, but I was just being lazy. There are two calls to Graphics_set_draw_mode in OnPaint, because the background image (in bmpBkgd) is written with DM_PUT, and the foreground image (bmpDude) is drawn using the draw mode stored in dmCurrent. Switching the drawing mode all the time like this seems a little silly and makes for way too much code. Something I could have done is had more than one Graphics object, each pointing to bmpDoubleBuffer, with a different set of attributes. Then I wouldn’t have had to go through all of this switching of drawing modes all the time; I would just use the Graphics object that had the right set of attributes. This sort of thing can be done for any of the attributes. If you were writing text with more than one font, you would just make two Graphics objects, set the fonts and colors of each, and write text using Graphics_draw_text for both, without having to switch with Graphics_set_font all of the time. Just don’t go crazy with this because there is only a limited amount of memory on the Cybiko, and making more Graphics objects than you need can cause it to crash when it runs out of memory.
Bitmap Draw Modes I talked briefly about these back in Chapter 9. The last parameter of Graphics_draw_ bitmap and DisplayGraphics_draw_bitmap have an fm parameter, which the documentation says is a “draw mode” (not to be confused with the draw mode we set using Graphics_draw_mode). There are three bit flags you can use and combine in this parameter, and a fourth constant that you can use when none of the bit flags are appropriate. These constants are BM_INVERSE, BM_FLIP_Y, BM_FLIP_X, and BM_NORMAL. Most of the time, you will use BM_NORMAL. The purpose of BM_INVERSE is to invert the colors of the source bitmap, so black becomes white, dark gray becomes light gray, and vice versa. BM_INVERSE is not currently (at the time of this writing) implemented in the Cybiko SDK. I do not know if they plan on ever implementing it. BM_FLIP_X and BM_FLIP_Y behave much as you would expect; they reverse horizontally or vertically (or both, if these flags are combined) the image you are drawing. These work just fine.
Team LRN
174
Chapter 14: Intermediate Graphics
BM_NORMAL just draws the image as it is, right side up and in the colors it is supposed to be. There are some good uses for BM_FLIP_X and BM_FLIP_Y. There would be good uses for BM_INVERSE if it worked, but it doesn’t, so I’m not going to bother with it. Let’s say you are making a game where the player controls a spaceship. Let’s also say that you are allowing the player to move in eight directions—north, northeast, east, southeast, south, southwest, west, and northwest, with north being toward the top of the screen and east being toward the right of the screen. In other circumstances, you would need eight images for your ship. With BM_FLIP_X and BM_FLIP_Y, you can get away with only using three. How? Well, the three images you need are the ship facing north, east, and northeast. To make the ship face south, you just use the north image, and use BM_FLIP_Y. You can use a similar idea for West using East and BM_FLIP_X. For the diagonals, you can use BM_FLIP_X for northwest, BM_FLIP_Y for southeast, and both BM_FLIP_Y and BM_FLIP_X for southwest. See? Eight directions with only three images, saving the precious space that would have been taken up by the extra five images for other things.
The BitmapSequence Class While on the topic of Bitmap and Graphics objects, I wanted to introduce you to the BitmapSequence class. I mentioned these briefly earlier on, but I didn’t go into any sort of detail. A BitmapSequence is nothing more than an array of Bitmap objects. When I talked about picture resources early on, I spoke of how a picture file can contain more than one image. In fact, it can contain as many as 255 of them, although you probably won’t use that many. Bitmap sequences are great for organizing your images into related sets. For instance one BitmapSequence might be used for background tiles, another for the animation for your main character, and yet another for items that your character can pick up, and so on. There are only a handful of functions to learn for the BitmapSequence class, so let’s get started!
Construction and Destruction As usual, we first want to look at how to create and destroy objects of the BitmapSequence class. Like all other objects on the Cybiko, this is done with a ctor function and a dtor function. BitmapSequence has two constructors and one destructor, which are shown in Listing 14.22. Listing 14.22 struct BitmapSequence*
BitmapSequence_ctor (struct BitmapSequence* ptr_bitmap_sequence)
Team LRN
Chapter 14: Intermediate Graphics
175
struct BitmapSequence* void
BitmapSequence_ctor_Ex (struct BitmapSequence* ptr_bitmap_sequence, char* file_name) BitmapSequence_dtor (struct BitmapSequence* ptr_bitmap_sequence, int memory_flag)
BitmapSequence_ctor takes a single parameter, the pointer to the BitmapSequence object you wish to construct. It constructs an empty sequence of bitmaps and returns a pointer to this new BitmapSequence. When would you want to use this function? Only when you are loading a BitmapSequence from an external file rather than a resource. BitmapSequence_ctor_Ex adds a parameter, file_name, which is the name of a resource in the application archive that you want to load into the BitmapSequence. BitmapSequence_dtor is like any other dtor function. You put either LEAVE_MEMORY or FREE_MEMORY as appropriate into the memory_flag parameter.
Retrieving Information There are two pieces of information that you might want from a BitmapSequence object: how many Bitmap objects are in the sequence and what bitmaps are in the sequence. To determine how many bitmaps are in the sequence, you use BitmapSequence_get_size, which is shown in Listing 14.23. To retrieve pointers to each of the Bitmap objects in the sequence, you use BitmapSequence_get_bitmap, shown in Listing 14.24. Listing 14.23 int
BitmapSequence_get_size (struct BitmapSequence* ptr_bitmap_sequence)
This function takes a single parameter (a pointer to a BitmapSequence) and returns the number of Bitmap objects in that sequence. Listing 14.24 struct Bitmap*
BitmapSequence_get_bitmap (struct BitmapSequence* ptr_bitmap_sequence, int bitmap_index)
This function takes two parameters, a pointer to a BitmapSequence and the index of the Bitmap object you wish to retrieve (the first image in the sequence has an index of zero). The function returns a pointer to the requested Bitmap object, if it exists. Using an index that is equal to or greater than the value returned by BitmapSequence_ get_size will have unpredictable results.
The Font Class The Font class, which we talked about back in Chapter 9, is actually a child class of BitmapSequence. You can in fact use a file containing a BitmapSequence as a Font object. Font really doesn’t care. We have already spoken about how to use the built-in
Team LRN
176
Chapter 14: Intermediate Graphics
fonts, cool_normal_font, cool_bold_font, mini_normal_font, and mini_bold_font. We are now going to discuss using fonts other than those that come with CyOS.
Construction and Destruction Naturally, in order to make and use your own Font object, you must first construct it using one of its ctor functions. When you are done using it, you must destroy it using the dtor function. The Font class has two constructors and one destructor, as shown in Listing 14.25. Listing 14.25 struct Font* struct Font* void
Font_ctor (struct Font* ptr_font) Font_ctor_Ex (struct Font* ptr_font, char* file_name, bool fixed, int spacing) Font_dtor (struct Font* ptr_font, int memory_flag)
Font_ctor creates a blank font, with no images. It takes as a parameter a pointer to a Font object, and returns a pointer to the newly constructed Font object. Font_ctor_Ex constructs a font and loads in a file (specified by file_name) to use as that font. In addition, you may specify whether or not this font is fixed width with the fixed parameter (either TRUE or FALSE), and the spacing of the font (the spacing parameter). Font_dtor is like any other destructor and is called in the same manner.
Attributes of a Font Object While the BitmapSequence class was relatively simple, used for nothing more than an array of Bitmap objects, the Font class is richer in functionality and amount of information that must be known in order to use a font successfully. A Font object has the following set of attributes: name, fixed/proportional, spacing, bitmap array, size, and image offset.
Name A Font object has a name. I do not know why, as I don’t really see a need for it. Still, the functions for setting and getting a Font object’s name are shown in Listing 14.26. Listing 14.26 char* Font_get_name (struct Font* ptr_font) void Font_set_name (struct Font* ptr_font, char* filename)
I provide this information for completeness only.
Fixed-Width and Proportional Fonts There are two main classes of fonts as far as the Cybiko is concerned. These are fixed width and proportional width. In a fixed-width font, all glyphs/characters are the same width, always. In a proportional font, different characters/glyphs have different widths,
Team LRN
Chapter 14: Intermediate Graphics
177
and in general, this is a lot easier on the eyes. To determine whether or not a font is fixed width, use Font_is_fixed. If it returns TRUE, it is fixed width. If it returns FALSE, it is proportional. You can set whether or not a font is fixed width either in the call to Font_ctor_Ex or with Font_set_fixed. The two functions I just described are shown in Listing 14.27. Listing 14.27 bool void
Font_is_fixed (struct Font* ptr_font) Font_set_fixed (struct Font* ptr_font, bool fixed)
Spacing You can also set the spacing of your Font objects. The meaning of spacing is different, depending on whether or not the Font object is fixed width or proportional. In a fixed-width font, spacing indicates how many pixels from the left of one character it is to the left of the next character. In a proportional font, spacing indicates how many pixels are placed between each character. Listing 14.28 shows the functions for getting and setting the spacing attribute of a Font object. Listing 14.28 int Font_get_spacing (struct Font* ptr_font) void Font_set_spacing (struct Font* ptr_font, int spacing)
For a good demo on fixed width and proportional fonts and spacing, please take a look at CyBk14_2 on the companion CD. It has 11 fonts, four proportional and the rest fixed, and it shows various statistics of them.
Bitmap Array Like BitmapSequence, a Font object is nothing more than array of Bitmap objects. You can use the Font version of get_bitmap, which operates the same way as BitmapSequence_get_bitmap, or you can use Font_bmp_by_char, which converts from ASCII code to the corresponding bitmap index. These functions are shown in Listing 14.29. Listing 14.29 struct Bitmap* struct Bitmap*
Font_get_bitmap (struct Font* ptr_font, int bitmap_index) Font_bmp_by_char (struct Font* ptr_font, int chr)
The relationship between the character code (ASCII) and the bitmap index is that the bitmap index is the character code minus 32 (0x20). This, I believe, was done because most of the character codes under 32 (the space character) are rarely if ever used.
Size You can also determine the number of character images in a Font object by using Font_get_size, shown in Listing 14.30. Listing 14.30 int
Font_get_size (struct Font* ptr_font)
Team LRN
178
Chapter 14: Intermediate Graphics
This function operates identically to BitmapSequence_get_size.
Image Offsets These next functions will take some explaining. In order to save space in an image file, if you have a transparent color, you can tell 2pic what it is, and it will crop out any rows or columns on the top, bottom, left, or right which consist completely of that color. In the case of fonts, this is usually the color white. Take, for example, the picture shown in Figure 14.4. Figure 14.4: An image
As you can see, a lot of the area around the image is pure white. This image is 32x32 and 2 bits per pixels, so with all of that wasted white in there, it takes up 256 bytes to store it in a file. Now, take a look at Figure 14.5, which shows a bounding rectangle around the important part of the image. Figure 14.5: Bounding rectangle for image
Only the stuff inside the bounding rectangle has non-white pixels in it, so it would be more efficient to store just this section, and ignore the rest of the image. This portion of the image ranges from (4,6) to (26,25), making the width 23, and the height 20. A 23x20 image in 2 bits per pixel takes up only 120 bytes, which is less than half of what it would take to store the entire image. However, we still, when drawing the bitmap, have it show up in relation to the upper-left corner of the original image. To accomplish this, we simply keep track of an offset in the bitmap record in the file in which it is stored. This sort of thing is especially common in fonts, where characters like the space take up virtually no visible pixels at all, and small characters like the exclamation points are usually no more than one or two columns wide. The tools for making bitmaps and fonts do this stuff automatically, but we can later retrieve the x and y offsets from the font by using Font_get_x and Font_get_y, shown in Listing 14.31. Listing 14.31 int int
Font_get_x (struct Font* ptr_font, int bitmap_index) Font_get_y (struct Font* ptr_font, int bitmap_index)
Team LRN
Chapter 14: Intermediate Graphics
179
Text Metrics The functions shown in Listing 14.32 are functionally the same as the text metric functions of the Graphics object. Indeed, I believe that the Graphics object simply calls these functions based on the currently selected font. Listing 14.32 int int int int
Font_get_char_width (struct Font* ptr_font, int chr) Font_string_width (struct Font* ptr_font, char* str) Font_string_width_Ex (struct Font* ptr_font, char* str, int len) Font_get_char_height (struct Font* ptr_font)
All of these functions are exactly the same as their Graphics class equivalents, other than taking a pointer to a Font object rather than a Graphics object. They give the same information.
Loading a Font Object from an Input Stream The Font_load function (Listing 14.33) is equivalent to the BitmapSequence_load function. Listing 14.33 bool
Font_load (struct Font* ptr_font, struct Input* input)
Splitting Strings Finally, there is a really cool and useful function for Font objects called Font_split_ string. It is shown in Listing 14.34. Listing 14.34 char*
Font_split_string (struct Font* ptr_font, char** str, int width, int* len)
Font_split_string is a little weird to work with at first, but once you’ve got the hang of it, it is quite useful. This function’s primary purpose is to split up a string into little pieces so that you can format it within a particular rectangular area. To do this, you start with a buffer into which you copy textual information, like so: char buf[80]; char* ptr; char* current; int len; sprintf(buf,”The quick brown fox jumped over the lazy dog.”);
You then set a character pointer to the beginning of the string, like so: ptr=buf;
Team LRN
180
Chapter 14: Intermediate Graphics
Now, you call Font_split_string several times, like this: current=Font_split_string(&fnt,&ptr,100,&len);
The number “100” can be replaced by whatever pixel width you are trying to write into. After this call, current will point to the portion of the string that you are writing next, and len will contain the number of characters to write, so you can write your string. The ptr variable will be updated to the start of the next line, so when you call the function again, it will continue where you left off. You keep calling Font_split_ string until len is equal to zero.
Summary You now know just about everything you would ever want to know about 2D graphics on the Cybiko (there are still just a few items left to cover, but I’ll tackle them later). Also, you can do just about anything with bitmaps, bitmap sequences, and fonts that you would ever want to do. Next up, we’re going to look at the Cybiko user interface part of the SDK with greater detail. This is perhaps the most complicated part of the SDK, so go get yourself a few sodas and make yourself a pizza. You’re going to need it.
Team LRN
Chapter 15
Form and Menu Basics Overview Welcome to the most complex part of the Cybiko SDK, the user interface (UI) system. We’ve already taken a look at one of the UI classes, cDialog, and we used it to make a simple message box and a simple input box, but the UI system can handle more—a whole lot more. In this chapter, we are going to start in the shallow end of the pool, and we’ll get to the deep end in a later chapter. By the end of the chapter, you will be able to make some simple dialog boxes and have the ability to make a simple menu.
Structure of the UI System There are a number of UI classes for the Cybiko. Table 15.1 lists all of them, along with what they do and what they inherit from. Table 15.1 UI classes Class
Parent Class
Purpose
cObject
NONE
Provide a base class for all other UI classes.
cBevel
cObject
Make a beveled box, good for grouping things.
cBitmap
cObject
Display a bitmap.
cBox
cObject
Display a box.
cButton
cObject
Display a push button.
cClip
cObject
Base class for complex objects.
cEdit
cObject
An edit box for textual input.
cItem
cObject
Item for use in a list.
cText
cObject
A text label.
181
Team LRN
182
Chapter 15: Form and Menu Basics
Class
Parent Class
Purpose
cProgressBar
cBevel
A progress bar to indicate how much of a task is done.
cCustomForm
cClip
Base class for forms.
cEngine
cClip
Object that gets the work of the UI system done.
cList
cClip
Contains a number of items for listing.
cSItem
cItem
A special type of item.
cXItem
cItem
A special type of item.
cDialog
cCustomForm
Message boxes and input boxes.
cFrameForm
cCustomForm
User-defined forms.
cXStr
cXItem
Special type of XItem.
cXByte
cXItem
Special type of XStr.
Now, at first glance, I know this list of 19 UI classes is a bit intimidating. When I first looked at the UI system on the Cybiko, I asked myself, how am I ever going to get this? So, if your eyes are glazing over and you’ve got a feeling of dread in your belly, you aren’t alone. We aren’t going to cover all of these objects in this chapter. Instead, we are going to select the easiest ones to learn and use them now. We’ll take on the more complicated classes at a later time.
cObject This is the easiest UI class to learn about. It does almost nothing. It exists solely to provide a base class for the other UI classes, enabling you to add objects of whatever class to objects that contain them.
cClip This class is pretty important. It is the base class for all UI classes that serve as collections of other objects, providing all of the functionality needed to manage a number of subelements. We aren’t going to look at cClip itself right now. We are instead going to look at some of its child classes.
cEngine The cEngine class is essential to the operation of the Cybiko UI system. However, you will never create one, and you will never call its functions directly. An object of the cEngine class already exists for every program. Its name is “Screen.” It has two functions, shown in Listing 15.1.
Team LRN
Chapter 15: Form and Menu Basics
183
Listing 15.1 int int
cEngine_Lock (struct cEngine* ptr_engine); cEngine_Unlock (struct cEngine* ptr_engine);
Each of these functions takes a pointer to a cEngine object (i.e., &Screen). Only one cEngine can ever exist at a time. In this way, cEngine is sort of like DisplayGraphics. You will probably never need these functions. cEngine inherits from cClip, and works as a collection of all of the visible UI elements you place on screen.
cCustomForm For your own dialogs, you will use cCustomForm, or one of its child classes cDialog or cFrameForm. We have already looked at cDialog, and seen just about all that it can do. For cCustomForm, which inherits from cClip, you create a form of a custom size, place items on it, and then start processing messages for it. A similar idea applies for cFrameForm except that a cFrameForm object is always full-screen. You can make full-screen cCustomForms as well, but why bother when a cFrameForm is full-screen automatically? In this chapter, we’ll be making some cCustomForms and some cFrameForms.
cList A cList item also inherits from cClip. This class is meant for list boxes, i.e., collections of cItem objects or objects that descend from cItem. This is a pretty handy class. We’ll be using one of these, as well as some cItems, to make a menu later on.
Making a cCustomForm Before we go any further, we have to deal with constructing a cCustomForm. Without a form of some kind, we wouldn’t have any place to put our other items. Well, this isn’t strictly true, we can always put things onto the cEngine object, Screen, but this chapter is about forms and menus, so we want to make some. Like any other object on the Cybiko, you create a cCustomForm object by calling its constructor, cCustomForm_ctor. This function is shown in Listing 15.2. Listing 15.2 struct cCustomForm* cCustomForm_ctor ( struct cCustomForm* ptr_custom_form, struct rect_t* rect, char* name, bool round, struct cWinApp* ptr_win_app );
The first parameter is (as always) a pointer to the object you are constructing. The function returns a pointer to the newly constructed cCustomForm.
Team LRN
184
Chapter 15: Form and Menu Basics
The rect parameter is a pointer to a rect_t, which specifies the position and size of the form. The name parameter is the title for the form. You can use NULL to not have a title bar. The round parameter is either TRUE or FALSE. If TRUE, the form has rounded corners; if FALSE, it has sharp rectangular corners. The choice is up to you. I personally find the rounded corners easier on the eyes, but there are situations where non-rounded corners have their place. Finally, ptr_win_app is a pointer to a cWinApp object, i.e., main_module.m_process. This is so that the cCustomForm can check for messages. On the other end, when you no longer need your cCustomForm object, you call its destructor, cCustomForm_dtor. This destructor is like all of the other destructors, so I’m not going to list it. Finally, to show your cCustomForm object, you call cCustomForm_Show. See Listing 15.3. Listing 15.3 void cCustomForm_Show (
struct cCustomForm * ptr_custom_form );
This function returns no value and takes as a parameter a pointer to a cCustomForm object that you wish to show. In CyBk15_1 on the companion CD, you can find the workspace and code for a very simple cCustomForm. It simply creates a cCustomForm in Prog_Init, shows it in Prog_Loop, and destroys it in Prog_Done. The cCustomForm object doesn’t get any simpler than that!
Adding Controls to the Form While simply creating a cCustomForm object is a fundamental step in the right direction, it’s not very impressive. Without something on the form to look at and interact with, we may as well not even have a form at all. So, we want to create other UI objects, and add them to our form. How do we do that? Well, I’ll get to the creation of additional UI objects in a moment. I’m going to talk about how to add them first. For cClip and all classes that are derived from cClip, there are functions to add, remove, and manage other objects that you wish to place into the control. To add an object, you call the appropriate AddObj or InsObj function. For example, on our cCustomForm, we would call cCustomForm_AddObj or cCustomForm_InsObj. The cCustomForm versions of these functions are shown in Listing 15.4. Listing 15.4 void
cCustomForm_AddObj ( struct cCustomForm* ptr_custom_form, struct cObject* ptr_object, int x, int y
Team LRN
Chapter 15: Form and Menu Basics
void
185
) ; cCustomForm_InsObj ( struct cCustomForm* ptr_custom_form, struct cObject* ptr_object, int x, int y, int index );
These two functions are nearly identical except that InsObj has an extra parameter. Neither function returns a value. The first parameter is a pointer to the cCustomForm object you are adding an object to. The second parameter is a pointer to the object you are adding to the form. The third and fourth parameters are the upper-left x and y coordinate of the object you are adding. For InsObj, the fifth parameter is an index into the list of objects already on the form. The InsObj function puts the new object before the object indexed by the value in index. To remove an object, you simply use the appropriate RemObj function. See Listing 15.5 for the cCustomForm version of this function. Listing 15.5 void cCustomForm_RemObj ( struct cCustomForm * ptr_custom_form, struct cObject * ptr_object );
This function returns no value. The first parameter is a pointer to the cCustomForm for which you are removing an object, and the second parameter is a pointer to the object that you wish to remove. NOTE: Keep in mind that while we are only discussing the cCustomForm class at the moment, the AddObj, InsObj, and RemObj functions work on cClip and all of its child classes, including cList, cEngine, cCustomForm, cDialog, and cFrameForm. Each of these classes (except for cEngine) has these functions implemented. For cEngine, you have to use the cClip version of these functions on the cEngine object.
Creating Simple Objects for Your Form Now that we know how to add objects to a form, we now need objects to add, which means it’s time to learn how to create the simpler objects (i.e., the non-interactive ones). I’m now going to show you how to make cBevel, cBox, cBitmap, and cText objects and add them to your forms.
cBevel A bevel is a user interface element that is usually used to frame other controls, often to give a quasi-3D appearance to the user interface. A bevel does nothing more than frame a rectangle that you specify the dimensions of. You can optionally tell cBevel to
Team LRN
186
Chapter 15: Form and Menu Basics
show zero, one, two, three, or all four of its sides. I admit, telling a cBevel object to show none of its sides is rather a waste of time, but it is possible. In order to get a 3D shaded effect, use two cBevels of contrasting colors and the same location with opposite sides being shown in each. To create a cBevel, you use cBevel_ctor, shown in Listing 15.6. To destroy a cBevel, use cBevel_dtor. Listing 15.6 struct cBevel*
cBevel_ctor (struct cBevel* ptr_bevel, int w, int h, color_t color, char flags);
This function returns a pointer to the newly created cBevel. The first parameter is a pointer to a cBevel that you wish to construct. The w and h parameters specify the width and height of the cBevel. (You specify its position when you use AddObj to put it on the form.) The color parameter specifies what color is to be used by cBevel, and flags is a combination of one or more tBevelSidesTypes constants. These constants are Top, Bottom, Left, Right, and All. The meanings of these should be rather obvious. You can use the bitwise OR operator (|) to combine these flags. The All flag is a combination of the four others. In CyBk15_2 on the companion CD, I have an extended version of the program I wrote for CyBk15_1. This time, the form created has two bevels placed on it. One is light gray, the other dark gray. One shows the left and top, the other shows the bottom and right, achieving the 3D effect I was discussing earlier. The cBevels give the form a slightly “popped out” sort of look. If I reversed the colors of the cBevels, it would have sort of a “sunken in” look. Try it out. NOTE: The form in CyBk15_2 is 80x80, centered on the screen. The cBevels are 40x40 each, centered on the form. However, the x and y of the cBevels are 18 for x and 18 for y, when normally you would expect it to be 20 and 20, since (80–40)/2=20. The form itself has a border of 3 pixels on each side, so the inside of the form is only 76x76 (minus 4 for each dimension), so to properly center the bevel, it has to be placed at (76–40)/2=18. Just keep this in mind when placing objects onto forms. The size of the form isn’t the same inside as outside.
You may notice in CyBk15_2 that during Prog_Done, I first remove all controls from the form, destroy them, and then destroy the form. This is a good practice to get into; otherwise you will have memory leaks.
cBitmap Seeing how easy the cBevel objects were, let’s give the cBitmap class a try. A cBitmap object is used to display a bitmap on a form. You probably guessed that from the name. There are two constructors for a cBitmap object, ctor and ctor_Ex. With ctor_Ex, you can specify a transparent color. As usual, you can delete a cBitmap object with a call to cBitmap_dtor.
Team LRN
Chapter 15: Form and Menu Basics
187
Listing 15.7 struct cBitmap* struct cBitmap*
cBitmap_ctor (struct cBitmap* ptr_cbitmap, struct Bitmap* ptr_bitmap) ; cBitmap_ctor_Ex (struct cBitmap* ptr_cbitmap, struct Bitmap* ptr_bitmap, color_t color);
Like good little constructors, these functions return pointers to the new objects they have created, and their first parameter is a pointer to an object that needs to be constructed. The second parameter is a pointer to an already constructed Bitmap object. This Bitmap object is not destroyed when the cBitmap object is destroyed, so you have to take care of that yourself. The third parameter of cBitmap_ctor_Ex is a color to use as the transparent color for the bitmap. It can be any of the CLR_* constants. Since the Bitmap object is separate from the cBitmap object, the cBitmap object is one of the few controls that you can change the appearance of without destroying it and re-creating it. You can create a Bitmap object, assign a Graphics object to write to it, and assign the Bitmap object to the cBitmap object, and any drawing you do on the Bitmap with the Graphics object will show up in the cBitmap object. This is a pretty handy thing at times. In CyBk15_3 on the companion CD, there is a program that takes our simple form and puts a cBitmap object onto it. Since I didn’t want to bother to make a new bitmap, it simply loads in Root.ico, makes the cBitmap, and puts it on the form.
cBox I see the cBox object as a complementary object to cBevel. Where cBevel frames a rectangular area, the cBox fills it in. I don’t really see a whole lot of uses for cBox, but I’m sure you can find some. To construct a cBox, you use cBox_ctor, shown in Listing 15.8. To destroy a cBox, you call cBox_dtor. Listing 15.8 struct cBox * cBox_ctor ( struct cBox * ptr_cbox, int w, int h, color_t color, bool transparent );
This function returns a pointer to the newly created cBox object. The first parameter is a pointer to a cBox object that needs to be constructed. The w and h parameters are the desired width and height of the cBox. You set the x and y position when you call AddObj. The color parameter is one of the CLR_* constants, setting the color of the cBox. The transparent parameter is supposed to set whether or not the cBox is transparent.
Team LRN
188
Chapter 15: Form and Menu Basics
In tests I have done, it doesn’t work. However, one might wonder why anyone would make a transparent filled-in cBox in the first place. An example of a form using a cBox can be found in CyBk15_4 on the companion CD.
cText This is the last of the elements that I consider to be “display elements.” The cText class is just a label you can place on your form. It primarily just sits there, and you don’t interact with it. There are plenty of uses for such a thing, including putting labels on other controls or just displaying information. To make a cText object, you use cText_ctor, shown in Listing 15.9. To destroy a cText object, use cText_dtor. Listing 15.9 struct cText* cText_ctor( struct cText* ptr_text, char* text, struct Font* ptr_font, color_t color );
This function returns a pointer to the newly constructed cText object, and takes as its first parameter a pointer to a cText object to construct. The text parameter is the text you wish to display. The ptr_font parameter is a pointer to a Font object that you wish to display the text with. The color parameter is the color in which you would like the text displayed. You can see an example of cText in use in CyBk15_5 on the companion CD.
Interacting with Controls While cBevel, cBitmap, cBox, and cText are wonderful for displaying information on a form, they really don’t further what forms are about in the first place, which is to gather information from the user. This means we have to have a control that the user can interact with—a cButton, a cEdit, or a cList. For now, we are going to look at cButton and cEdit, leaving cList as its own topic. Before we talk about either of these things, however, we need to talk about cCustomForm again. In almost all cases, you will want to show your forms modally. If you remember from our discussion of cDialog back in Chapter 11, we talked about the difference between modal and modeless dialogs. When you don’t want the user do to anything else in the program except respond to your form, you want to use a modal form. Making a form modeless is rather difficult, so I’m not going to discuss it right now. To show a form modally, you make use of cCustomForm_ShowModal, shown in Listing 15.10.
Team LRN
Chapter 15: Form and Menu Basics
189
Listing 15.10 int
cCustomForm_ShowModal (struct cCustomForm* ptr_custom_form);
This function takes as a parameter a pointer to a cCustomForm that you wish to show modally. It returns the ModalResult returned by the form. I have spoken about modal results before, in association with cDialogs. Whenever you create a cDialog, you tell the constructor which buttons you want to have (mbOk, mbCancel, etc.), and cDialog_ShowModal returns which one was pressed (mrOk, mrCancel, and so on). In your own forms, you get to set up your own modal results, or you can use the predefined values. I try to use the predefined values if at all possible. Only if there is no preexisting modal result value do I define my own. You assign modal results to cButtons, and when that button is pressed, cCustomForm_ShowModal returns that value. Also, cCustomForm_ShowModal will return (with an mrNone) when a MSG_QUIT or a MSG_SHUTUP occurs. Just keep that in mind and handle it appropriately.
cButton By far, the most commonly used control is the cButton control, since this is what makes modal forms work. The cButton class is a rather simple beast. To make one, you use cButton_ctor, which is shown in Listing 15.11. To destroy one, you use cButton_dtor. Listing 15.11 struct cButton* cButton_ctor ( struct cButton* ptr_cbutton, char* text, int modal_result );
This function takes a pointer to a cButton to be constructed, some text to display on the button, and the modal result assigned to this button. It returns a pointer to the newly constructed cButton object. It’s a very simple object to make. After you have constructed a cButton, add it to the form with the AddObj function, then show the form modally. When making and positioning buttons, always remember the following: the basic image for a button is 48x12, and it casts a “shadow” three pixels to the right and down. It is the shadow that you want to position, not the top of the button. So, decide where you want the button to be centered, subtract half of the width of the button (24) and subtract another 3 pixels, for a total of –27 for the horizontal coordinate. For the vertical coordinate, subtract half of the height (6) and subtract another 3, for a total of –9. There is an example of using a single cButton on a form in CyBk15_6 on the companion CD. Since all of the action takes place in Prog_Init, the code for making a form with a single button on it is in Listing 15.12.
Team LRN
190
Chapter 15: Form and Menu Basics
Listing 15.12 bool Prog_Init() { //initialization struct rect_t rc; int mr; //clear the screen DisplayGraphics_fill_screen(main_module.m_gfx,CLR_WHITE); //set up custom form rect_set(&rc,40,25,80,50); cCustomForm_ctor(&customform,&rc,NULL,TRUE,main_module.m_process); //set up the button cButton_ctor(&btn,"Ok",mrOk); //add the button cCustomForm_AddObj(&customform,&btn,11,14); //show form modally(store the modal result in mr) mr=cCustomForm_ShowModal(&customform); //remove the button cCustomForm_RemObj(&customform,&btn); //destroy the button cButton_dtor(&btn,LEAVE_MEMORY); //destroy the form cCustomForm_dtor(&customform,LEAVE_MEMORY); //clear the screen DisplayGraphics_fill_screen(main_module.m_gfx,CLR_WHITE); DisplayGraphics_show(main_module.m_gfx); //return TRUE if program initialized, and FALSE if it did not return(TRUE); }
cEdit Another commonly used control is cEdit. This control allows you to type in text, which is quite handy from time to time. Since there are a wide variety of uses for a cEdit, it is a more complicated control. The constructor is shown in Listing 15.13. The destructor is named cEdit_dtor and operates as all destructors do.
Team LRN
Chapter 15: Form and Menu Basics
191
Listing 15.13 struct cEdit * cEdit_ctor ( struct cEdit * ptr_cedit, struct Font * ptr_cfont, int buffer_size, int style, color_t color, int width );
Like all constructors, the first parameter is a pointer to the object you are constructing, and the function returns the newly constructed object’s pointer. The ptr_cfont parameter is a pointer to a Font object you wish to use for this edit box. The buffer_size parameter indicates what size text buffer you want the edit box to have. Since this is an int, the maximum size is 32,767, although you’ll rarely need one that big. The style parameter is a combination of bit flags, es_normal, es_password, es_centered, and es_readonly. The meanings of the flags are relatively obvious. The color parameter specifies what color you want to use for the text in the edit box. Finally, width is how wide the edit box should be, in pixels. Normally, the cEdit control allows any number of lines of text (as many as you can get without exceeding the number of characters for the edit box). In fact, the edit box will scroll the form vertically when there is enough text to justify doing so. For a single line edit box, you put a –1 in for width, and you get a single line edit box that scrolls horizontally instead of vertically. With cEdit, the constructor and the destructor aren’t the entire story as they were with other controls. Indeed, there are lots of tasks we can do on a cEdit box, since there are a thousand different ways to deal with text. Most commonly, you will want to be able to set or get the text stored in an edit box. This is done with the cEdit_SetText and cEdit_GetText functions, shown in Listing 15.14. Listing 15.14 int cEdit_SetText(struct cEdit* ptr_cedit,char* text); int cEdit_GetText(struct cEdit* ptr_cedit,char* text);
Both of these functions have the same parameter list; the only difference is the usage of the text parameter. The first parameter is a pointer to a cEdit object. The functions return the number of characters set or retrieved from the cEdit object, and text is either a string to copy to the cEdit object or a buffer in which to retrieve the text from the cEdit object. In CyBk15_7 on the companion CD you can find a sample program that makes an edit box on a form. It initializes the edit box to “Hello!” and the result is stored into a string buffer after the form has been closed (there are no buttons; the only way to exit this form is by pressing Esc). Listing 15.15 shows the Prog_Init function, in which all of the form stuff takes place.
Team LRN
192
Chapter 15: Form and Menu Basics
Listing 15.15 bool Prog_Init() { //initialization struct rect_t rc; int mr;
//rectangle for making form //modal result
//clear the screen DisplayGraphics_fill_screen(main_module.m_gfx,CLR_WHITE); //set up custom form rect_set(&rc,0,0,160,100); cCustomForm_ctor(&customform,&rc,NULL,TRUE,main_module.m_process); //set up the text buffer sprintf(editboxtext,"Hello!"); //create edit box cEdit_ctor(&editbox,cool_normal_font,255,es_normal,CLR_BLACK,156); //copy the text buffer to cEdit cEdit_SetText(&editbox,editboxtext); //add objects to form cCustomForm_AddObj(&customform,&editbox,0,0); //show the form (store modal result in mr) mr=cCustomForm_ShowModal(&customform); //retrieve the cEdit text cEdit_GetText(&editbox,editboxtext); //remove objects cCustomForm_RemObj(&customform,&editbox); //destroy edit box cEdit_dtor(&editbox,LEAVE_MEMORY); //destroy the form cCustomForm_dtor(&customform,LEAVE_MEMORY); //clear the screen DisplayGraphics_fill_screen(main_module.m_gfx,CLR_WHITE); DisplayGraphics_show(main_module.m_gfx); //return TRUE if program initialized, and FALSE if it did not return(TRUE); }
Team LRN
Chapter 15: Form and Menu Basics
193
As you can see, as the forms get more complicated, the code required to make them gets longer and more complicated too. This isn’t going to stop anytime soon. It is just going to get worse. To simplify our task, we will later wrap these forms into single function calls, and we’ll inherit from cCustomForm to make specialized forms and use them as components. We aren’t done with cEdit by a long shot. There are still plenty of things to learn about it. However, since this chapter is merely an introduction to the different control types, we’ll deal with cEdit again in a later chapter.
cList and cItem The cList class is even more complicated than cEdit. It is the most complicated control in the Cybiko UI. The complication comes not in cList itself, which is relatively simple on its own. Rather, the complication comes in with cItem, which is what a cList object contains. The cItem class is the base class of four other classes, each of which have their own peculiar behavior. For now, we are only going to deal with cItem; we’ll talk about the classes that derive from cItem in a later chapter.
cList The cList class is pretty straightforward. It derives from cClip, so it is like a “brother” class to cCustomForm. The only purposes of a cList object are to hold a list of cItem objects and provide functionality to the user to browse through those items. We’ll get to the cItem class in just a bit. To construct a cList object, you use cList_ctor, shown in Listing 15.16. To destroy a cList, you use cList_dtor. Listing 15.16 struct cList* cList_ctor(struct cList* ptr_list,int width);
As usual, the first parameter is a pointer to a cList that needs to be constructed, and the function returns a pointer to the newly created object. The second and only other parameter is width, which measures in pixels how wide the cList should be. You add a cList object to your form using the appropriate AddObj function, just like any other control. You can add whatever items you like to the cList object using cList_ AddItem or cList_AddItem_Ex, and you can remove items by calling cList_RemItem. Adding and removing items is shown in the discussion on cItem.
cItem A cList object is useless without any cItems. To create a cItem object, you make use of cItem_ctor, shown in Listing 15.17. To destroy a cItem, you use cItem_dtor. Actually you won’t ever have to use cItem_dtor, as we shall see in a moment.
Team LRN
194
Chapter 15: Form and Menu Basics
Listing 15.17 struct cItem* cItem_ctor ( struct cItem* ptr_citem, int width, char* title_name, bool submenu, char* right_text, struct Bitmap* ptr_bitmap );
There are lots of parameters here. Naturally, the first parameter is a pointer to an object you wish to construct, and the function returns a pointer to the newly created item. The width parameter specifies how wide this item is in pixels. It should be the same width as the cList into which it is going. The title_name parameter is the main text you wish to have shown on the item. In a menu, you generally only set this parameter. The submenu parameter makes the item display an indication that there is a submenu when this item is selected. It does not affect the behavior of this item. It only changes its appearance. The right_text parameter displays some text on the right of the item. This is used in the file manager application on the Cybiko to display the size of various files. If you have no need for this parameter, pass NULL. The ptr_bitmap parameter is a pointer to a bitmap. This bitmap is a small icon that you can have on the left of the title_name. This is also used in the file manager, which has little icons for the various file types. If you have no need for this parameter, pass NULL. Just like in cBitmap, the bitmap associated with the item is separate from the item itself, so when the item is destroyed, the bitmap still exists. Even within just the basic cItem, you have much variety. You can have it with or without text on the right. You can have it with or without a small bitmap on the left. You can indicate that a submenu follows, or not. When you make cItem objects, you really want to dynamically allocate them before constructing them. There are a few benefits to this. One, you can use the same variable to initialize all of your items. Two, you can rely on the cList object to clean up your cItem objects for you when you call the cList’s destructor. Considering the fact that you could have many cItem objects in a list, not having to clean up afterward is a boon. As stated earlier, you use cList_AddItem, cList_AddItem_Ex, and cList_RemItem to add, insert, or remove items from a list, respectively. These functions operate identically to the various AddObj, InsObj, and RemObj functions, only they deal with items rather than just any plain old object. Since I already covered the AddObj, InsObj, and RemObj functions, I’m not going to explicitly cover the Item functions, as they are the same.
Team LRN
Chapter 15: Form and Menu Basics
195
CyBk15_8 on the companion CD has a simple program that makes use of a cList object placed on a cCustomForm. Naturally, it adds a number of cItem objects to the cList object. This is all done inside of Prog_Init, which is shown in Listing 15.18. Listing 15.18 bool Prog_Init() { //initialization struct rect_t rc; //repaint OnPaint(); //set up custom form rect_set(&rc,40,25,80,50); cCustomForm_ctor(&customform,&rc,NULL,TRUE,main_module.m_process); //create the list cList_ctor(&list,76); //add items //"new" ptr_item=(struct cItem*)malloc(sizeof(struct cItem)); cItem_ctor(ptr_item,76,"New",FALSE,NULL,NULL); cList_AddItem(&list,ptr_item); //"open..." ptr_item=(struct cItem*)malloc(sizeof(struct cItem)); cItem_ctor(ptr_item,76,"Open...",FALSE,NULL,NULL); cList_AddItem(&list,ptr_item); //"save" ptr_item=(struct cItem*)malloc(sizeof(struct cItem)); cItem_ctor(ptr_item,76,"Save",FALSE,NULL,NULL); cList_AddItem(&list,ptr_item); //"save as..." ptr_item=(struct cItem*)malloc(sizeof(struct cItem)); cItem_ctor(ptr_item,76,"Save As...",FALSE,NULL,NULL); cList_AddItem(&list,ptr_item); //"exit" ptr_item=(struct cItem*)malloc(sizeof(struct cItem)); cItem_ctor(ptr_item,76,"Exit",FALSE,NULL,NULL); cList_AddItem(&list,ptr_item);
Team LRN
196
Chapter 15: Form and Menu Basics
//add list to form cCustomForm_AddObj(&customform,&list,0,0); //show the form cCustomForm_ShowModal(&customform); //remove the list cCustomForm_RemObj(&customform,&list); //destroy the list cList_dtor(&list,LEAVE_MEMORY); //destroy the form cCustomForm_dtor(&customform,LEAVE_MEMORY); //repaint OnPaint(); //return TRUE if program initialized, and FALSE if it did not return(TRUE); }
Currently, the only way to exit from the form is to hit Esc. cCustomForm_ShowModal doesn’t do much to help us. In order to customize the behavior of our forms, we will have to start deriving our own form classes from cCustomForm, which we will do next. You now have knowledge of most of the controls available to you as part of the Cybiko UI. Sure, we haven’t covered all of them, but we will eventually. You now have more than enough of a foundation to get stuff done. NOTE: Just a note about cList widths: When you have a title bar for your form (i.e., the third parameter of cCustomForm_ctor is not NULL), there is some odd behavior when you make a cList with a width of the form’s width minus four, unless you are using a fullscreen window. When you have a title for the menu in a window that is not fullscreen, you will get a little icon in the title bar that points to the right. I have no idea why. So, in these cases, use the form width minus six. How can you tell if a form is fullscreen? Simple... the rectangle bounding it will have an x,y coordinate of (–2,–2), and a width and height of 164 and 104. You can basically just check that the width is 164.
Custom Form Behavior cCustomForm_ShowModal only does so much. If there are buttons on the form, it will return when one is pressed. When Esc is pressed, it will also exit the function. However, that’s just not good enough. For example, in CyBk15_8, we had a little menu. It would be ideal that when Enter was pressed, cCustomForm_ShowModal would return,
Team LRN
Chapter 15: Form and Menu Basics
197
and then we could grab whichever item had been selected and do something with that information. To do that, however, we will need to derive our own class from cCustomForm, and add this behavior ourselves. The first part of that task is creating a new struct that derives from cCustomForm. To this struct we will add all of the controls we will need to use on this form. Listing 15.19 shows what I mean. Listing 15.19 struct cMenuForm: public cCustomForm { struct cList* ptr_list; };
Next, we need a few functions. At the very least, we will need cMenuForm_ctor, cMenuForm_dtor, and cMenuForm_ShowModal. These are shown in Listing 15.20. Listing 15.20 struct cMenuForm* cMenuForm_ctor( struct cMenuForm* ptr_menuform, struct rect_t* ptr_rect, char * name, bool round, struct cWinApp* ptr_winapp ); void cMenuForm_dtor(struct cMenuForm* ptr_menuform,int mem_flag); int cMenuForm_ShowModal(struct cMenuForm* ptr_menuform);
We could have some others as well, but this is fine for a starting point. It will get us most of the way there. In cMenuForm_ctor, your code should look like Listing 15.21. Listing 15.21 struct cMenuForm* cMenuForm_ctor( struct cMenuForm* ptr_menuform, struct rect_t* ptr_rect, char * name, bool round, struct cWinApp* ptr_winapp ) { //inherited constructor cCustomForm_ctor(ptr_menuform,ptr_rect,name,round,ptr_winapp); //allocate and construct cList ptr_menuform->ptr_list=(struct cList*)malloc(sizeof(struct cList)); cList_ctor(ptr_menuform->ptr_list,ptr_rect->w-6); //add cList to form cCustomForm_AddObj(ptr_menuform,ptr_menuform->ptr_list,0,0); //return newly created object
Team LRN
198
Chapter 15: Form and Menu Basics
return(ptr_menuform); }
In this function, we construct the form according to the parameters passed. We then allocate and create the cList object. Finally, we add the cList to the form. In cMenuForm_dtor (shown in Listing 15.22), we simply clean up the object. Listing 15.22 void cMenuForm_dtor(struct cMenuForm* ptr_menuform,int mem_flag) { //clean up the cList cList_dtor(ptr_menuform->ptr_list,FREE_MEMORY); //inherited destructor cCustomForm_dtor(ptr_menuform,mem_flag); }
The destructor is pretty simple. We destroy the cList (with FREE_MEMORY, since we dynamically allocated it), and then we pass the pointer to the form along to the base class’s destructor. Finally, we make cMenuForm_ShowModal. It is shown in Listing 15.23. Listing 15.23 int cMenuForm_ShowModal(struct cMenuForm* ptr_menuform) { //variables struct Message* ptr_msg; bool done; //set the modal result ptr_menuform->ModalResult=mrNone; //set done flag to FALSE done=FALSE; //loop while not done while(!done) { //show the form cCustomForm_Show(ptr_menuform); //get a message ptr_msg=cWinApp_get_message(ptr_menuform->CurrApplication,0,1,MSG_USER); //process the message switch(ptr_msg->msgid) { case MSG_QUIT: case MSG_SHUTUP: { //we are done done=TRUE;
Team LRN
Chapter 15: Form and Menu Basics
199
}break; case MSG_KEYDOWN: { //check for enter key if(Message_get_key_param(ptr_msg)->scancode==KEY_ENTER) { ptr_menuform->ModalResult=mrOk; break; } //check for Esc key if(Message_get_key_param(ptr_msg)->scancode==KEY_ESC) { ptr_menuform->ModalResult=mrCancel; break; } //default message handling cCustomForm_proc(ptr_menuform,ptr_msg); }break; default: { //default message handling cCustomForm_proc(ptr_menuform,ptr_msg); }break; } //delete the message Message_delete(ptr_msg); //if the modal result has changed, we are done if(ptr_menuform->ModalResult!=mrNone) done=TRUE; } return(ptr_menuform->ModalResult); }
In this function, we do our own modal loop. First, we set up the ModalResult member of ptr_menuform. I haven’t discussed this member before, but it is just an int in which you store the modal result of your form. Next, we start the modal loop itself. Three things take place in the modal loop. One, the form is shown. Two, we wait for a message. Three, we process that message. When we receive a MSG_QUIT or a MSG_SHUTUP, we must exit the modal loop, but we don’t change the modal result. This is the behavior of cCustomForm as well, so we should keep it consistent. The only two things we are customizing behavior for is during the MSG_KEYDOWN, and then, we only care about KEY_ESC and KEY_ENTER. For all other keys being pressed and all other messages, we rely on default message processing, à la cCustomForm_proc, shown in Listing 15.24. Listing 15.24 bool cCustomForm_proc ( struct cCustomForm * ptr_custom_form,
Team LRN
200
Chapter 15: Form and Menu Basics
struct Message * ptr_message );
This function takes a pointer to a cCustomForm object (or an object derived from a cCustomForm) and a pointer to a Message object. It returns TRUE if the message was processed, and FALSE if it was not. It takes care of all of the cCustomForm processing that we are not overriding. Now we have a nice class for our menus, or at least the very beginnings of a class. There is more we need to do. For example, there is no provision for adding menu items to the list, except manually using the cList object pointer, ptr_list, in the form. It seems like we should just be able to call a function called cMenuForm_AddMenuItem and have the appropriate item added for us. Also, we currently have no way to determine which menu item was selected, so a cMenuForm_GetSelectedItem would be in order. It should return the number of the item selected in the menu, starting with zero for the topmost item. There are no functions anywhere that tell you the width of a cList. When creating items for a cList, we need to specify a width, so we should probably store it in the cMenuForm struct. Finally, we should have #defines to make cMenuForm versions of all of the rest of the cCustomForm functions, just to be consistent. CyBk15_9 on the companion CD contains a program that makes use of the cMenuForm class that I developed. All of the code that shows the menu form is in Prog_Init and is shown in Listing 15.25. As you can see, it’s a lot shorter than the code used to make a menu in Listing 15.18. Listing 15.25 bool Prog_Init() { //initialization struct rect_t rc; int menuresult; //repaint OnPaint(); //construct menu form rect_set(&rc,30,0,100,100); cMenuForm_ctor(&menuform,&rc,"Main Menu",FALSE,main_module.m_process); //add items to menu cMenuForm_AddMenuItem(&menuform,"New"); cMenuForm_AddMenuItem(&menuform,"Open..."); cMenuForm_AddMenuItem(&menuform,"Save"); cMenuForm_AddMenuItem(&menuform,"Save As..."); cMenuForm_AddMenuItem(&menuform,"Exit"); //show the menu cMenuForm_ShowModal(&menuform); menuresult=cMenuForm_GetSelectedMenuItem(&menuform);
Team LRN
Chapter 15: Form and Menu Basics
201
//destroy the menu cMenuForm_dtor(&menuform,LEAVE_MEMORY); //repaint OnPaint(); //return TRUE if program initialized, and FALSE if it did not return(TRUE); }
Figure 15.1 shows the menu displayed in this program. Figure 15.1: Output of CyBk15_9
cObject and cClip Common Functionality You now have just about all the knowledge you need to start dealing with various types of Cybiko forms. There will be more later on, of course, but you’ve got a solid foundation, and you’ll be able to accomplish most of what you want to. There is, however, one more topic I want to talk about before we end the chapter and move on to other things, and that is the common functionality of cObject and cClip. We already know that cObject is the base class of all Cybiko UI classes, and cClip is the base class for cCustomForm, cList, and others. These two important classes provide common functionality for all of their derived classes.
cObject Common Functionality The cObject class provides some base functionality for all other Cybiko UI classes. This includes control over the visibility of the control, whether the control can receive input focus, and other important common functionalities. To draw a control, use cObject_Show. To hide a control, use cObject_Hide. These functions are shown in Listing 15.26, and their uses should be fairly obvious. Listing 15.26 void cObject_Hide(struct cObject* ptr_object); void cObject_Show(struct cObject* ptr_object);
To enable or disable a control, use cObject_Enable or cObject_Disable, as appropriate. When a control is enabled, it can receive input focus (i.e., when pressing Tab to move
Team LRN
202
Chapter 15: Form and Menu Basics
from control to control, a control that is disabled will be skipped). These functions are shown in Listing 15.27, and like Show and Hide, their uses are fairly obvious. Listing 15.27 void cObject_Disable(struct cObject* ptr_object); void cObject_Enable(struct cObject* ptr_object);
Finally, Listing 15.28 contains a hodgepodge of miscellaneous functions that didn’t fit into any particular group. Listing 15.28 struct cClip* cObject_GetParent(struct cObject* ptr_object); void cObject_Disconnect(struct cObject* ptr_object); bool cObject_Select(struct cObject* ptr_object); void cObject_update(struct cObject* ptr_object);
cObject_GetParent gets the parent of the control, i.e., whatever we used AddObj on to add this control. cObject_Disconnect will remove a control from its parent (essentially the same thing as using RemObj). cObject_Select makes that control the selected control (i.e., have the input focus), and finally cObject_update updates the appearance of a control. Of this common functionality, the stuff you are most likely to use is cObject_Disable, and then only when creating a form and specifying which controls cannot receive input focus. You will most likely call this on cBevels, cBitmaps, cBoxes, and cTexts, since they don’t need input focus ever.
cClip Common Functionality You have already used some of this functionality in the context of the cCustomForm class, but I’m listing all of it here for completeness. The first group of functions, shown in Listing 15.29, adds, inserts, and removes controls to and from the cClip object in question. I have already covered these functions, so I’m just going to list them. Listing 15.29 void cClip_AddObj(struct cClip* ptr_clip, struct cObject* ptr_object, int x, int y); void cClip_InsObj(struct cClip* ptr_clip, struct cObject* ptr_object, int x, int y, int index); void cClip_RemObj(struct cClip* ptr_clip, struct cObject* ptr_object);
This next group allows you to maneuver through the control list of the cClip object. These are shown in Listing 15.30. Listing 15.30 bool cClip_SelectFirst(struct cClip* ptr_clip); bool cClip_SelectPrev(struct cClip* ptr_clip, bool wrap); bool cClip_SelectNext(struct cClip* ptr_clip, bool wrap);
Team LRN
Chapter 15: Form and Menu Basics
203
cClip_SelectFirst, to no one’s surprise, selects the first control in the cClip object. cClip_SelectPrev and cClip_SelectNext select the controls before or after (respectively) the currently selected control. The wrap parameter specifies whether you want to go from first to last or last to first if you are at the beginning or the end of the control list, respectively. In all cases, the various Select functions return TRUE if the operation was successful, and FALSE if it was not. Scrolling isn’t something we’re really going to get into now; however, Listing 15.31 lists the scrolling functions available to the cClip class. Listing 15.31 void cClip_Scroll(struct cClip* ptr_clip, struct rect_t* rectangle); void cClip_Scroll_Ex(struct cClip* ptr_clip, int x, int y); void cClip_SendScroll(struct cClip* ptr_clip); int cClip_GetShifty(struct cClip* ptr_clip); int cClip_GetShiftx(struct cClip* ptr_clip);
Finally, let’s look at some informational functions. How many controls belong to this cClip? What controls belong to this cClip? Where in the list of controls owned by this cClip can I find a control? What control in this cClip is currently selected? The functions in Listing 15.32 answer all of these questions. Listing 15.32 int cClip_GetCount(struct cClip* ptr_clip); struct cObject* cClip_get_by_index(struct cClip* ptr_clip, int index); int cClip_FindObj(struct cClip* ptr_clip, struct cObject* ptr_object); struct cObject* cClip_GetSelectedObject(struct cClip* ptr_clip);
cClip_GetCount returns how many controls belong to this cClip. cClip_get_by_index returns a control at a particular position. cClip_FindObj looks for a control in the control list for a cClip object, returning –1 if it is not found. cClip_GetSelectedObject returns the control that is currently selected. To determine the index of the currently selected control, you first use cClip_GetSelectedObject and pass the result of that function call into cClip_FindObj.
Summary As I stated in the beginning, the Cybiko UI is the most complicated part of the SDK. I hope that I have demystified some of it for you. We certainly aren’t done with the UI system, but you should now be able to make most of the forms that you want to. Hopefully you have seen that it isn’t so hard after all.
Team LRN
Chapter 16
More Miscellaneous Overview We’ve learned a lot about graphics and forms so far, but there are a few extra tidbits you need to know about to round out your Cybiko programming knowledge. For example, in Chapter 12, we talked about the Input and FileInput classes. Now we will talk about the Output and FileOutput classes, so you will be able to write to resources and files. Also, we will talk a bit about message buffers, which aren’t commonly used, but can be quite helpful. Finally, we will discuss the DirectKeyboard class. This class is an absolute must for games, as we shall soon see.
Output and FileOutput Just as we had the Input and FileInput classes to open files and resources for reading, so do we have Output and FileOutput classes for writing to these places. These classes will be rather easy for you to learn because they use the same functions as Input and FileInput, just with “write” rather than “read.”
Creating Output and FileOutput Objects The Output class, like the Input class, has no constructor. In order to open one, you must use a function external to the Output class functions. Namely, you must use Archive_open_write or Archive_open_write_Ex. These are shown in Listing 16.1 Listing 16.1 struct Output* Archive_open_write (struct Archive* ptr_archive, int entry) ; struct Output * Archive_open_write_Ex ( struct Archive * ptr_archive, char * sz_name );
Both of these functions return pointers to Output objects. The first parameter in each is a pointer to an Archive object. We haven’t talked about Archive objects yet, but we will in just a moment. The second parameter is either a numeric value or the name of
204
Team LRN
Chapter 16: More Miscellaneous
205
a resource, depending on which function you are using. Most of the time, you will want to use the name of the resource, but in especially large projects, you might not store the names of your resources in order to save space. Now, about archives. An Archive object encapsulates an .app file on the Cybiko. We’ll cover it in greater detail in a later chapter. Each program has its own Archive, and a pointer to this is stored in main_module.m_process->module->archive, so you can pass this value to Archive_open_write or Archive_open_write_Ex to open an Output object to your own resources. We will cover opening resources in external files later. To use FileOutput, you make use of a constructor, and optionally the FileOutput_open function. These are shown in Listing 16.2. Listing 16.2 struct FileOutput* FileOutput_ctor (struct FileOutput* ptr_file_output) ; struct FileOutput* FileOutput_ctor_Ex ( struct FileOutput* ptr_file_output, char* sz_file_name, bool create ); bool FileOutput_open (struct FileOutput* ptr_file_output, char* sz_file_name, bool create);
In FileOutput_ctor, you supply a pointer to an object you wish to construct, and it returns the pointer to the newly constructed (unopened) FileOutput object. For FileOutput_ctor_Ex, you also supply a filename, and a TRUE or FALSE indicating whether you want to create the file if it does not already exist. FileOutput_open has the same parameter list as FileOutput_ctor_Ex, but returns TRUE or FALSE depending on the success of the operation. I suggest using FileOutput_ctor and FileOutput_open, since you can then check for whether or not the file was opened.
Destroying Output and FileOutput Objects To destroy an Output or FileOutput object, you use the appropriate destructor, Output_dtor or FileOutput_dtor. In the case of Output objects, you will always have pointers to them, so you will need to use FREE_MEMORY. In the case of FileOutput objects, they may or may not be dynamically allocated by you, so use the appropriate memory flag.
Getting Information about Output/FileOutput Objects Like in the Input/FileInput classes, there exist some Output/FileOutput functions that will let you know what the condition of the object is. These are shown in Listing 16.3.
Team LRN
206
Chapter 16: More Miscellaneous
Listing 16.3 //these functions also have FileOutput versions int Output_get_flags (struct Output* ptr_output); bool Output_is_eof (struct Output* ptr_output); bool Output_is_bad (struct Output* ptr_output); bool Output_is_good (struct Output* ptr_output); long Output_get_size (struct Output* ptr_output); long Output_tellp (struct Output* ptr_output); //this function only works on FileOutput long FileOutput_tell (struct FileOutput* ptr_file_output);
All of these functions work in the same manner as their Input/FileInput equivalents. You can determine if your stream is good, bad, at the end of the file, what size it is, and what position you are currently at. Notice that there is an Output_tellp. The Input version is Input_tellg. I imagine that with the Input class, the “g” was for “get,” and in the Output class, the “p” is for “put.” That’s just a theory, however.
Writing to a File or Resource To write to an Output or FileOutput object, you use one of the four writing functions shown in Listing 16.4. Listing 16.4 long Output_write (struct Output* ptr_output, void* buffer, long length) ; int Output_write_byte (struct Output* ptr_output, int byte); short Output_write_word (struct Output* ptr_output, short word); long Output_write_dword (struct Output* ptr_output, long dword);
These functions mirror the corresponding read function from the Input class. There are FileOuput versions of each of these functions. The return value of Output_write is the actual number of bytes written, and it may be less than the length parameter (so you may want to check that). In the case of Output_write_byte, Output_write_word, and Output_write_dword, the return value is the value written to the stream, or –1 if it failed.
Navigating through a File Finally, there are a few extra little functions you use to navigate through an output stream. These are shown in Listing 16.5. Listing 16.5 long long bool
Output_seekp (struct Output* ptr_output, long pos, seek_t mode) FileOutput_seek (struct FileOutput* ptr_file_output, long pos, seek_t mode) FileOutput_truncate (struct FileOutput* ptr_file_output, long position)
Output_seekp and FileOutput_seek operate in the same manner as Input_seekg and FileInput_seek. The information about the “seek mode” applies here as well (see Chapter 12).
Team LRN
Chapter 16: More Miscellaneous
207
FileOuput_truncate is for FileOutput objects only. It will truncate, or cut short, an external file at the position specified. Resources are of a fixed size, and hence they cannot be truncated.
About Writing to Resources An important thing to remember about resources, if you are going to write to them, is that they are of a fixed size. They cannot expand or contract. Also, if you wish to write to a resource, you cannot have it compressed (i.e., you will have to put a hyphen (-) in front of the resource’s name in filer.list). In addition, you will have to make the resource the proper size before building the application archive. If you need to write variable-sized streams, it is best to store this as an external file and use FileOuput.
Buffers A Buffer object represents nothing more than a block of memory for you to work with. Even better, CyOS can move the memory referenced by CyOS into a more efficient location. Buffers can be resized, and you can write any sort of data to them. They can be attached to Message objects. They are very cool things to use to store information. Since they can do so much, they naturally have a lot of functions dealing with them.
Creating and Destroying Buffers To create a buffer, you use either Buffer_ctor or Buffer_ctor_Ex, shown in Listing 16.6. Listing 16.6 struct Buffer* struct Buffer*
Buffer_ctor (struct Buffer* ptr_buffer, size_t size, size_t increment) Buffer_ctor_Ex (struct Buffer* ptr_buffer, struct Buffer* templ)
With Buffer_ctor, you specify the size of the buffer, as well as an increment value. The increment value is how much the Buffer object will be expanded by if you write too much to it. More about increment values in a moment. With Buffer_ctor_Ex, you copy the attributes of an already existing Buffer object. To destroy a buffer, use Buffer_dtor. Now, about the increment value for Buffer_ctor. A Buffer object will expand if you write more than it can handle. Let’s say that you make a Buffer object that is initially allocated to 1,000 bytes. Now, let’s say that you have written 999 of those bytes, and then you write another 10 bytes onto it. This is not a problem. The buffer will reallocate itself, adding a number of bytes equal to the increment value. If, for example, you were using an increment value of 25 in this case, the Buffer object would reallocate
Team LRN
208
Chapter 16: More Miscellaneous
itself to contain 1,025 bytes, which easily will contain the 999 already existing bytes as well as the additional 10 bytes you are writing to it. Deciding on a proper increment value is a tricky process. You don’t want it to be too small, because reallocating the buffer takes time and you don’t want to be reallocating it several dozen times. You also don’t want it too large, since too many extra bytes is a waste of space. Choosing the right value depends also on what type of data you are storing in the buffer. If you are storing strings, you might want a larger increment value, such as 64. If you are storing long integers, you might only want a 16.
Retrieving Information There comes a time in every buffer’s life when you just need to ask “how big are you?” There are two meanings for size where a buffer is concerned. There is “size,” and there is “allocated size.” The functions for retrieving these values are shown in Listing 16.7. Listing 16.7 size_t size_t
Buffer_get_size (struct Buffer* ptr_buffer) Buffer_get_allocated_size (struct Buffer* ptr_buffer)
So, what is the difference between size and allocated size? The size (retrieved by Buffer_get_size) is how much space is currently occupied in the Buffer object. The allocated size (retrieved by Buffer_get_allocated_size), is the number of bytes allocated to the buffer in the constructor (almost always larger than the value returned by Buffer_get_size). By using Buffer_get_size, you can start with an empty Buffer object, and fill it up sequentially with strings or whatever data you like.
Locking and Unlocking Buffers A Buffer object is nothing more than a chunk of memory that can be moved around by CyOS. Since it can be moved around by the operating system, and since you cannot be sure when it will be moved or if it will be moved, you have to ensure that it will not be moved while you are reading or writing to it. To do this, you lock the buffer (using Buffer_lock) before writing to it or reading from it. When you are finished writing to it, you unlock it (using Buffer_unlock). If you aren’t sure whether or not a Buffer object is currently locked, you can call Buffer_is_locked to find out. These three functions are shown in Listing 16.8. Listing 16.8 bool Buffer_is_locked (struct Buffer* ptr_buffer) void* Buffer_lock (struct Buffer* ptr_buffer) void Buffer_unlock (struct Buffer* ptr_buffer)
Each of these functions takes as a parameter a pointer to a Buffer object. The only difference is in the return value or the lack of one. Buffer_is_locked returns either
Team LRN
Chapter 16: More Miscellaneous
209
TRUE or FALSE, depending on the locked state of the Buffer object. Buffer_lock returns a pointer to the Buffer object’s allocated area. The documentation says to be careful with this pointer because it can cause the device to lock up. Finally, Buffer_unlock unlocks the buffer. The pointer returned by Buffer_lock is no longer valid after a call to Buffer_unlock. You probably won’t lock and unlock a Buffer object yourself, but rather make use of the storing and reading functions of the Buffer class, which automatically lock and unlock the buffer as appropriate. You only want to use lock/unlock when accessing the memory of a buffer directly.
Storing and Reading Information There are a number of “helper” functions in the Buffer class that you can use to store or retrieve data from the Buffer object. You can store or retrieve individual characters, integers, long integers, strings, and even randomly structured blocks of memory. Ergo, every type of data you have in your programs can be stored in a Buffer object, and later retrieved from that Buffer object. All of these functions are shown in Listing 16.9. Listing 16.9 void*
Buffer_load (struct Buffer* ptr_buffer, void* ptr_data, size_t offset, size_t length) void Buffer_store (struct Buffer* ptr_buffer, void* ptr_data , size_t offset, size_t length) char* Buffer_load_string (struct Buffer* ptr_buffer, char* str, size_t offset, size_t length) void Buffer_store_string (struct Buffer* ptr_buffer, char* str, size_t offset) char Buffer_get_char (struct Buffer* ptr_buffer, size_t offset) void Buffer_set_char (struct Buffer* ptr_buffer, size_t offset, char data) int Buffer_get_int (struct Buffer* ptr_buffer, size_t offset) void Buffer_set_int (struct Buffer* ptr_buffer, size_t offset, int data) long Buffer_get_long (struct Buffer* ptr_buffer, size_t offset) void Buffer_set_long (struct Buffer* ptr_buffer, size_t offset, long data)
The three fundamental types (char, int, and long) can be stored with a call to Buffer_set_char, Buffer_set_int, or Buffer_set_long respectively. The functions are essentially the same, regarding parameters. You send the buffer you are storing the information in, the position in which you are storing the information, and what information you are storing at that location. To read chars, ints, or longs from a Buffer object, you use Buffer_get_char, Buffer_get_int, or Buffer_get_long (whichever applies). For the get functions, you only specify what Buffer you are reading from and where in the buffer you are reading. For strings and randomly structured memory blocks, you use the load and store functions: Buffer_store_string (for strings) or Buffer_store (for randomly structured memory blocks) to store data, and Buffer_load_string (for strings) or Buffer_load (for randomly structured memory blocks) to read data.
Team LRN
210
Chapter 16: More Miscellaneous
To store a string, you use Buffer_store_string. The parameters are a pointer to the Buffer object, a pointer to the string you wish to store, and the offset in the buffer in which you wish to store it. To load a string, you use Buffer_load_string, with the parameters of the buffer from which you are reading, a string buffer into which you want the string loaded, the offset from which you are reading, and the maximum length to read. To store a random block of memory, you use Buffer_store and Buffer_load. These are mostly similar to the string storing and loading functions, except that in the store function, you specify how large the block of memory is (with a string, it autodetects the null terminator).
Buffer Sizing Since a buffer is a resizable chunk of memory, it only makes sense that you can modify its size. Indeed, the functions listed in Listing 16.10 show how to do just that. Listing 16.10 void bool void bool
Buffer_compact (struct Buffer* ptr_buffer) Buffer_set_size (struct Buffer* ptr_buffer, size_t size) Buffer_free (struct Buffer* ptr_buffer) Buffer_ensure_size (struct Buffer* ptr_buffer, size_t test_size)
Buffer_compact reallocates the Buffer object so that it just barely contains the amount of memory required by the data stored in it. That is, after a call to Buffer_compact, Buffer_get_size and Buffer_get_allocated_size will be equal. You want to do this before attaching the Buffer object to a Message object (which I’ll get to in a moment). Buffer_set_size naturally allows you to set the size of a buffer. Be careful with this. If you set the size to smaller than what it takes to store all of the data in the buffer, the extra data at the end will be lost. This function returns TRUE if the Buffer object was successfully reallocated, and FALSE if it was not. To completely clear out a Buffer object and start from scratch, use Buffer_free followed by a call to Buffer_set_size. Buffer_ensure_size is a safer way to resize a Buffer object in which you already have stored data. If the test_size is smaller than the value returned from Buffer_get_size, then no reallocation occurs.
Buffers and Messages As stated earlier, you have the ability to attach buffers to messages. Normally, you do this for Message objects you are going to send to other Cybikos, which we haven’t talked about yet. Still, I’d rather talk about this while on the topic of buffers rather than on the topic of communications. The three functions for using Buffer objects with Message objects are shown in Listing 16.11.
Team LRN
Chapter 16: More Miscellaneous
211
Listing 16.11 bool Message_has_buffer (struct Message* ptr_message) struct Buffer* Message_get_buffer (struct Message* ptr_message) void Message_attach_buffer (struct Message* ptr_message, struct Buffer* ptr_buffer)
To attach a buffer to a message, use Message_attach_buffer. The parameters are the message you want the buffer attached to and the buffer you are attaching. There is no return value. To see if a message has an attached buffer, use Message_has_buffer. You’ll get TRUE if there is a buffer, and FALSE if there is none. Finally, to gain access to the attached buffer, use Message_get_buffer. This returns a pointer to the buffer attached to the message, which you can then use for reading information.
DirectKeyboard Up until now, all of our keyboard input has been through Message objects. While this isn’t necessarily a bad way to go about it, in games, we usually don’t care when a key is pressed or released. We only care if a key is currently up or down. This is the functionality we get from the DirectKeyboard class. DirectKeyboard is a rather simple part of the SDK. It consists of four functions, and those four functions are all we will ever need; we can say goodbye to all of the MSG_KEYUP and MSG_KEYDOWN processing (except in circumstances where we actually need such processing). The four DirectKeyboard functions are shown in Listing 16.12. Listing 16.12 struct DirectKeyboard* DirectKeyboard_get_instance (void); void DirectKeyboard_dtor (struct DirectKeyboard* ptr_direct_keyboard, int memory_flag); void DirectKeyboard_scan (struct DirectKeyboard* ptr_direct_keyboard); bool DirectKeyboard_is_key_pressed (struct DirectKeyboard* ptr_direct_keyboard, short scancode);
There is no constructor for a DirectKeyboard object. You never have to allocate one. To make use of DirectKeyboard, you make a DirectKeyboard pointer and set it equal to the return value of DirectKeyboard_get_instance. When you are cleaning up, you call DirectKeyboard_dtor, with the pointer set to the DirectKeyboard object you have been using and FREE_MEMORY for the memory_flag. You will always use FREE_MEMORY to destroy DirectKeyboard objects. Periodically, you need to use DirectKeyboard_scan. This loads in the current states of all of the keys into the DirectKeyboard object. When using DirectKeyboard, I generally do this during MSG_KEYDOWN (if the key is not a repeat), MSG_KEYUP, and MSG_GOTFOCUS. These are the only times when the state of the keyboard could have changed, so you don’t really need to scan it at any other time.
Team LRN
212
Chapter 16: More Miscellaneous
Finally, to check whether a key is up or down, you use DirectKeyboard_is_ pressed, with the scancode of the character you are checking. That’s all there is to the DirectKeyboard class. It is incredibly simple to use.
Summary This chapter was sort of a hodgepodge of information, including output to resources and files, buffers, messages with buffers, and finally direct access to the keyboard. Thesse topics didn’t really fit elsewhere, and they weren’t large enough topics to be chapters on their own. I now confer upon you the title of “Intermediate Cybiko Programmer.” From here on out, we will be mostly refining the skills you have learned so far, and probing the often untouched classes that deal with the Cybiko.
Team LRN
Chapter 17
Advanced Cybiko Graphics Overview No, we haven’t finished with Cybiko graphics. There is still a lot of ground to cover, especially in the 3D graphics department, which we will talk about in this chapter. Before we get to that, however, there are still a few functions I have yet to cover regarding the Graphics and DisplayGraphics classes.
Direct Memory Access We’ve talked at great length about the Graphics and DisplayGraphics classes. We’ve covered drawing pixels and other primitives, and so forth. There is another way to access the graphical data associated with these objects: we can read or write directly to memory. Most of the time, we don’t want to, but occasionally, there arises a time when there is no better way. So, in Listing 17.1, there are the Graphics versions of how we access the screen buffer itself. There are DisplayGraphics versions for each of these functions. Listing 17.1 int Graphics_get_bytes_total ( char * Graphics_get_buf_addr (
struct Graphics * ptr_gfx ); struct Graphics * ptr_gfx );
The first function, Graphics_get_bytes_total, takes as a parameter a pointer to a Graphics object, and returns the size, in bytes, taken up by that Graphics object’s screen buffer. Since a Graphics object renders onto a Bitmap object, this tells us how many bytes are taken up by that bitmap. The second function, Graphics_get_buf_addr, takes as a parameter a pointer to a Graphics object, and returns a pointer to the actual screen buffer.
213
Team LRN
214
Chapter 17: Advanced Cybiko Graphics
So, with these functions, you can know how many bytes are in a screen buffer and where those bytes are located. This doesn’t help us much, yet, but it will eventually get us where we want to go. Consider Listing 17.2. Listing 17.2 //gfx is a Graphics object, associated with some bitmap char* buf; int bufsize; int index; //grab the buffer address buf=Graphics_get_buf_addr(&gfx); //get the size of the buffer address bufsize=Graphics_get_bytes_total(&gfx); //clear the buffer to all zeros for(index=0;index
As you might expect, this snippet of code will clear all of the bytes in the Bitmap object associated with gfx to 0. However, we have not yet talked about what the meaning of each byte is, so we don’t know what was actually done.
Pixel Formats The meaning of a byte in a screen buffer depends on whether the Bitmap object is monochrome or four color. In a monochrome Bitmap object, one bit represents one pixel, and there are eight pixels per byte. In a four-color Bitmap object, two bits represent a pixel, and so there are four pixels per byte.
Monochrome Pixel Format There are eight pixels per byte in the monochrome format. This is the format most commonly used by fonts. The number of bytes per line depends on the width of the image. A horizontal row of pixels takes up a whole (not fractional) number of bytes, so if you have a 10 pixel wide image, it takes up 2 bytes rather than 1 byte and 2 bits. This horizontal row is more commonly called a scan line. A bitmap has one scan line for each y position, from 0 to height –1. To determine the number of bytes in a scan line, you can either take pixel width of the image, add 7, then divide by 8 (discarding all fractions), or you can divide the size of the screen buffer (retrieved from Graphics_get_bytes_total) by the height of the image in pixels. Either way will work. This value (the number of bytes per scan line) is referred to as the “pitch” of the image, i.e., the number of bytes you need to add to move down the image by one pixel. The first byte in a monochrome bitmap represents the leftmost eight pixels in the topmost row. If the pitch is larger than one byte per scan line, the second byte
Team LRN
Chapter 17: Advanced Cybiko Graphics
215
represents the eight pixels to the right of the leftmost eight pixels. Each byte adds another eight pixels on the right of the image. So, to determine in which byte the pixel at x,y is located, you can use the following calculations: Listing 17.3 pitch=(width+7)/8; ypos=y*pitch; xpos=x/8; bytepos=ypos+xpos;
Here, bytepos is the index into the array pointed to by the screen buffer. We’re getting closer to actually getting something useful. Now that we’ve got it narrowed down to an individual byte, we need to get down to an individual pixel, which means we have to do some bitwise operations. The byte itself is represented backward. Bit 7 is the first pixel, and bit 0 is the last pixel. The pixel position within the byte is x%8, and the bit position for this pixel is 7 minus this value, so the bit position can be calculated as follows: Listing 17.4 xpel=7–x%8;
Now, we must determine a set and reset mask for this pixel (since we don’t want to modify any of the other pixels in the byte). We do this like so: Listing 17.5 setmask=1<<xpel; resetmask=~setmask;
We can (finally) do some pixel manipulation. We can set the pixel to either 1 or 0, retrieve the value of the pixel (TRUE or FALSE), or toggle the pixel from on to off or vice versa. Listing 17.6 //Setting the pixel //to 1 buf[bytepos]|=setmask; //to 0 buf[bytepos]&=resetmask; //retrieving the pixel if(buf[bytepos]&setmask!=0) { //pixel is set } else {
Team LRN
216
Chapter 17: Advanced Cybiko Graphics
//pixel is not set } //toggling the pixel buf[bytepos]^=setmask;
As you can see, there is a lot of tomfoolery involved in even the simplest of pixel manipulations. Also, any code you are likely to write to set or get an individual pixel in this way is likely to be slower than the Graphics_set_pixel and Graphics_get_pixel functions, so you probably don’t want to write your own pixel plotting routine.
Four-Color Pixel Format In the four-color pixel format, which is the one you will see most often, including the display itself, there are 2 bits per pixel and as a result, 4 pixels per byte. As in the monochrome bitmap, each scan line takes up a whole number of bytes, so if you have a width of 10 pixels, you have a width of 3 bytes rather than 2½ bytes. To determine the number of bytes in a scan line of a four-color image, you use the following calculation—(width+3)/4—or you can just divide the result of Graphics_get_ bytes_total by the height. Since the byte length of a scan line is calculated differently from width for the two formats, but the calculation based on total bytes remains the same, you can use this to determine whether an unknown bitmap is mono or four color as follows: Listing 17.7 bytewidth2=(width+7)/8; //mono calculation based on width bytewidth4=(width+3)/4; //4 color calculation based on width bytewidth=(totalbytes)/height;calculation based on total bytes and height if(bytewidth==bytewidth2) { //monochrome image } if(bytewidth==bytewidth4) { //four-color image }
This can be useful because there is no method of determining the format of a bitmap any other way. The pitch and byte positions are calculated in a similar manner to that of the monochrome image format, just using the number 4 rather than the number 8. Listing 17.8 pitch=(width+3)/4; ypos=y*pitch; xpos=x/4; bytepos=ypos+xpos;
To get the pixel location within a byte, which like the mono format is still ordered backward, we can use the following calculation.
Team LRN
Chapter 17: Advanced Cybiko Graphics
217
Listing 17.9 xpel=(3–x%4);
Getting and setting individual pixels becomes a little more complicated, because now we are setting or getting two bits instead of just one. To get a pixel, we can’t just check a single bit and return TRUE if the result is non-zero. To set a pixel, we have to first reset the pixel so that we don’t merge two different pixel values. In order to do this, we need more values in order to do stuff right. Listing 17.10 bitshift=(xpel*2) getmask=3<
For setmask only, color is the color of the pixel you wish to set: 0 is white, 1 is light gray, 2 is dark gray, and 3 is black. Listing 17.11 shows some pixel operations for the four-color format. Listing 17.11 //set pixel buf[bytepos]=buf[bytepos] & resetmask | setmask; //get pixel value=((buf[bytepos] & getmask) >> bitshift) & 3;
As you can see, this is a little more involved than the monochrome calculations, and as a result, they are even slower. All of these calculations, however, are very similar to what is going on inside of Graphics_set_pixel and Graphics_get_pixel.
Using Direct Memory Access for Drawing So, if individual pixel writing is not such a good idea (indeed, my tests and those of others have shown that doing pixel writing with direct memory manipulation is a great deal slower than just using the pixel functions of the Graphics class), what should we use direct memory access for? One thing we might use it for is for copying bit patterns. The uses for this are many. Using direct memory access, we can set up to 16 pixels for a four-color bitmap or up to 32 pixels for a mono bitmap at a time. We can do this by writing entire ints (8 four-color or 16 mono) or longs (16 four-color or 32 mono) rather than chars. Also, we can use any bitwise operator we like during these operations, which can allow us to do transparency without giving up a color. Here are some examples of setting multiple-length bit patterns. Listing 17.12 //four pixels char* buf; buf=DisplayGraphics_get_buf_addr(main_module.m_gfx);
Team LRN
218
Chapter 17: Advanced Cybiko Graphics
buf[0]=–1; //eight pixels int* buf; buf=(int *)DisplayGraphics_get_buf_addr(main_module.m_gfx); buf[0]=–1; //16 pixels long* buf; buf=(long*)DisplayGraphics_get_buf_addr(main_module.m_gfx); buf[0]=–1;
For signed values, –1 means “all bits set,” so each of these examples will make a short horizontal line of the appropriate length on the main display. Something to keep in mind when using an int or long pointer to access a screen buffer: your pitch will be different; since an int takes up two bytes, the pitch is half of the pitch used for chars. Since a long takes up four bytes, the pitch is one-fourth of the pitch used for chars. A general formula to determine the pitch for types other than chars is as follows: Listing 17.13 pitch=(Graphics_get_bytes_total(&gfx)/height)/(sizeof(T)); //T is the type you are using //for access
A warning: Unless your bitmap has a bytelength that is a multiple of 2 or 4, using ints or longs to access them will be problematic, as the int or long will straddle two scan lines.
Signed Values Another problem with bit manipulation is that all types are signed, so –1 means “all bits set,” whereas you would normally use 0xFF for chars, 0xFFFF for ints, and 0xFFFFFFFF for longs. Sadly, at the moment, Cybiko has not revealed plans to make unsigned variables available to you. This makes our job of using direct memory access more problematical.
Pixel Alignment Another problem we have with writing more than one pixel at a time is that the bytes contain either 4 or 8 pixels, depending on format. So, we can easily draw pixels that start with x at a multiple of 4 or 8, but positioning them at, say, x=3 takes a bit more work. It’s not just a simple matter of shifting bits, because bits from one byte that “fall off the end” will have to be put on other bytes when they get shifted, and so on. So, accessing a screen buffer’s memory is only really useful when you can guarantee that you are accessing along the byte boundary.
Team LRN
Chapter 17: Advanced Cybiko Graphics
219
Four-Color Transparency Consider the following image for a moment.
Figure 17.1
The patterned pixels are those that we wish to have transparent. This image is impossible to draw with transparency using Graphics_draw_bitmap, because every color is present, so we can’t give one up. One solution would be to make two bitmaps, shown below, and draw one on top of the other.
Figure 17.2: Transparent color: light gray
Figure 17.3: Transparent color: white
Team LRN
220
Chapter 17: Advanced Cybiko Graphics
This solution will work, but it requires you to have two bitmaps. In addition, you have to change the draw mode in between drawing each bitmap. The advantage is that you could draw it anywhere on the screen, byte aligned or not. The other way you might do this is to make a bitmask, essentially an image copied from the original, with all transparent pixels black and all other pixels white, and place white pixels in any transparent location on the original bitmap. Here’s what I mean: 0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1
1
1
1
0
1
0
1
1
1
1
0
1
1
0
1
1
1
1
0
1
0
0
2
0
0
2
0
0
0
0
2
0
0
2
0
0
0
3
3
0
0
3
3
0
Figure 17.4
The shading makes it a little hard to see, so here’s just the numbers: 00000000 00000000 01111110 10111101 10111101 00200200 00200200 03300330 3
3
3
0
0
3
3
3
3
3
3
0
0
3
3
3
3
0
0
0
0
0
0
3
0
3
0
0
0
0
3
0
0
3
0
0
0
0
3
0
3
3
0
3
3
0
3
3
3
3
0
3
3
0
3
3
3
0
0
3
3
0
0
3
Figure 17.5
Team LRN
Chapter 17: Advanced Cybiko Graphics
221
Again, here are just the numbers: 33300333 33300333 30000003 03000030 03000030 33033033 33033033 30033003 If you look carefully at the numbers in the bitmask, you can kind of see the outline of the image. These pixel values might be in an array that you set up manually, or they might be in a Bitmap object somewhere that you associate with a Graphics object to read from the buffer. Both of these images might be in the same Bitmap object, stacked on top of one another. It doesn’t really matter as long as you can get a pointer to them. Since this is a four-color image, which is four pixels per byte, this image is exactly two bytes wide (8 pixels divided by 4). The height is 8 also, so there is a total of 32 bytes for each image, or 64 bytes for both of them. For simplicity, we will consider each of these bitmaps to be within their own Bitmap object, bmp and bmpmask. Since each scan line is two bytes long, it is most efficient to use ints to access the bits. If we are writing to the main display, this is not a problem, since the main display is 160 pixels wide, which takes up 40 bytes, which is divisible by 2 (so, 20 ints per line). The only major issue is that we have to align this image on a byte boundary so that the left of the image will have an x coordinate that is divisible by 4. Also, we have to be sure that the image isn’t shown partially on the screen, or we will get some artifacts on the opposite side. Here’s some code that will draw this image with four-color transparency in the upper-left corner of the screen: Listing 17.14 struct Graphics gfx; struct Graphics gfxmask; int* buf; int * bufmask; int* bufdst; int index; Graphics_ctor_Ex(&gfx,&bmp); Graphics_ctor_Ex(&gfxmask,&bmpmask); buf=(int*)Graphics_get_buf_addr(&gfx); bufmask=(int*)Graphics_get_buf_addr(&gfxmask); bufdst=(int*)DisplayGraphics_get_buf_addr(main_module.m_gfx);
Team LRN
222
Chapter 17: Advanced Cybiko Graphics
for(index=0;index<8;index++) { bufdst[index*20]=bufdst[index*20] & bufmask[index] | buf[index]; }
This code will write our image, with four-color transparency, eight pixels at a time. It’s a beautiful thing. We are done with direct memory access. Sure, there are more things you can do with it, but I’m sure you’ll figure out new and interesting ways to use it on your own.
cy3d Remember when I said that the UI system was the most complicated system as far as Cybiko programming was concerned? Well, after we talk about the 3D graphics subsystem, you may have a different opinion. Naturally, most of you undoubtedly want to make use of 3D graphics on the Cybiko. It seems these days everybody wants stuff to be 3D rather than 2D. Sigh. It ain’t like the old days. The Cybiko device has some 3D acceleration built into it. Programming 3D graphics on the Cybiko is a great deal more primitive than on other platforms. (Hey, it’s a four-color display, what do you expect?) Regardless, it is a part of the Cybiko SDK, which means I cover it. In order to make use of the cy3d functions, you must include cy3d.h in your files that use these functions. In addition, you have to ensure that cy3d.dl is on any Cybiko unit that you are writing a 3D app for. Don’t blame me, I don’t make these rules!
Fundamental Types Used in cy3d Before we get too far into cy3d, we first have to talk about the fundamental types used by cy3d. These are fixed_t, point_t, pos_t, and raster_t. Most of the cy3d functions use them, so if you aren’t familiar with them, you’ll easily get lost.
fixed_t There are no floating-point variables on the Cybiko. Period. Everything is a char, an int, a long, a pointer, or some kind of struct. However, 3D graphics requires the use of floating-point values. So, how can we use floating-point values (like 1.5) on the Cybiko? Quite simple: we don’t. We have to fake it. To fake it, we will use what are called fixed-point decimals. This is what fixed_t is. It is nothing more than a lowly int, but its usage is different. The high eight bits are used for the whole number, and the low eight bits are used for a fraction. Confused yet? Just wait.
Team LRN
Chapter 17: Advanced Cybiko Graphics
223
The Whole Number Part You can easily convert from a char to a floating-point value. To convert from a char to a fixed_t, you multiply the char by 256 (or <<8 if you prefer). To convert back, you divide by 256 (or >>8). Here are a few examples. Listing 17.15 //set a fixed_t to 100 value=100*256; //100<<8 //set a fixed_t to –100 value=–100*256; //–100<<8 //find the whole number part of a value whole=value/256; //value>>8
The Fractional Part Assigning a fraction to a fixed_t takes a little more work. Consider the fraction 1/100. To set a fixed_t to 1/100, we can first set the fixed-point value to 1 (i.e., 1*256 or 1<<8, or 256), then divide by 100. The integer value of the fixed_t would be 2. The value 2/256 is 0.0078125, which is a little bit less than the 0.01 that we intended. Such is life with fixed_t. As the fractions get smaller, they become less accurate. The only truly accurate fractions for a fixed_t are 1 2, 1 4, 1 8, 116, 1 32, 1 64, 1128, and 1 256, and multiples thereof. This is just a limitation of using fixed_ts, and there is no way around it. Negative Numbers Another minor problem with fixed_t is the representation of negative numbers that have fractions. Let’s say you want to represent –1½ as a fixed_t. Your first impulse might be to use –1*256 + 256/2. This would be incorrect. You would get the value of –128, which equates to –½. You have to remember that –1½ is the same as saying –1 –1/2, so the proper way to set the value is –1*256 –256/2, which will give you –384, which is the proper representation of –1½. Similarly, converting backward has this problem. Converting back from –384 to a whole number will give us a –1, with a remainder of –128, which is –½, to give you the value of –1½. Addition and Subtraction For a fixed_t, addition and subtraction have no problems. You can use these normally, and you will get the proper results. For example, a –1½ (–384), plus 1 (256) will give you –128, or –½. Multiplication While addition and subtraction have no issues, multiplication cannot be said to have none. Indeed, consider the simplest of multiplications, 1 x 1, which should give us 1.
Team LRN
224
Chapter 17: Advanced Cybiko Graphics
The fixed_t version of 1 is 256, and 256x256 is 65,536, which cannot even be represented as an int. To get a number that means something, we have to divide each of the numbers we are multiplying by 16 (>>4), then multiply them. Unfortunately, we lose the lowest four bits of the fraction, meaning we can only represent fractions down to 1 16 for multiplication. We will, however, get the proper values from multiplication. Consider again 1x1, or in fixed_t 256x256. Dividing each of these by 16 will give us 16x16, which equals 256, which represents 1, so mission accomplished. If you want to multiply a fixed_t by a regular integer (non-fixed_t value), it will work normally: 1(fixed)x1(int) is 256x1=256. Division Naturally, if multiplication is screwed up, we can expect division to be. Consider the simple example of 1/1 (one divided by one, which is one). In fixed_t, 1 is 256, so 256/256 is 1. A fixed-point value of 1 means 1/256 or 0.0039062. In order to get it right, we have to multiply the first number by 16 (<<4) and divide the second number by 16 (>>4). This introduces a problem. The first number is now limited to between –16 and +16, and the second number loses the last four bits of its fraction. So, division has its share of data loss when doing fixed-point division. You can always divide a fixed-point value by an integer, and the result will be the proper value: 1(fixed)/1(int) is 256/1 which has a result of 256, or the fixed-point 1. Multiplication and Division with No Data Loss There is a way around this data loss, fortunately. We can copy the fixed_t values into longs, and do the calculations there. Here’s the syntax for multiplication: Listing 17.16 //x and y are fixed_t values, z is the result(also a fixed_t) long x2; long y2; long z2; x2=x; y2=y; z2=(x2*y2)>>16; z=(fixed_t)z2;
There is no data loss in the above calculations. Similarly, we can copy to longs for doing division, like so: Listing 17.17 //x and y are fixed_t, z is the result(also a fixed_t) long x2,y2,z2; x2=x; y2=y; z2=(x2<<16)/y2; z=(fixed_t)z2;
Team LRN
Chapter 17: Advanced Cybiko Graphics
225
Again, no data loss occurs. This method is more accurate than using just the fixed_t’s, but it does have a slight performance cost, since you have to copy over values from ints into longs, and then back again. The moral of the story is to avoid multiplication and division whenever possible. fixed_t Functions Since we now have a way to represent non-integer values (admittedly, the method is primitive, but it’s all we have), we can make use of trigonometry. The Cybiko trigonometry functions are sin and cos, as shown below. Listing 17.18 fixed_t fixed_t
cy3d_sin ( int direction ) cy3d_cos ( int direction )
These two functions take integers. The measure is in degrees (0 through 359). The return value is a fixed_t equal to either the sin or cos of that angle. Another function of note is cy3d_sqrt, which allows us to take the square root of an integer value. It is shown below. Listing 17.19 int
cy3d_sqrt ( long value )
This function takes a long and returns the integer square root as an int. The value must be non-negative.
point_t and pos_t These types are two different ways to hold a two-dimensional coordinate (x and y). They are quite similar, and there are functions for converting between them. Here are their definitions: Listing 17.20 struct point_t { fixed_t p_x; fixed_t p_y; }; struct pos_t { short ps_x; short ps_y; };
As you can see, point_t represents an (x,y) pair with fixed_t, and pos_t represents an (x,y) pair with shorts (i.e., ints). There are two functions that will convert between these two types (so you don’t have to do the bit shifting yourself). Here they are:
Team LRN
226
Chapter 17: Advanced Cybiko Graphics
Listing 17.21 struct pos_t cy3d_pos ( point_t p ); struct point_t cy3d_point ( pos_t p );
Although this probably goes without saying, cy3d_pos converts a point_t to a pos_t, and cy3d_point converts a pos_t to a point_t. These are the only two functions dealing with pos_t. It is not otherwise useful, and you should probably avoid using it whenever possible, as it will only slow you down.
raster_t Finally, we come to raster_t. A raster_t is nothing more than a collection of textures or sprites. A texture is a bitmap that is applied to a 3D primitive, such as a wall. A sprite is a 2D image that is used to represent monsters or objects, which can be scaled to simulate relative nearness and farness. Textures and sprites are of fixed size. A texture is 32 pixels wide and 16 pixels tall (128 bytes). A sprite is 32 pixels wide and 32 pixels tall (256 bytes). The reasons for these sizes have to do with how the Cybiko performs texture mapping and how it draws its sprites. To create a resource that may be used as a texture or a sprite requires the use of one of the SDK utility, BMP2SPR.EXE. Despite its name, it can generate both textures and sprites. In order to make use of BMP2SPR, you need to make a bitmap of the appropriate size (32x16 or 32x32) with 256 colors (it won’t work on bitmaps with any other bit depths), and use the following syntax: Listing 17.22 BMP2SPR outfile infile1.bmp infile2.bmp infile3.bmp
You don’t put an extension on outfile; BMP2SPR figures out which one to use. You can have one or more input files (infile1, infile2, and so on). The utility will load in the bitmap, and write out the texture (.tex) file or the sprite (.spr) file. Once you have created a texture or sprite collection, you can add it to your application just like any other resource by putting the filename in Filer.list. Once you’ve got it in the archive, you can use one of three loading functions. These are shown below. Listing 17.23 raster_t* raster_t* raster_t*
cy3d_load (char *name) ; cy3d_load_tex (int index); cy3d_load_spr (int index);
Generally, you will want to use cy3d_load. It’s a generic function that loads either a sprite or a texture, depending on the extension of the resource. If you load a file that has a .tex extension, it will load a texture. If the extension is .spr, it will load a sprite. The function returns a raster_t*, which you do not have to allocate beforehand, but you do have to free it when you are through with it (cy3d was added to the SDK in a
Team LRN
Chapter 17: Advanced Cybiko Graphics
227
rush, it seems, so they don’t have nice, neat constructors and destructors, just these “quick-and-dirty” functions). The reason I suggest using cy3d_load above the others is that it’s the only one that allows you to give the name of the resource rather than the index. I personally don’t like giving index names into the resource list, because I just don’t pay that much attention to which resource is #0, which is #1, and so on. The cy3d_load_tex and cy3d_load_spr will load a texture or a sprite, respectively, based on the given index into the resource list, and returns a raster_t*, which you again have to free when you are done with it.
How cy3d Works Ever play Wolfenstein 3D? Cybiko 3D is a lot like it. It’s not really 3D at all. It’s 2D with a fake third dimension. It’s not even the “2.5D” that Doom was. In other words, you won’t be making a Descent-type game with it. It is pretty fast, though, and you can make stuff that is pretty cool with it. There are two different types of things you can draw with cy3d: textures and sprites. Textures are used for walls, and sprites are used for objects and creatures. They are dealt with in separate manners, but they mostly work about the same. I’m going to take you through the process of using each of them, because I could explain it all day and you might never understand it, because cy3D is something you understand by doing, not by talking about it.
Textures As stated earlier, cy3d uses 32x16 textures, which you convert from a bitmap using BMP2SPR. You load them using either the cy3d_load or cy3d_load_tex function. You release their memory with the free function. Load up CyBk17_1, the first of our texture demos. Put it on your Cybiko, and make sure that cy3d.dl is also loaded on your Cybiko as well. (cy3d.dl can be found in the SDK subfolder called Lib. You can put it on your Cybiko simply by dragging the file into the console.) All apps that make use of the cy3d functions must have cy3d.dl on it, or the application will lock up. Figure 17.6 shows the output of CyBk17_1.app. Figure 17.6: Output of CyBk17_1.app
It’s not much, just a couple of walls, but it is “3D,” and for the Cybiko, it looks pretty cool. The scene is stationary, because I haven’t put in controls that allow movement yet. I didn’t want to show you too much all at once, since this cy3d stuff is kind of overwhelming.
Team LRN
228
Chapter 17: Advanced Cybiko Graphics
This demo is based on CyBk6_3.app, the idle-loop framework. The changes are actually relatively minor (getting a single texture up and running isn’t too much work). The main things that changed were Prog_Init, Prog_Done, and Prog_Loop. I also added a DrawWall function to make things easier on myself. Global Variables Before I get into the function listings, I’ll show you the new global variables I added: Listing 17.24 //frame buffer char* framebuffer; //z buffer fixed_t* zbuffer; //the four corners. point_t corner[4]; //wall texture raster_t* wall;
First is the framebuffer, which is simply a pointer to the video display buffer (as retrieved by DisplayGraphics_get_buf_addr). Second is zbuffer, which is a depth buffer that I’ll explain in a little while. Third, there is the corner array. Even though you can only see three walls in the demo, there are indeed four. One of them is behind you. Finally, wall is a raster_t that we will use for a texture. Prog_Init Here is the new Prog_Init. Listing 17.25 bool Prog_Init() { //initialization //initialize frame buffer framebuffer=DisplayGraphics_get_buf_addr(main_module.m_gfx); //initialize z buffer zbuffer=(fixed_t*)malloc(sizeof(fixed_t)*160); //initialize corners corner[0].p_x=–512; corner[0].p_y=–1024; corner[1].p_x=–512; corner[1].p_y=1024; corner[2].p_x=512; corner[2].p_y=1024; corner[3].p_x=512; corner[3].p_y=–1024;
Team LRN
Chapter 17: Advanced Cybiko Graphics
229
//load the wall wall=cy3d_load("wall.tex"); //return TRUE if program initialized, and FALSE if it did not return(TRUE); }
The Prog_Init function is now responsible for a number of things. First, it initializes the framebuffer variable by retrieving the DisplayGraphics object’s buffer pointer. Next, the z buffer is allocated. If you’ve never done 3D programming before, a z buffer might seem a little strange to you. For the Cybiko, a z buffer is just an array of 160 fixed_t variables, one for each column of pixels on the screen. Each value holds how far away that given column is from the camera (or the viewer, or you). When you draw a wall or a sprite, the value of a column is based on a texture’s position and orientation (could also be a sprite). If the value calculated is less than the value currently in the z buffer, that column is drawn. If it is greater, nothing is drawn. In this way, you will have the scene rendered correctly without having to figure out what is “in front” of something else. Luckily, you don’t ever have to mess with the z buffer on your own; the cy3d functions do it for you. All you have to know is how to set it up, and how to clear it out (which we’ll look at in Prog_Loop). You don’t need to have in-depth knowledge of how it does its job. Next, the four corners are given their values. The x values span from –512 to 512. The y values span from –1,024 to 1,024. Finally, the wall texture is loaded from the wall.tex resource. Prog_Done Very little of what was initialized has to be cleaned up. Here’s the Prog_Done function. Listing 17.26 void Prog_Done() { //cleanup //clean up textures free(wall); //clean up z buffer free(zbuffer); }
All that goes on here is freeing the wall, and freeing the z buffer. Everything else cleans itself up. Prog_Loop And now for the functional part of the program, Prog_Loop, which does the actual work of the application.
Team LRN
230
Chapter 17: Advanced Cybiko Graphics
Listing 17.27 void Prog_Loop() { //idle loop //clear out the z buffer memset(zbuffer,0x7f,sizeof(fixed_t)*160); //draw the "sky" cy3d_draw_sky(framebuffer,CLR_WHITE); //draw the walls DrawWall(0,1); DrawWall(1,2); DrawWall(2,3); DrawWall(3,0);
//corner //corner //corner //corner
0 1 2 3
to to to to
corner corner corner corner
1 2 3 0
//mirror the frame buffer cy3d_mirror_buffer(framebuffer); //show the frame buffer DisplayGraphics_show(main_module.m_gfx); }
First, the z buffer gets cleared out. The manner in which it is done may seem a little counterintuitive, so I’ll explain it. The zbuffer variable points to an array of 160 fixed_t values, each measuring how far away a given column of pixels on the screen is from us. In order to draw something on the screen, we have to be writing a value to the z buffer that is “closer” than the current value. So, before we do anything, we need to start the value of each column in the zbuffer at a very high value. I am using a memset of 0x7f (127). Each byte of this array gets the value of 127. Since a fixed_t is actually two bytes, it gets the value of 0x7F7F. When translated from a fixed-point to a normal number, this equals 127 +127/256, or a little less than 127.5. Generally speaking, there will be something closer than that to see, so this is a high enough value to use for clearing out the z buffer. After the z buffer is cleared out, we “draw the sky.” This simply means to clear out the top half of the frame buffer to a given color, sort of like a partial DisplayGraphics_fill_screen. You can clear the buffer to any color. Using too much dark on the screen, however, makes for strange artifacts on the display. Next, the walls are drawn using the DrawWall function (which I’ll show in a moment). Now comes a sort of weird part. We have only been drawing to the top half of the screen. The bottom half of the screen has whatever was on it before we started drawing. In many cases, we just draw the lower half of the screen by copying the top half of the screen onto it upside down. This is what cy3d_mirror_buffer does. It doesn’t work in all cases, as we shall see, but it does in this case.
Team LRN
Chapter 17: Advanced Cybiko Graphics
231
Finally, we call DisplayGraphics_show to make the frame buffer visible. And then the scene is finished. Here is the function prototype for cy3d_draw_sky: Listing 17.28 void
cy3d_draw_sky (char *buff, color_t clr);
This function returns no value and takes two parameters. The first is a pointer to a display buffer, and the second is a color_t. DrawWall Finally, here is the DrawWall function. Listing 17.29 void DrawWall(int corner1, int corner2) { //draw the wall from corner1 to corner2 cy3d_draw_wall(framebuffer,corner[corner1],corner[corner2],wall,0,0,zbuffer); }
This function does the actual drawing of a wall. It takes two indices into the corner array, and draws the wall in between them. cy3d_draw_wall takes a number of parameters, a pointer to a frame buffer, two point_t’s, a raster_t to use as a texture, an index into the texture array (you can have more than one texture in a texture file), a vertical offset, and pointer to a z buffer. All in all, it’s pretty simple. Here is the function prototype for cy3d_draw_wall. Listing 17.30 void
cy3d_draw_wall (char *buff, point_t p1, point_t p2, raster_t *tex, int index, int shift, int *zbuff);
This function returns no value and takes a number of parameters. The first parameter, buff, is a pointer to a display buffer. The second and third parameters are point_t’s, and specify where the wall begins and ends, respectively. The fourth parameter is a raster_t pointer, telling the function what set of textures you want to use for this wall. The fifth parameter is an index into the texture set, zero being the first texture. The sixth parameter is a vertical shift. Normally, the wall is drawn along the top half of the screen, stopping at the y location of 49. Using shift, you can change this default behavior. Finally, the last parameter is a pointer to a z buffer, an array of 160 fixed_ts.
Looking Around Rendering walls is all well and good, but it won’t be interactive until we can move and look around. In order to do this, we have to talk a little bit about cameras. A camera, in the context of a 3D view, represents the position of the player in the world. It is the “you” in the 3D world. The camera has two attributes: a position (represented by a point_t) and a direction (represented by an int that contains a degree measurement).
Team LRN
232
Chapter 17: Advanced Cybiko Graphics
In the real world, when you move around a room, you move and the room stays still, or at least that’s how you see it. It would be ridiculous to think that you stayed still while the room moved around you. However, both points of view would be correct, as they both fit the same facts. So, we can think of you moving around the room, the room moving around you, or a little of both. It doesn’t matter. In 3D programming, and on the Cybiko in particular, the camera is always sitting at (0,0) and facing 0 degrees. Always. Without deviation. It cannot move around the room. So, we must make the room move around it to give us the illusion that we are moving around the room. The manner in which we do this is by using the cy3d_warp function. It is perhaps the least appropriately named function in the cy3d portion of the SDK. Here’s the prototype. Listing 17.31 point_t
cy3d_warp (point_t p, point_t camera, int direction);
This function returns a point_t, which is the “warped” point result of the calculation. It takes three parameters. The first is a point_t, the source point (i.e., the “actual” coordinates of something in the 3D world). The second is another point_t, the position of the camera in the 3D world. The third parameter is the direction, in degrees, that the camera is pointing in. This function takes the source point (p), the camera position, and camera direction, and from it determines where the source point should be plotted and spits out that coordinate as the return value. How it does this (in case you are interested) is by taking the source point, subtracting the camera position, and then rotating by the direction angle. Again, the room moves around you. In CyBk17_2.app, I have taken CyBk17_1, and made us able to look around by pressing the left and right arrow keys. To do this, I added a few extra global variables: a point_t named camera, an int named direction, and a DirectKeyboard pointer, which makes the movement seem a little bit smoother. Most of the changes to go from CyBk17_1.app to CyBk17_2.app are almost cosmetic, dealing mainly with the addition of a DirectKeyboard object (getting the instance, scanning it, and checking for key states). The change that is important (the one that achieves the “looking around” effect) is found in the DrawWall function. I’m going to show the same stuff I showed for CyBk17_1.app, but I’m also going to show the changes from that app in bold, so you can see them better. Globals I added three globals, a DirectKeyboard pointer named pdkbd, a point_t called camera, and an int called direction. Listing 17.32 //directkeyboard structure struct DirectKeyboard* pdkbd;
Team LRN
Chapter 17: Advanced Cybiko Graphics
//frame buffer char* framebuffer; //z buffer fixed_t* zbuffer; //the four corners. point_t corner[4]; //wall texture raster_t* wall; //camera point_t camera; //direction int direction;
This is pretty straightforward. I don’t think I need to explain it further. Prog_Init() The Prog_Init function naturally has more to do now. Listing 17.33 bool Prog_Init() { //initialization //initialize frame buffer framebuffer=DisplayGraphics_get_buf_addr(main_module.m_gfx); //initialize z buffer zbuffer=(fixed_t*)malloc(sizeof(fixed_t)*160); //initialize corners corner[0].p_x=–512; corner[0].p_y=–1024; corner[1].p_x=–512; corner[1].p_y=1024; corner[2].p_x=512; corner[2].p_y=1024; corner[3].p_x=512; corner[3].p_y=–1024; //load the wall wall=cy3d_load("wall.tex"); //initialize camera camera.p_x=0; camera.p_y=0; //initialize direction direction=0; //initialize directkeyboard
Team LRN
233
234
Chapter 17: Advanced Cybiko Graphics
pdkbd=DirectKeyboard_get_instance(); //return TRUE if program initialized, and FALSE if it did not return(TRUE); }
The main addition here is initializing the camera position to (0,0), initializing direction to 0, and setting up the DirectKeyboard object. Nothing very difficult here. Prog_Done() Since we initialized more stuff in Prog_Init, we naturally have to clean it up in Prog_Done. Listing 17.34 void Prog_Done() { //cleanup //clean up textures free(wall); //clean up z buffer free(zbuffer); //clean up direct keyboard DirectKeyboard_dtor(pdkbd,FREE_MEMORY); }
The only addition here is the DirectKeyboard destructor call. Prog_Loop() Some minor changes occurred to Prog_Loop. (If you are wondering when we’re actually going to get to some substantial changes, we will in a minute). Listing 17.35 void Prog_Loop() { //idle loop //scan keyboard DirectKeyboard_scan(pdkbd); //check for KEY_LEFT if(DirectKeyboard_is_key_pressed(pdkbd,KEY_LEFT)) { direction+=5; } //check for KEY_RIGHT
Team LRN
Chapter 17: Advanced Cybiko Graphics
235
if(DirectKeyboard_is_key_pressed(pdkbd,KEY_RIGHT)) { direction+=355; } //make direction fit into 0-359 range direction%=360; //clear out the z buffer memset(zbuffer,0x7f,sizeof(fixed_t)*160); //draw the "sky" cy3d_draw_sky(framebuffer,CLR_WHITE); //draw the walls DrawWall(0,1); DrawWall(1,2); DrawWall(2,3); DrawWall(3,0);
//corner //corner //corner //corner
0 1 2 3
to to to to
corner corner corner corner
1 2 3 0
//mirror the frame buffer cy3d_mirror_buffer(framebuffer); //show the frame buffer DisplayGraphics_show(main_module.m_gfx); }
Here the additional code scans the keyboard, and checks the state of KEY_LEFT and KEY_RIGHT. If KEY_LEFT is pressed, add 5 degrees to the direction. If KEY_ RIGHT is pressed, “subtract” 5 degrees. (Adding 355 degrees is the same as subtracting 5 degrees.) DrawWall Finally! Something worth looking at. Listing 17.36 void DrawWall(int corner1, int corner2) { //temporary points point_t c1; point_t c2; //warp corners c1=cy3d_warp(corner[corner1],camera,direction); c2=cy3d_warp(corner[corner2],camera,direction); //draw the wall from corner1 to corner2 cy3d_draw_wall(framebuffer,c1,c2,wall,0,0,zbuffer); }
Team LRN
236
Chapter 17: Advanced Cybiko Graphics
The entire function has changed. We’re still just drawing a wall from one corner to another, but now there is an intermediate step—we are “warping” the corner positions around the camera and storing them in temporary variables c1 and c2. “So,” you’re asking me, “where’s the hard part?” There isn’t one. It’s just that easy. Go ahead and try out the application. Turn left. Turn right. Turn left again. Turn right again. When you get tired of it, come back, and we’ll make the camera move around the scene (or the scene move around the camera—take your pick).
Moving Around We can draw walls. We can turn the camera. Just one ingredient left, and we’re well on our way to mastery of the cy3d section of the SDK—we just need to move. First, I want to talk a little bit about movement in a 3D world, which means talking about movement in 2D and how it is different. When writing a 2D application, we have the benefit of having absolute directions. We press the up arrow key (KEY_UP), and rightfully we expect that our character/cursor/whatever moves towards the top of the screen. Likewise, when we press the KEY_DOWN key, we expect it to move down the screen. The same basic idea for KEY_LEFT and KEY_RIGHT. In 3D land, however, we expect KEY_LEFT and KEY_RIGHT to cause the camera to turn (as we have already done). We expect KEY_UP to move us “forward,” and KEY_DOWN to move us “backward.” The concept of “forward” and “backward” relies entirely on the direction the camera is facing. This is where the cy3d_move function comes in. Based on a position, a direction, and a length of movement, it calculates the new position of something. This “something” can be the camera, a creature, or just about anything else that needs to move in the 3D world. Here’s the prototype for cy3d_move. Listing 17.37 point_t cy3d_move(point_t p, fixed_t distance, int direction );
This function takes three parameters and returns a point_t. The first parameter is the starting point, the second parameter is a fixed_t distance, and the third parameter is a direction in degrees. The returned value is what the starting point would be if it were moved in the given direction by distance. We already have a point_t for our position, called camera, and a direction called direction. In order to move the camera around, all we need is to pick a distance. Moving Around Example In CyBk17_3, I have done just that. Very few lines were added in order to make us able to move the camera around, and all of them are in Prog_Loop, as shown below. Listing 17.38 void Prog_Loop() {
Team LRN
Chapter 17: Advanced Cybiko Graphics
//idle loop //scan keyboard DirectKeyboard_scan(pdkbd); //check for KEY_LEFT if(DirectKeyboard_is_key_pressed(pdkbd,KEY_LEFT)) { direction+=5; } //check for KEY_RIGHT if(DirectKeyboard_is_key_pressed(pdkbd,KEY_RIGHT)) { direction+=355; } //make direction fit into 0-359 range direction%=360; //movement //KEY_UP if(DirectKeyboard_is_key_pressed(pdkbd,KEY_UP)) { //move forwards camera=cy3d_move(camera,64,direction); } //KEY_DOWN if(DirectKeyboard_is_key_pressed(pdkbd,KEY_DOWN)) { //move backwards camera=cy3d_move(camera,–64,direction); } //clear out the z buffer memset(zbuffer,0x7f,sizeof(fixed_t)*160); //draw the "sky" cy3d_draw_sky(framebuffer,CLR_WHITE); //draw the walls DrawWall(0,1); DrawWall(1,2); DrawWall(2,3); DrawWall(3,0);
//corner //corner //corner //corner
0 1 2 3
to to to to
corner corner corner corner
1 2 3 0
//mirror the frame buffer cy3d_mirror_buffer(framebuffer); //show the frame buffer DisplayGraphics_show(main_module.m_gfx); }
Team LRN
237
238
Chapter 17: Advanced Cybiko Graphics
As you can see, there isn’t much to it. When KEY_UP is pressed, we move 64 (¼ of a unit) in direction. When KEY_DOWN is pressed, we move –64 in direction. We could have done backward movement a different way, by adding 180 to direction instead of using –64 for distance. The method is not particularly important as long as you get the results you want. How cy3d_move Works You can get along just fine using cy3d_move without knowing how it works. However, I know that there are those of you who are interested in its inner workings. If you don’t care, skip this next bit. There are two functions that help cy3d_move do its job. These are cy3d_sin and cy3d_cos. These functions are trigonometric, although not typical as they deal in degrees instead of radians. Here are the prototypes for cy3d_sin and cy3d_cos: Listing 17.39 fixed_t cy3d_sin(int direction); fixed_t cy3d_cos(int direction);
Both of these functions return a fixed_t and take a single parameter, a direction represented as a degree measurement of an angle. Trigonometry is rather important to 3D rendering, as we require it for rotation as well as movement. I can’t possibly hope to give an adequate explanation of trigonometry in this book (there are huge volumes written on the subject of triogonometry), but here are the “absolute essentials.” Cosine and sine return values that are on the unit circle. The unit circle is nothing more than a circle with a radius of 1 that has its center at the origin (0,0). An angle of 0 represents a direction that points in the positive X direction (“due east”). As one might expect, the cos of 0 is 1, and the sine of 0 is 0, since a line moving in that direction hits the unit circle at location (1,0). Similarly, the direction of 180 hits the circle at (–1,0), so the cosine and sine of 180 is –1 and 0. The cosine and sine of 90 are 0 and 1, and the cosine and sine of 270 is 0 and –1. In between values vary between the values of –1 and 1 for sine and cosine. In each case, the sine of an angle squared plus the cosine of an angle squared when added together gives you one. What this means is that, given a direction, adding the cosine and sine of that angle to x and y will move you 1 unit in that direction. Also, if you multiply the cosine and sine by the same number, you will move in that direction by that number of units.
Sprites We’ve got walls. We’ve got turning. We’ve got movement. Just a few more things we need before we can make a 3D game. The first thing is sprites. I’m not talking about soft drinks here. I’m talking about the graphics you will use for creatures, items, and other objects in the game that are not walls.
Team LRN
Chapter 17: Advanced Cybiko Graphics
239
A sprite is handled quite simply by cy3d in a similar manner to textures. Both rely on raster_t* variables to render. The difference is that a sprite is 32x32 and has a transparent color, and a texture is 32x16 and has no transparency at all. Sprites are made in the same way textures are, by using BMP2SPR. Whether something is a texture (to be saved as a .tex file) or a sprite (a .spr file), it is autodetected by BMP2SPR. A sprite may be loaded using either cy3d_load or cy3d_load_spr. As with textures, I highly suggest using cy3d_load rather than cy3d_load_spr, unless you are really into keeping track of resource indices. Freeing a sprite resource is done in the same way as textures, by using the free function. So, by already having used textures, you know a great deal about how to deal with sprites. The transparent color for all sprites is dark gray. No, you don’t get to pick the transparent color, sorry. Dark gray isn’t that nice of a color anyway. You won’t miss it. So, you know how to make a sprite, you know the limitations on drawing them (size, colors available), and you know how to load them and clean up after them. The only thing remaining is to actually draw one. That is done with the cy3d_draw_sprite function, shown below. Listing 17.40 void cy3d_draw_sprite(char* buff, point_t p, raster_t* spr, int index, int* zbuff);
If you think this function looks a lot like cy3d_draw_wall, you’re right. The only thing missing is the shift parameter. The buff parameter is the pointer to the display buffer. The point_t, p, is the position at which you wish to draw the sprite. The spr pointer points to the sprite data, index is the frame number of the sprite (0 being the first frame), and zbuff is the pointer to the z buffer. Drawing a sprite is much like drawing a wall, so when does this become hard? It doesn’t. Sprite Demo In CyBk17_4, I’ve added a single sprite. I didn’t have to do a lot to the program to add this, so the code has remained mostly the same as CyBk17_3. Figure 17.7 shows the output. Figure 17.7: Output of CyBk17_4.app
I added about a total of five lines to the program to get a sprite up and running. There are two new globals, a raster_t* called skel, that is the sprite for the skeleton, a point_t called skelpos (for the position of the skeleton), a call to cy3d_load in Prog_Init
Team LRN
240
Chapter 17: Advanced Cybiko Graphics
that loads in the skeleton sprite, a call to free that cleans up the sprite, and finally a call to cy3d_draw_sprite in Prog_Loop that draws the sprite. Notice that the sprite is drawn after the walls have been drawn. Something you might want to look at. When you see the sprite, it always appears that the sprite is facing you. This is an old technique for 3D called “billboarding.” Unlike textures, which seem to face directions other than toward you, sprites always face the camera. In order to make it look like a sprite is facing another way, you have to fake it by using a different image that appears to be the sprite facing another direction. I’m no artist (heck, I stole the skeleton sprite and the wall texture), and actually, as far as sprites facing you are concerned, it usually won’t be a big deal.
Collision Detection So far, we haven’t concerned ourselves too much with anything other than the rendering of the walls and the sprites. However, this is not all there is to making a 3D game. In the examples in which you can move around (namely CyBk17_3 and CyBk17_4), you might have noticed that you can walk right through the walls, or walk right through the skeleton. That, of course, is not how the real world works, and since a 3D game is a simplified simulation of reality (or at least we hope it is), we will want to program in some basic laws of physics (like not being able to walk through walls and not being able to walk through other objects). Since we are dealing with two types of things, namely walls and sprites, we have to deal with two types of collisions. We can treat a wall like a line segment and a sprite like a single point (or at least, a very small circle). Determining whether a collision with a wall has occurred can be a tricky proposition. In the examples we’ve had so far, we’ve been essentially in a rectangular room, from (–1024,–512) to (1024,512), using fixed_t values, or (–4,–2) to (4,2) if we use integer coordinates. Doing collision detection would be a simple matter of checking that the camera were still within the box. For walls that are “horizontal” or “vertical,” (with either both y or both x coordinates the same), the job is nearly as simple. You can just check if the camera position is within the range of the wall, and check that both the start and end position are on the same side. For walls that aren’t horizontal or vertical, the job becomes more complicated. You have to determine whether you can even cross the wall, then check whether or not such a thing has happened. For collision with sprites, the job is much easier. You can simply measure the distance between the camera and the sprite, and if you get too close, disallow the move. In order to determine the distance from one point_t to another, you can use one of two functions, cy3d_distance or cy3d_distance2. These are shown below. Listing 17.41 fixed_t cy3d_distance(point_t p1, point_t p2); long cy3d_distance2(point_t p1, point_t p2);
Team LRN
Chapter 17: Advanced Cybiko Graphics
241
Both of these functions take two point_t parameters. They differ by return type. cy3d_distance returns a fixed_t, and cy3d_distance2 returns a long. This long is a fixed-point value, with 16 bits on either side of the decimal point (it makes taking the square root a lot easier). Even though these two functions do essentially the same thing, I suggest that you use cy3d_distance2, since cy3d_distance does the same job with the addition of a square root. Square roots are costly to compute, so if you don’t have to calculate one, don’t.
Summary You now have near complete understanding of Cybiko graphics. This is as far as the SDK can take us, at least right now. The cy3d system is rather simplified, and I’m sure you’ve seen games that far exceed the abilities I’ve shown you here. However, I only get to work with what they give me. Who knows what the future will hold?
Team LRN
Chapter 18
Advanced Forms Overview In Chapter 15, I showed you the Cybiko UI system, the most complicated part of the Cybiko SDK. There is much functionality packed into this part of the SDK. It has great power and great flexibility. In this chapter, I will explore the full potential of the Cybiko UI system. You will see how to derive your own custom, reusable form classes. To a small degree, I’ve already shown you how in Chapter 15. Now, I will provide you with more examples, and show how to get the best use out of the Cybiko UI controls.
Form Class Template The first step in making a new type of form is deciding what to derive it from. There are a few good candidates for this. The four that I see are cFrameForm, cCustomForm, cClip, and cObject. These classes are listed from most derived to least derived, and they are all along the same path. The cFrameForm class is a good candidate, but only if you are requiring a full-screen form. Other than this (relatively minor) distinction, cFrameForm is identical to cCustomForm. To my mind, this limits the flexibility of the types of forms we can create. The cCustomForm class is an even better candidate, since we can cause it to be full-screen or not just by passing different parameters to its constructor. We can make a cFrameForm out of a cCustomForm (indeed, cFrameForm derives from cCustomForm). The cClip class is both a good and bad candidate. On the “good” side, we have all of the built-in child handling functionality that gets inherited by cFrameForm. We can add controls to a cClip object. We can even add other cClips to a cClip object. However, we would have to implement drawing the box (or rounded rectangle) around the cClip (or not draw it, depending on what we want to do). So, if we are looking at cClip
242
Team LRN
Chapter 18: Advanced Forms
243
to make something similar to a cCustomForm, there is a lot of work we would have to do. Still, there are times when cClip might be our best bet. We’ll keep an open mind. The cObject class is perhaps the poorest candidate. If we used it as a starting point for our own forms, we would have to add much of the functionality that cClip adds, and we would have to add the functionality that cCustomForm adds. This is reinventing the wheel quite a bit. However, it may be worth it if the need is great enough. If we need to go as low as cObject to start deriving our own classes, though, we should also consider just starting from scratch. So, cFrameForm is, to my mind, too limited, cClip is too open, and cObject is way too open. Our best bet is cCustomForm.
Skeleton Code When making our new form, we generally want it all to happen in a single function. This makes the creation of our form easily interfaced, much like the MessageBox and InputBox functions I showed earlier. There are times, of course, when we do not just want to wrap something up into a single function, but rather we want to derive a new type of form. In that case, we will want to implement a new constructor, destructor, proc, and ShowModal function, and make a version of each cCustomForm function for our new form. These can be done simply enough by making use of #define. UI forms have much in common with applications. With each one, we initialize, pump messages, and clean up when we’re done. Consider the following function prototypes for a new class called cMyForm. Listing 18.1 struct cMyForm* cMyForm_ctor(struct cMyForm* ptr_form,...); //replace "..." with an //actual parameter list void cMyForm_dtor(struct cMyForm* ptr_form,int mem_flag); int cMyForm_ShowModal(struct cMyForm* ptr_form); bool cMyForm_proc(struct cMyForm* ptr_form,struct Message* ptr_msg);
The declaration of cMyForm would look something like this: Listing 18.2 struct cMyForm: public cCustomForm { /*additional members*/ };
And also, there would be a number of #defines to make cMyForm versions of cCustomForm functions, like so: Listing 18.3 #define cMyForm_Disconnect cCustomForm_Disconnect #define cMyForm_Select cCustomForm_Select
Team LRN
244
Chapter 18: Advanced Forms
#define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define
cMyForm_update cCustomForm_update cMyForm_GetParent cCustomForm_GetParent cMyForm_Hide cCustomForm_Hide cMyForm_Show cCustomForm_Show cMyForm_Disable cCustomForm_Disable cMyForm_Enable cCustomForm_Enable cMyForm_AddObj cCustomForm_AddObj cMyForm_InsObj cCustomForm_InsObj cMyForm_RemObj cCustomForm_RemObj cMyForm_SelectFirst cCustomForm_SelectFirst cMyForm_SelectPrev cCustomForm_SelectPrev cMyForm_SelectNext cCustomForm_SelectNext cMyForm_Scroll cCustomForm_Scroll cMyForm_Scroll_Ex cCustomForm_Scroll_Ex cMyForm_SendScroll cCustomForm_SendScroll cMyForm_GetShifty cCustomForm_GetShifty cMyForm_GetShiftx cCustomForm_GetShiftx cMyForm_GetCount cCustomForm_GetCount cMyForm_get_by_index cCustomForm_get_by_index cMyForm_FindObj cCustomForm_FindObj cMyForm_GetSelectedObject cCustomForm_GetSelectedObject
This is strictly a matter of taste. I do it because I can then make use of cMyForm_* functions with my cMyForm objects, rather than mixing and matching cMyForm functions with cCustomForm functions, which makes the code harder to read. In most cases, you will only have to implement the constructor, destructor, proc, and ShowModal. The constructor, of course, is where you do all of your initialization for the form. Naturally, if the appearance can be customized by the user, there should be a parameter for doing so. Also, you need enough information to be able to construct the cCustomForm base class as well. Sometimes this means putting the parameters of cCustomForm_ctor on the command line. Other times it means that you, the programmer, supply these values for your new form, and these cannot be changed. Also during the constructor, you will be adding any controls that are being supplied by default to the form. Naturally, the user of your code can add other controls after the constructor. In the destructor, you clean up all your data, and respond to the FREE_MEMORY flag of mem_flags by freeing the pointer to the form itself. This is just the standard way of dealing with destructors. In the proc function, you customize the way the form responds to messages, just as you would deal with responding to messages in your main application. If ever a message is unhandled, you send it to the base class’s proc function, which in this case would be cCustomForm_proc. It’s important to note here the significance of the ModalResult member of the form. When you initialize a form, this should be set to mrNone. During message processing, there may be a need to change this value to something else, either one of the several predefined modal result values (mrQuit, mrOk, etc.), or a new one you make
Team LRN
Chapter 18: Advanced Forms
245
for yourself. This is important for the ShowModal function, since the ShowModal function simply keeps reading in messages and sending them to the proc until the ModalResult member is no longer mrNone. This is what the cMyForm_ShowModal code would look like. Other forms derived from cCustomForm use similar code. Listing 18.4 int cMyForm_ShowModal(struct cMyForm* ptr_form) { struct Message* ptr_msg; //set modal result to mrNone ptr_form->ModalResult=mrNone; //while still in modal loop while(ptr_form->ModalResult==mrNone) { //show the form cMyForm_Show(ptr_form); //grab a message ptr_msg=cWinApp_get_message(ptr_form->CurrApplication,0,1,MSG_USER); //send message to proc cMyForm_proc(ptr_form,ptr_msg); //delete message Message_delete(ptr_msg); } //return modal result return(ptr_form->ModalResult); }
That’s all there is to the cMyForm_ShowModal function. For other cCustomForm derived forms, you can pretty much replace the instances of cMyForm with whatever name the class has. In all other ways, the code will be identical.
The proc Function The proc function for a customized UI form is perhaps the most important thing to write, since it is this function that customizes the behavior of the form. For the most part, the messages received during the ShowModal function can be passed right along to cCustomForm_proc, which takes care of the common functionality of the UI system, such as moving from button to button using the Tab key, pressing down on a button with the Enter key, and moving through a list with the arrow keys. You certainly don’t want to have to rewrite all of that functionality yourself, so any message you don’t handle, you should send along to cCustomForm_proc, leaving you to concentrate only on the custom behavior unique to the form.
Team LRN
246
Chapter 18: Advanced Forms
Let’s say, that you are making a form that has a cList on it. This is the only control you have on the form. The cList has a number of cItems, with simple text containing items in a menu. You want to be able to reuse this form over and over again. Making a menu is a rather common thing, and making a custom form for one every time would just be tedious. So, you come up with a cMenuForm class, which derives from cCustomForm. Here’s what the struct might look like: Listing 18.5 struct cMenuForm: public cCustomForm { struct cList m_list; int m_index; };
The m_list member will be the cList control, and the m_index member will be a number corresponding to the item in the menu that is selected, with 0 being the first item. We shall assign –1 to mean no item was selected and instead the menu was canceled by pressing Esc. With this in mind, we know that we will at least need to write a cMenuForm_ctor, cMenuForm_dtor, cMenuForm_ShowModal, and cMenuForm_proc. For all other functions, we can use the cCustomForm versions, or use #define to make cMenuForm versions for these functions. The cMenuForm_ctor function might look something like the following. Listing 18.6 struct cMenuForm* cMenuForm_ctor(struct cMenuForm* ptr_form,int x,int y,int w,int h, char* name,bool round,struct cWinApp* ptr_winapp, char** menu, int itemcount);
If you take a look at cCustomForm_ctor, you will see that most of the parameters of cMenuForm_ctor closely mirror the cCustomForm parameters. I did make some changes, like replacing the rect_t pointer in cCustomForm_ctor with four parameters describing a rectangle, but other than that the parameters are in the same order as in cCustomForm_ctor. Also, cMenuForm_ctor has two additional parameters, menu and itemcount. NOTE: When deriving new classes, I try to stick to the original format as much as possible, and if I am adding a parameter, I tend to add it at the end rather than in the middle. This helps me to remember the parameter lists better, as they will have a common basis, and instead of remembering a completely new order of parameters, I only have to remember what parameters are added to those of the base class. Just a trick I taught myself a few years back. Saves me from confusing myself.
The two extra parameters, menu and itemcount, are going to take some explaining. I intend for the cMenuForm constructor to take a list of strings of variable length, and
Team LRN
Chapter 18: Advanced Forms
247
the best way to handle that is with a pointer to a string (char**) and an int stating the number of items in the list. This way, I can declare my list of menu items like so: Listing 18.7 char* MyMenu[]= { "Item 1", "Item 2", "Item 3", "Item 4" };
And I can then create my cMenuForm object in the following way: Listing 18.8 struct cMenuForm menuform; cMenuForm_ctor(&menuform,0,0,160,100,"Title",FALSE,main_module.m_process,MyMenu,4);
The four items pointed to by MyMenu will be placed on the form. If I wanted only three of those items, I could put 3 as the last parameter. If I had 12 items, I could put 12. So, I identify what cMenuForm_ctor has to do. First, it has to construct the base class, cCustomForm. Next, it has to construct the m_list member, and add all of the items to the menu. Finally, it has to initialize the m_index number to –1. Here’s what it might look like: Listing 18.9 struct cMenuForm* cMenuForm_ctor(struct cMenuForm* ptr_form,int x,int y,int w,int h, char* name,bool round,struct cWinApp* ptr_winapp, char** menu, int itemcount) { struct rect_t rc; int i; struct cItem* ptr_item; //construct base class rect_set(&rc,x,y,w,h);//construct rectangle cCustomForm_ctor(ptr_form,&rc,name,round,ptr_winapp);//initialize base class //construct cList cList_ctor(&ptr_form->m_list,w-4);//construct cList, with width-4 cMenuForm_AddObj(ptr_form,&ptr_form->m_list,0,0);//add cList to form //add items for(i=0;im_list,ptr_item);//add item to list }
Team LRN
248
Chapter 18: Advanced Forms
//initialize m_index to -1 ptr_form->m_index=-1; }
There’s nothing too over-the-top here. I did just what I said I was going to do. I want to point out, though, that all of the cItems for the list are dynamically allocated. With cList, this is not a problem, as cList_dtor will automatically destroy the list of items, and we won’t have to do anything to prevent a memory leak. On the flip side, we will also need a cMenuForm_dtor. In this function, we simply need to destroy the cList item that we constructed during cMenuForm_ctor (which will also destroy the items), and then just pass the parameters along to cCustomForm_dtor, like so: Listing 18.10 void cMenuForm_dtor(struct cMenuForm* ptr_form,int mem_flag) { //destroy cList cList_dtor(&ptr_form->m_list,LEAVE_MEMORY); //send to base class destructor cCustomForm_dtor(&ptr_form,mem_flag); }
This is the rule for destructors when you are deriving new classes from old ones: destroy what you have added to the class, then pass it along to the base class’s destructor, which will handle the rest. The cMenuForm_Show function will be the same as the code shown in the cMyForm_ShowModal example, with the cMyForms taken out and replaced with cMenuForms. That code rarely if ever changes. This leaves us with cMenuForm_proc left to do. For this, we have to determine exit points for the form, i.e., what conditions will cause the ModalResult of the form to change, and thereby exit the cMenuForm_ShowModal function? First off, if a MSG_QUIT or MSG_SHUTUP message is received, we need to quit, and so we shall set the ModalResult to mrQuit. If KEY_ESC is pressed, we need to cancel out of the form without setting the m_index member. For this, I will use the mrCancel menu result. If KEY_ENTER is pressed, we have selected a menu item, and so we need to update the m_index member and exit the modal loop. For this state, I will use the mrOk menu result. With this scheme, cMenuForm_ShowModal can give us the information we need. If we receive an mrQuit, we should quit the program. If we get an mrCancel, we should pretend as if the menu had never been brought up. If we get an mrOk, we must do something appropriate in response to the item that was selected. So, we have narrowed down what cMenuForm_proc has to accomplish. We need only respond to three messages: MSG_QUIT, MSG_SHUTUP, and MSG_KEYDOWN. All other messages can go to cCustomForm_proc. Within MSG_KEYDOWN, the only
Team LRN
Chapter 18: Advanced Forms
249
keys we even care about are KEY_ESC and KEY_ENTER. All others can be handled by cCustomForm_proc. So, let’s have a go at it. Listing 18.11 bool cMenuForm_proc(struct cMenuForm* ptr_form,struct Message* ptr_msg) { //switch based on msgid switch(ptr_msg->msgid) { case MSG_QUIT: case MSG_SHUTUP: { ptr_form->ModalResult=mrQuit; //set modal result return(TRUE); //handled }break; case MSG_KEYDOWN: { //switch based on scancode switch(Message_get_key_param(ptr_msg)->scancode) { case KEY_ESC: { ptr_form->ModalResult=mrCancel; //set modal result return(TRUE); //handled }break; case KEY_ENTER: { //update m_index member ptr_form->m_index=cList_FindObject(&ptr_form->m_list, cList_GetSelectedObject(&ptr_form->m_list)); ptr_form->ModalResult=mrOk; //set modal result return(TRUE); //handled }break; } }break; } //not handled, send to cCustomForm_proc return(cCustomForm_proc(ptr_form,ptr_msg)); }
This function might look a little long, but that is mainly because of the way I have vertically spaced it out to make it easier to read. We are really responding to very little. Each event that we are responding to takes up no more than two or three lines, and most of that is concerned with changing the ModalResult and returning TRUE. So, with these principles in mind, you are now set up to build an entire customizable menu form. I’d say that’s pretty cool. It’ll save us time for developing the hard stuff.
Team LRN
Chapter 19
Wireless Communication Overview One of the more intriguing aspects of the Cybiko is the radio frequency communication capabilities. With it, you can chat, play games, compose and send e-mail, and so on. In this chapter, we’re going to look at wireless communications. This includes how to determine what other Cybikos are in the area, and how to send messages from one Cybiko to another. One thing to keep in mind is that the part of the API dealing with wireless communication is not very well documented (by now, you’re probably used to this).
CyIDs If you remember way back to Chapter 7, we were looking at the Message struct, shown again here. Listing 19.1 struct Message { struct Message* next; char* dst_name; cyid_t cyid_from; cyid_t cyid_to; bool deleted; short msgid; long param [2]; };
We also talked about cyid_t being a type that allows you to differentiate between different Cybikos. Each Cybiko has its own, unique CyID, and periodically broadcasts it to any other Cybikos that might be in the area.
250
Team LRN
Chapter 19: Wireless Communication
251
For us programmers, sending a message from one Cybiko to another is theoretically a very simple process. We simply need to fill in appropriate values in cyid_from and cyid_to, and then send the message. In fact, there is even a special function for sending messages remotely. It is called, to no one’s surprise, send_remote_msg. Here’s the prototype. Listing 19.2 bool send_remote_msg(cyid_t cyid,char* app_name, int msgid, long d0, long d1, size_t size);
This function has a lot in common with the send_msg function. In fact, all of the parameters are the same except for the very first one, which is added by send_ remote_msg and allows you to put in there the CyID of the desired recipient of the message. So, provided that you have a messge you want to send to another Cybiko, it is really a rather simple matter of knowing that Cybiko’s CyID. Or is it simple? We shall see.
Finding Another CyID There are a number of low-level functions for dealing with the radio communications on the Cybiko. I won’t be covering those here. You can look them up in the SDK docs, if you wish. Most of them aren’t really all that useful. They fetch and set information, such as the radio channel your device is on, and so on. If you’re into that sort of thing, more power to you, but due to space and time limits, I’m leaving the exploration of these functions up to you. What I am going to talk about is selecting remote partners for apps and games. The SDK has some functions built in that help you decide on a game partner. The first one we are going to explore is select_partner. Listing 19.3 cyid_t select_partner(struct Process *ptr_process, int style, char *sz_partner_name);
This function takes three parameters, two inputs and one output. The ptr_process parameter is a pointer to a Process object. You just use main_module.m_process. The style parameter is a combination of flags. These flags add additional items to the list of potential partners found. Table 19.1 Style flags Constant
Meaning
SGP_NONE
No extra items
SGP_HOT_SEAT
Hot seat
Team LRN
252
Chapter 19: Wireless Communication
Constant
Meaning
SGP_CYBIKO
Cybiko Player (use when there is only one level of Cybiko player)
SGP_CYBIKO_EASY
Cybiko player, Easy
SGP_CYBIKO_HARD
Cybiko player, Hard
SGP_SINGLE_GAME
Solo game
SGP_CYLANDIA
This will show CyPet names rather than nicknames.
SGP_DONT_INVITE
Does not invite, just returns the CyID.
You can use these flags alone or in combination with other flags to get the results that you desire. If your game is a two-player only game, then you probably won’t use SGP_SINGLE_GAME. Likely, you will never use SGP_CYLANDIA either. If the game you have made is too difficult to make an AI for, then the SGP_CYBIKO, SGP_CYBIKO_EASY, and SGP_CYBIKO_HARD won’t be used. In the example program CyBk19_1, I demonstrate the use of the select_partner function. It’s only one line, and it is in the Prog_Init function. Listing 19.4 partner=select_partner(main_module.m_process,SGP_HOT_SEAT|SGP_CYBIKO,sz_partner_name);
With this line, I’m allowing the “hot seat” and “vs. cybiko” option. Along with the list of all of the other Cybikos in the area, it will list “Computer” and “2 Players 1 Unit” as options. The little menu that comes up as a result of this function has several ways of exiting. One, you can hit Esc to cancel it. In that case, it returns 0. Otherwise, you can press Enter on any of the items in the menu. If the item in question is that of another Cybiko in the area, the CyID of that Cybiko is returned. If another option, such as “Computer” or “2 Players 1 Unit” is selected, one of a number of special constants will be returned. Table 19.2 has a list of these constants and their meanings. Table 19.2 Special return values of select_partner Value
Meaning
SGP_RES_HOT_SEAT
2 players, 1 unit
SGP_RES_CYBIKO
Vs. computer
SGP_RES_CYBIKO_EASY
Vs. computer, easy level
SGP_RES_CYBIKO_HARD
Vs. computer, hard level
SGP_RES_SINGLE_GAME
Single-player game
As you can see, these constants are fairly obviously linked with the flags sent to the select_partner function. All of these values are <0. This cuts down the total number
Team LRN
Chapter 19: Wireless Communication
253
of Cybikos able to be produced from 4 billion to 2 billion. I don’t think there will be a problem.
Launching the Application or Game The select_partner function is really great. It packs a lot of functionality into a single function call (the code to do the same thing with the low-level communications functions is rather long and difficult to understand), but it is limited to simply selecting the player. It does not launch the application on the other machine. For that, we have to use a different function, select_app_partner or select_game_partner. Here is the prototype for select_game_partner, which is likely to be the one you will use most of the time. Listing 19.5 cyid_t select_game_partner ( struct Process * ptr_process, char * sz_game_name, int style, char * sz_partner_name );
This function has, for the most part, the same parameter list as select_partner, with one additional parameter in between ptr_process and style called sz_game_name. This parameter has the name of the game you wish to play with the other person (normally, this will be the same as the file you are currently running). When calling this function, it starts the same way that select_partner does, with a list of people to choose from. However, if you select another Cybiko in the area, after hitting Enter, it will go through a process of determining if the other person has the game on his device. If he does not, it will tell you so, and you can make another choice, or you could cancel out of the program, go to the file uploader, send him the game and then try again. If the other player does have the game on his device, then it will pop up a dialog on his device inviting him to play. If he says yes, the game will launch on his device. Rather than require each player to select the other to play against, the “spawned” copy on the remote machine is sent some parameters in the command line, in the argv array. The argc parameter of the main function will be 3. The value of argv[0] is always the name of the application, and so normally argc is 1. The extra information sent to a spawned copy of a game is the CyID in argv[1] and the nickname of the person who invited you in argv[2]. You can check for these parameters, and then completely skip the partner selection process. In CyBk19_2, I have done just that. This example came almost straight out of the SDK docs. In the main function, there is a simple check. Listing 19.6 if(argc>1) //more than one argument { partner=atoul(argv[1]); //copy cyid strcpy(sz_partner_name,argv[2]); //copy partner name
Team LRN
254
Chapter 19: Wireless Communication
} else { //select a partner partner=select_game_partner(main_module.m_process,"CyBk19_2", SGP_HOT_SEAT|SGP_CYBIKO,sz_partner_name); }
This happens right after the call to init_module. First, I check to see if argc is greater than 1. If it is, I assume that this is a spawned copy, and copy out the information from the argv list. If not greater than 1, it goes through the partner selection process. I don’t check for cancelling out in this example, but doing so would be an easy matter of checking partner for 0. As you can see, select_game_partner really packs a punch. You, as a programmer, don’t really have to worry about much. Heck, in ten lines, I supplied enough code to link two Cybikos for a game session.
Running a Game Session Selecting a game partner is rather easy, as we have demonstrated. Also, sending a message to a remote device is rather easy as well; simply use the send_remote_msg function. Now comes the hard part, the game session itself. There are a lot of unknowns in networked games in general. They are even more compounded when the networked computers in question are wireless, and even worse when the wireless communication has such a limited range. In fact, I could have devoted every page of this book to how to handle the communication, and I still would not exhaust the topic, so instead, I’m reduced to giving you a few vague guidelines, and hoping you come up with something that works. n
The best description of a Cybiko network is “peer to peer,” meaning simply that there is no Cybiko that is the “boss” of the others. Any Cybiko in range is free to send messages to any other Cybiko in its range, and the messages are sent directly to the other device, not through some intermediary. This poses a number of problems. The remote Cybiko might move out of range, or the user of that Cybiko might turn it off, or it might suddenly power down because of low battery charge. Your Cybiko has no way of knowing that this has happened. Cybikos do not send out “I’m leaving range now” messages or “I’m turning off now” messages. All that we ever can know is that a particular message did not reach its destination. At that point, we consider the connection to be broken. This is the best we can do.
Team LRN
Chapter 19: Wireless Communication
n
255
Even though the network is technically “peer to peer,” you may wish to structure your games to have more of a client/server structure. This means the initiating app (the first app, rather than the one launched on a remote device) should keep track of most of the game data. This entails sending a message from the client device to the server device, having the server respond to the request rather than the client, and then having the server tell the client what happened. As you might expect, this will slow down communications somewhat, and is absolutely terrible for games that are not turn based.
Summary This is a short chapter, and while informative, it doesn’t give you all of the tools you need to get a multi-player game up and running. In many ways, multi-player gaming is the most complicated topic on the Cybiko, more complicated even than the UI system or cy3d. I’ve given you the initial push; it’s up to you to make the most of it.
Team LRN
Chapter 20
File System Overview We’ve spoken a bit about the Input, FileInput, Output, and FileOutput classes, and I’ve shown you their uses. However, they aren’t the entire story when it comes to files stored on the Cybiko. You have, I’m sure, run the Uploader & File Manager application on your Cybiko at least once. This application lists all of the files in your Cybiko’s storage. Well, it doesn’t show all of them. Certain files that are important to CyOS are deliberately not listed and thus not accessible through the file manager app. However, the fact that the file manager can list the files on your device means that you can too. The file manager, after all, is just an app like one that you or I would write. Also, you might want to make some sort of file editor utility, and being able to browse the files on your Cybiko might be a useful thing. Also in this chapter, we are going to cover the application archive. Each .app file is an archive containing a number of resources (you already knew this). You are already familiar with accessing resources within your own program’s .app file. Now I’m going to show you how to access resources in other application archives. With this, you can make one program that uses the resources in another app for its graphics, sounds, and whatever else is in it. In this way, you can make a modifiable program where all you do is make another application archive to make the game play differently. Finally, I will cover the “other devices” that you can store files on for your Cybiko. This includes the 1 MB memory card and the MP3 player with a smart media card.
The File and FileFind Class Probably the most common task you would like to perform with the Cybiko file system is determining which files exist in it—sort of a directory listing. To do this, you have to make use of both the File and FileFind classes.
256
Team LRN
Chapter 20: File System
257
The File Class First, we will take a look at the File class, since you need knowledge of it in order to effectively use the FileFind class. The File class consists of the File struct and a number of functions for working with the File struct, each of which start with File_. There are two groups of functions that start with File_. Those that I will present here work with the File struct, and I will later talk about those functions that do not work with File objects but instead tell us something about the entire file system. First, the File struct itself. See Listing 20.1. Listing 20.1 struct File { char name [MAX_NAME_LEN]; long size; time_t modification_time; }
This is a pretty simple struct. The name member is a string containing the name of a file. The size member tells us how many bytes are in this file, and modification_time tells us when the file was last modified. The modification_time member is a time_t. The time_t type is a binary encoded way to represent time. I cover it elsewhere. Mainly, we are concerned with the name of a file and the size of that file. Usually, the last time a file was modified is unimportant except when making an editor. You can construct your own File objects manually. Most of the time, you probably won’t do this, instead relying on a FileFind object, but you might, so here it is. Listing 20.2 shows the constructors and destructor for the File class. Listing 20.2 struct File* File_ctor (struct File *ptr_file); struct File* File_ctor_Ex (struct File *ptr_file, char *sz_file_name); void File_dtor (struct File *ptr_file, int memory_flag);
The first constructor, File_ctor, constructs a File object that is uninitialized. To use this File object later, you will need to call File_get_info, or use a FileFind object to fill it in. The second constructor, File_ctor_Ex grabs the information for a file whose name is placed into the second parameter. Both constructors operate as all other constructors do. Finally, the File object destructor works exactly like all of the other object destructors. While I’m not actually covering the use of time_t right here, the getter and setter for a file’s date have to be covered here. These functions can be found in Listing 20.3. Listing 20.3 time_t File_get_date (struct File *ptr_file); bool File_set_date (struct File *ptr_file, time_t date);
Team LRN
258
Chapter 20: File System
The use of File_get_date and File_set_date should be fairly obvious. They retrieve or set the modification_time member of a file, respectively. The last two File functions we are concerned with are File_get_info and File_is_free. These are shown in Listing 20.4. Listing 20.4 bool bool
File_get_info (struct File *ptr_file, char *sz_file_name); File_is_free (struct File *ptr_file);
The File_get_info function loads in information about a file specified by sz_file_name. This is essentially like calling File_ctor_Ex. If the file does not exist, File_get_info returns FALSE. This function is a handy way to quickly tell if a file does or does not exist. File_is_free checks to see if it is okay for this file to be opened for input or output. A file that is currently open will return FALSE. If it returns TRUE, you can feel free to perform file operations on it with FileInput or FileOutput. File_is_free answers the question, “is it okay for me to modify this file right now?”
The FileFind Class The File class, by itself, is really only good for looking at information for files that we already know the name of. So, if you rely on an external file for something, and that file has a specific name, you can make effective use of the File class for making sure it exists. However, if you are making use of external files, you often can make use of several of them, not just one or two, and the names are unlikely to be a particular name. For stuff like this, you will want to know what files are on the system. To do this, you need to use the FileFind class. The FileFind class is constructed using FileFind_ctor, and destroyed using FileFind_dtor. These functions are both shown in Listing 20.5. Listing 20.5 struct FileFind* void
FileFind_ctor (struct FileFind *ptr_file_find, struct File *ptr_file_info, char *sz_mask) FileFind_dtor (struct FileFind *ptr_file_find, int memory_flag)
The destructor operates as all other destructors do, so I’ll concentrate on the constructor. The first parameter is a pointer to a FileFind object, as you might expect in a constructor. The return value is also this pointer. The second parameter is a pointer to a File object, which must have been constructed with File_ctor prior to its use here. This File object is used internally by FileFind. The final parameter, sz_mask, is the search mask for the file. You can use wildcards (?) in this string for a position where there is a character but it doesn’t matter what that character is, and (*) for a group of characters that don’t matter. For example, if you wanted to find the files pic1.pic, pic2.pic, and pic3.pic, you might use “pic?.pic” as a mask, but if you wanted to find all
Team LRN
Chapter 20: File System
259
files that had .pic on the end, you would use “*.pic”. The constructor does not move to the first element. It sits before the beginning of the list. After you have constructed a FileFind object, you may wish to begin a new, different search. You can do this by using FileFind_init, shown in Listing 20.6. Listing 20.6 void
FileFind_init (struct FileFind *ptr_file_find, struct File *ptr_file_info, char *sz_mask);
Essentially, FileFind_init has the same parameters as FileFind_ctor, and it does the same exact thing—it begins a search session. Using FileFint_init allows you to recycle a FileFind object without having to destroy it and construct it again. Once you have a search session started, you check for more elements in the list, and move to the next element in the list if one begins. See Listing 20.7 for the functions that help you do this. Listing 20.7 bool FileFind_has_more_elements (struct FileFind *ptr_file_find) struct File* FileFind_next_element (struct FileFind *ptr_file_find)
The FileFind_has_more_elements function checks to see if there is another file to be found that matches the mask you supplied in either FileFind_ctor or FileFind_init. It returns TRUE if another file is to be found, and FALSE if no more files that match the criteria exist. FileFind_next_element moves to the next element in the list. If there is no element to be found, it returns NULL. If another element is found, it returns a pointer to a File object (the same file object that you supplied in FileFind_ctor or FileFind_init) that contains information about the next file in the list. Listing 20.8 shows a common way to search for files matching a particular condition. In this listing, I am iterating through all of the files on the Cybiko that have .app at the end of their names. Listing 20.8 struct File file_info; struct FileFind file_find; File_ctor(&file_info); FileFind_cotr(&file_find,&file_info,"*.app"); while(FileFind_has_more_elements(&file_find)) { FileFind_next_element(&file_find); //do something with file_info here, we found }
//construct a blank File object //start the search //check for more elements //move to the next element another file
If you instead wanted to find all of the .pic files on your Cybiko, you could replace “*.app” with “*.pic”, and so on. The code that iterates through a file list remains much the same as the code in Listing 20.8. The only difference is what you do with the file’s information once you have it.
Team LRN
260
Chapter 20: File System
Archive Class You are already at least somewhat familiar with the Archive class, since you make use of an archive every time you make a program, and every time you open up a resource, the Cybiko SDK invisibly makes use of the Archive class to retrieve the resource. The Archive class encapsulates an archive file, which includes .app files. Other examples of archives include .dl files. However, an archive is not required to be a program or dynamic link library. Indeed, you can simply have an archive filled with resources. An archive is simply a file that is put together by the use of Filer.exe. What this means to us as programmers is that we can decouple our programs from our data, if we so desire. We can have a single, small program that uses data in another archive file. With this ability, we can switch data files for a game, and we suddenly have a game that appears different from the original. This adds replay value to the game, and we can also make a nice editor program to distribute with the game so that your users can make their own special mod files and share them with other users of your game. Many of the most successful games in history enjoyed their success in part because of just this sort of thing.
Creating and Destroying an Archive Object An Archive object, like any other object in the Cybiko SDK, requires that you use a constructor and destructor to make and destroy the object. The constructor and destructor are shown in Listing 20.9. Listing 20.9 void void
Archive_ctor (struct Archive *ptr_archive, char *sz_archive_name) Archive_dtor (struct Archive *ptr_archive, int memory_flag)
The destructor works like any other destructor for a Cybiko object. You put FREE_MEMORY or LEAVE_MEMORY in the second parameter as appropriate. The constructor takes two parameters, a pointer to an allocated Archive object that you wish to construct, and the filename of the archive you wish to open. Oddly, Archive_ctor returns no value, for reasons unknown.
Retrieving Information about an Archive Object Once you have constructed an Archive object, there are a few things that you probably want to know about it before you start opening the resources within it. First, you might need to know whether or not the Archive is a proper archive. To do this, use the Archive_is_good function, shown in Listing 20.10. Listing 20.10 bool
Archive_is_good (struct Archive *ptr_archive)
Team LRN
Chapter 20: File System
261
This function takes a single parameter, a pointer to the Archive object you wish to know about. The return value is TRUE if the Archive is a valid archive, and FALSE if it is not. This function will return FALSE if the archive name supplied to the constructor function does not exist, or if the file is not a file created by Filer.exe. Before opening an archive, you still probably want to make sure the file exists in the first place before trying to construct an Archive object from it. Immediately after constructing an Archive object, you will want to use Archive_is_good to make sure that the archive is valid. Another piece of information stored with the archive is the Archive_archive_name function, shown in Listing 20.11. Listing 20.11 char*
Archive_archive_name (struct Archive *ptr_archive)
This function returns the name of the archive. You probably won’t need this function much, since you supply the name of the archive in the call to the Archive constructor.
Connecting with an Archive Object’s Resources After you have constructed an Archive object, and verified its validity, the next thing you’ll want to do is start reading from or writing to its resources. Before you do this, however, you may wish to know how many entries there are, and what the names of these entries might be. To determine how many entries are in an Archive, you use Archive_count_entries, shown in Listing 20.12. Listing 20.12 int
Archive_count_entries (struct Archive *ptr_archive)
This function returns the number of resources stored in the Archive object. This is quite useful if you are iterating through the Archive to list all of its resources. Along the same line is the Archive_get_name function, which, given a resource’s index, will give you the name of the resource. This function is shown in Listing 20.13. Listing 20.13 char*
Archive_get_name (struct Archive *ptr_archive, int entry)
To this function you supply a pointer to an Archive object and an int containing the index into the resource list (the first resource has an index of 0). Keep in mind that there is an option that Filer.exe uses that does not list a name for a resource within an archive file. This is often true for the executable code itself, and may be true for other “hidden” resources. To access such hidden resources, you must use their indexes, since no name exists.
Team LRN
262
Chapter 20: File System
Opening a Resource for Input Finally, we get to what we really want to do, namely opening a resource in an external archive for input. This will by far be the most common use for the Archive object, since it is rarely necessary to write to an archive file. There are two functions that retrieve an Input object pointer from an Archive object’s resource. These functions are Archive_open and Archive_open_Ex. These are shown in Listing 20.14. Listing 20.14 struct Input* struct Input*
Archive_open (struct Archive *ptr_archive, int entry) Archive_open_Ex (struct Archive *ptr_archive, char *sz_name)
Archive_open uses an index into the resource list to open a resource for reading. Archive_open_Ex uses a name of a resource. Both functions work about the same way. They return an Input pointer from which you can read data. If an invalid entry or resource name is sent to these functions, the return value will be NULL.
Opening a Resource for Output Writing to a resource is far less common than reading from them, but situations do come up, such as configuration resources or high score tables. The functions to open up an Archive object’s resource for writing are Archive_open_write and Archive_ open_write_Ex. These are shown in Listing 20.15. Listing 20.15 struct Output* struct Output*
Archive_open_write (struct Archive *ptr_archive, int entry) Archive_open_write_Ex (struct Archive *ptr_archive, char *sz_name)
These functions are comparable to the Archive_open and Archive_open_Ex functions, except that instead of opening Input objects, they open Output objects. Except for that, these are identical in function. They return NULL if for some reason the resource cannot be opened.
Other Devices We need to discuss one last bit about the file system, then I’ll let you be. The topic is “other devices.” By this I mean “external” devices for storing data. Examples are the 1 MB expansion card and the MP3 player with a smart media card. If you have either of these, you were undoubtedly disappointed to see that when you put apps onto it, they did not show up on the desktop, and you instead had to run these programs through the file manager application. In the next chapter, I’ll show you why you don’t actually have to do that, but suffice it to say that with a little creativity, you don’t have to use the file manager to run your apps on other devices. So, the main question is, how do we access these other devices?
Team LRN
Chapter 20: File System
263
There are four variables in the SDK that allow us to access these devices. The variables are MP3_DRIVE_NAME, DEFAULT_DRIVE_NAME, DRIVE_A_NAME, and DRIVE_B_NAME. The MP3_DRIVE_NAME (with a value of “MP3”) is fairly obvious. It is the name of the MP3 device, and it can be used to access that device. The DEFAULT_DRIVE_NAME (with a value of “default”) is also fairly obvious. It refers to the built-in flash device on which you normally store files, and the device on which CyOS is stored. DRIVE_A_NAME (with a value of “a”) and DRIVE_B_NAME (with a value of “b”) are less obvious. They are too generically named. While I’m not aware (at this point) of a use for drive B, drive A is used for the 1 MB expansion card. To access a file on another device, put a string together like: “\drivename\filename.ext”, where drivename is the name of a device and filename.ext is the name and extension of your file. If you don’t put “\drivename\” at the beginning of the string, the default drive will be used. Easy enough?
Getting Information about a Device Now that you know how to access something on an external device, it would be nice if you could find out some information about these devices, such as how much space is available for storage, how many files there are, and so on. Listing 20.16 shows some functions dealing with devices. They are technically static members of the File class. Listing 20.16 long File_free_bytes_total (char *sz_device_name) long File_free_user_bytes_total (char *sz_device_name) long File_files_total (char *sz_device_name) long File_bytes_total (char *sz_device_name) long File_blocks_total (char *sz_device_name) long File_bad_blocks_total (char *sz_device_name) int File_block_size (char *sz_device_name) long File_free_blocks_total (char *sz_device_name) bool File_bootable (char *sz_device_name)
While most of these functions are named for what they do, I’ll explain them (briefly) anyway. File_free_bytes_total retrieves the number of free bytes for a device. File_free_ user_bytes_total will give you the number of bytes free for use by the user (you). File_files_total gives you the number of files on a device. File_bytes_total gives you the total number of bytes on a device, and between this function and File_free_bytes_ total, you can determine how many bytes are being used on a device File_blocks_total tells you how many blocks are on a device. File_bad_blocks_total tells you how many of these blocks are bad. File_block_size tells you how many bytes are in a block. Finally, File_bootable tells you if a device is bootable or not.
Team LRN
264
Chapter 20: File System
These functions can indirectly be used to determine if a given device is attached to the system or not. If a device exists, then File_bytes_total will be non-zero.
mFileName Functions These functions just kind of sit out in left field. They have to do with filenames and device names, so they kind of belong in this section. Quite frankly, I didn’t know where else to put them. They are shown in Listing 20.17. Listing 20.17 int mFileName_max_file_name_length (void) char* mFileName_current_device_name (void) bool mFileName_is_valid_file_name (char *sz_full_name) void mFileName_make_path (char *sz_full_name, char *sz_device_name, char *sz_file_name) void mFileName_split_path (char *sz_full_name, char *sz_device_name, char *sz_file_name) void mFileName_get_path (char *sz_full_name, char *sz_path)
The mFileName_max_file_name_length (a darn long function name, if you ask me) function lets you know how long of a filename you are allowed. It takes no parameters and returns an int. mFileName_current_device_name tells you what the name of the current device is. It takes no parameters and returns a pointer to a string. mFileName_is_valid_file_name can be quite useful. It lets you know if a potential filename is a valid filename. Not all characters are allowed in filenames, most notably “*” and “?”, so if you ever prompt for a filename, it is a good idea to use this function to ensure its validity. mFileName_make_path can also be useful. It takes a device name (in the sz_device_name parameter) and a filename (in the sz_file_name parameter), and combines them into a fully qualified name, storing this name in the string pointed to by sz_full_name. The mFileName_split_path function does the opposite of mFileName_make_path. It takes the fully qualified name of a file (in the sz_full_name parameter) and puts the device name into the string pointed to by sz_device_name and the filename into the string pointed to by sz_file_name. mFileName_get_path does a similar thing to mFile_name_split_path, except that only the device name is retrieved.
Summary As you’ve undoubtedly seen by now, the file system for the Cybiko isn’t very complicated. It doesn’t need to be to do its job. You can easily search for files, gain information about a file storage device, and even open up archives other than the one your application is stored in. Making the most out of the Cybiko file system is important, because you can then get the full potential out of the Cybiko itself.
Team LRN
Chapter 21
Modules and Processes Overview The information in this chapter isn’t really totally necessary to program for the Cybiko. Indeed, you can get by with only a dim awareness of the information in this chapter. In its current state, you cannot use the SDK to write a multi-threaded application (not directly, anyway), so much of the subject matter on threads and processes is moot, at least for now. However, this will not always be so, and this is likely to be the only book on Cybiko development made for a while, so at least giving some coverage of this subject matter is the correct thing to do.
module_t Unlike other topics, I’m going to explore this one from a high level of abstraction to a low level of abstraction, going through the many levels in between. It wouldn’t make much sense the other way around. We start with struct module_t, which by now you should be quite used to seeing. In every application, you have a variable of this type called main_module, and you use this variable to access the screen through its member m_gfx and to retrieve messages through its member m_process. The main_module variable is perhaps the single most important variable in any of your programs. And yet, we have never discussed how it works and what goes into it. We’ve just taken for granted that the init_module function will magically do all that we need to get our applications up and running. The module_t variable is kind of like an automobile: you don’t need to know how it works in order to use it. The module_t struct contains two members, m_gfx and m_process. The declaration looks something like this:
265
Team LRN
266
Chapter 21: Modules and Processes
Listing 21.1 struct module_t { struct DisplayGraphics* m_gfx; struct cWinApp* m_process; };
The m_gfx member is a pointer to a DisplayGraphics object. We’ve talked about the DisplayGraphics class enough already, so we won’t talk about it here. We shall instead concentrate on the m_process member.
cWinApp (m_process member of module_t) You’ve seen the cWinApp struct before, though we haven’t really explored it all that much. It is used for the UI system for message handling in forms. Again, we haven’t gone into detail on its inner workings, but we’ve managed to use it just fine. We know that we can read messages from it, and now that you are accustomed to the Cybiko way of doing things, you see the lowercase “c” in front of cWinApp, and you suspect it has something to do with the UI system. You’re right, it does. Given that Cybiko C is primarily function based with a few C++ extensions, it is surprising to see something like cWinApp, which looks to be a case of multiple inheritance, one from AppGeneric and another from cClip. This is an illusion. cWinApp only derives from AppGeneric (which I’ll talk about in a moment), and its relationship to cClip is fake. You can treat cWinApp as a cClip only so often. You can add cObjectderived objects to it, but you cannot add a cWinApp to a normal cClip object. At best, we can say that cWinApp emulates a cClip’s functionality in addition to the functionality provided by AppGeneric. So, now let’s talk about the object hierarchy of cWinApp, minus the cClip emulation stuff. The parent class of cWinApp is AppGeneric. The parent class of AppGeneric is Process. The parent class of Process is Thread, and the parent class of Thread is SystemThread. Confused yet? Wondering why there is such a long hierarchy for the concept of threads? Now that we’ve gone from top to bottom, we’re going to explore this weirdness from bottom to top, and see what we can learn.
SystemThread To start us off, I’m going to show the declaration for struct SystemThread, as it is seen in the process.h include file of the SDK. Listing 21.2 struct { char bool char
SystemThread SystemThread_Members_STUB[40]; wake_state; wake_state_PADDING[3];
Team LRN
Chapter 21: Modules and Processes
267
void* controller; clock_t total_time; clock_t last_time; clock_t start_time; clock_t end_time; bool resume_status; char resume_status_PADDING[3]; char* stack_top; char* stack_bottom; long flags; struct Module* module; char priority; char saved_priority; char priority_PADDING[2]; struct SystemThread* next; void* sp; void* sp_peak; void* queue; };
In the SDK docs, very little of this is documented, other than the module member. In reality, most of these members don’t matter much to us, since we will hardly ever be at the low level of managing threads on our own. Some day, Cybiko, Inc., will let us know what all of this does. Until then, we can only speculate. The important thing about the SystemThread class is the functions it gives us. These functions are listed below. Listing 21.3 bool SystemThread_pause (struct SystemThread *ptr_thread, clock_t timeout); priority_t SystemThread_get_priority (struct SystemThread *ptr_system_thread); void SystemThread_set_priority (struct SystemThread *ptr_system_thread, priority_t priority);
The SystemThread_pause function is useful for creating delays. You pass a pointer to a SystemThread (or derived) object and a number of clock ticks, and the SystemThread will pause for that many clock ticks. The function returns FALSE if the timeout has passed. SystemThread_get_priority and SystemThread_set_priority work together. Without going into too much detail about how threads work, a thread with a higher priority gets more processor time than a thread with a lower priority. You use SystemThread_ get_priority to retrieve the priority of a thread, and SystemThread_set_priority to set the priority of a thread. The priority_t type has three predefined values: PR_IDLE, PR_NORMAL, and PR_RUNTIME. PR_IDLE has a value of 5, PR_NORMAL a value of 50, and PR_RUNTIME a value of 100. As for the rest of the information in the SystemThread struct, I suggest treating it as read-only, if you need to work with it at all. Many of the members (most of them) aren’t documented, and changing them can cause unpredictable behavior. Take care with this structure.
Team LRN
268
Chapter 21: Modules and Processes
Thread One step above SystemThread is the Thread structure. Thread adds two members to the ones already in existence in SystemThread, one called stack_size and one called stack_size_PADDING. There is no documentation on what these members do or are. You’ll probably want to leave them alone. One curious thing about the Thread struct I found out while poking around in the SDK header files (heck, I found most of the information for this chapter in header files). There is a global variable called current_thread. This global is a struct Thread*. I’m not sure what it does or how to use it, but if you’re interested in poking around, it seems like current_thread might be a good place to start.
Process The next step up from Thread is Process. With Process, we start to actually see some interesting things happening. Process has facilities for requesting the focus and message retrieval, along with a function that allows us to retrieve the DisplayGraphics object pointer. Finally, this object hierarchy is heading in a useful direction! There exists a Process_pause function, which works the same as the SystemThread_pause function (in fact, it is the same function). This function is shown below: Listing 21.4 bool Process_pause (struct Process *ptr_process, clock_t timeout);
Since we already covered this function in SystemThread, I won’t cover it again. The Process versions of get_priority and set_priority are missing, but you can use the SystemThread versions just fine on a Process variable. Not that you’d ever really need to or want to, but you could. And the Process class adds a bit of functionality to Thread. First, there are functions for dealing with the focus. By focus I’m talking about the application focus. When an application has focus, it gets to draw to the screen, read from the keyboard, and so on. When it does not have focus (sometimes called “blurred”), it only performs tasks in the background, if it does anything at all. Listing 21.5 struct Process* Process_request_focus (struct Process *ptr_process) ; bool Process_has_focus (struct Process *ptr_process) ;
The Process_request_focus function does what it says it does: it requests focus from CyOS. It may or may not get the focus. The return value is the process that had the focus prior to this function call. To check whether or not your application has the focus, you use Process_has_ focus (again, just like it says it does). If it has the focus, this function returns TRUE. If not, FALSE. Every process has a name. You give your application’s process a name in the Root.inf file, in line seven. When you are sending messages, you need to know the
Team LRN
Chapter 21: Modules and Processes
269
name of the process to which you are sending it, and so there is the Process_get_ name function: Listing 21.6 char*
Process_get_name (struct Process *ptr_process) ;
This function returns a pointer to a string containing the name of the process. Speaking of messages, there are a few Process functions for dealing with sending and retrieving messages. I’m going to list them here, even though they have been covered elsewhere (or should, by now, be fairly obvious as to their use). Listing 21.7 void Process_put_message (struct Process *ptr_process, struct Message *ptr_message) ; struct Message* Process_get_message (struct Process *ptr_process, long timeout, int min, int max) ; struct Message* Process_peek_message (struct Process *ptr_process, bool remove, int min, int max);
And finally, the Process class gives us the ability to acquire a pointer to the DisplayGraphics object. Listing 21.8 struct DisplayGraphics*
Process_get_display (void) ;
You should notice that this function, while it begins with the word Process, does not actually have any parameters. It returns the DisplayGraphics pointer. You could, in fact, use this function in all of the places where we normally put main_module.m_gfx. So, as you can see, Process doesn’t add a whole lot, but what it does add is significant. With these functions, we are closer to being able to make a useful application.
AppGeneric Up until AppGeneric, there is not quite enough stuff available to be able to make an application. With AppGeneric, we can, even though we always use cWinApp, which adds even more capabilities. First, here’s a list of functions for AppGeneric that already existed in Process. I feel no need to explain them twice. I simply wish to show you that they still apply for AppGeneric. Listing 21.9 bool AppGeneric_pause (struct AppGeneric *ptr_app_generic, clock_t timeout) struct Process* AppGeneric_request_focus (struct Process *ptr_app_generic) bool AppGeneric_has_focus (struct AppGeneric *ptr_app_generic) struct DisplayGraphics* AppGeneric_get_display (void) struct Message* AppGeneric_peek_message (struct AppGeneric *ptr_app_generic, bool remove, int min, int max) struct Message* AppGeneric_get_message (struct AppGeneric *ptr_app_generic, long timeout, int min, int max)
Team LRN
270
Chapter 21: Modules and Processes
void
AppGeneric_put_message (struct AppGeneric *ptr_app_generic, struct Message *ptr_message) char* AppGeneric_get_name (struct AppGeneric *ptr_app_generic)
The new facilities added by AppGeneric are of two types, those dealing with the graphics display and those dealing with message handling. The first of these functions is AppGeneric_init_display, shown here: Listing 21.10 struct DisplayGraphics*
AppGeneric_init_display (void)
This function takes no parameters and returns a pointer to the DisplayGraphics object. For all intents and purposes, this is the same as calling the AppGeneric_get_display function (which is the same as the Process_get_display function). Why they put two names on the same function is beyond me. Next is AppGeneric_clear_screen, shown below. Listing 21.11 void
AppGeneric_clear_screen (void)
This function takes no parameters and returns no values. It clears the screen to CLR_WHITE. In my book, this function is not very useful. I personally would go with DisplayGraphics_fill_screen. These last three functions deal with messaging. The first one is AppGeneric_proc, which is important for handling messages that are unhandled by the main application. Listing 21.12 bool
AppGeneric_proc (struct AppGeneric *ptr_app_generic, struct Message *ptr_message)
This function takes two parameters, a pointer to an AppGeneric object and a pointer to a Message object. It returns a bool—TRUE if the message was processed, and FALSE if it was not. What this function actually does for you is allow you to use the Cybiko help system, and when the help button is pressed, it will pop up the 0.help file (or whatever the current help context is). AppGeneric_proc does not delete messages, so you are still responsible for deleting messages when this function returns. These last two are really interesting, in that I have only the foggiest notion of what they do, and I’ve never needed them. These are the AppGeneric_cancel_shutup and AppGeneric_ext_cancel_shutup functions. Listing 21.13 void void
AppGeneric_cancel_shutup (struct AppGeneric *ptr_app_generic) AppGeneric_ext_cancel_shutup (void)
AppGeneric_cancel_shutup takes a single parameter, a pointer to an AppGeneric object, and returns no value. It cancels a task switch (??) made by the task manager (???), and removes any MSG_SHUTUP messages from this application’s message queue.
Team LRN
Chapter 21: Modules and Processes
271
AppGeneric_ext_cancel_shutup takes no parameters and returns no values. It, too, cancels a task switch (????), but does not remove the MSG_SHUTUP messages. Now, you might be asking why all of the question marks. MSG_SHUTUP is barely documented in the SDK docs. The docs say that MSG_SHUTUP exists, and that’s about it. In the example programs that come with the SDK, MSG_SHUTUP is treated as if it were a MSG_QUIT. Okay, that’s fine. This whole task manager and task switching thing is only mentioned during the AppGeneric section of the docs. I’ve worked on other multitasking operating systems, and I know the basic theory of how they work, and I know that CyOS is a multitasking OS. However, the total lack of documentation on this stuff is frustrating.
cWinApp The cWinApp is what brings together the classes dealing with threads and processes, and the classes that comprise the UI system. In a way, it inherits behavior from both AppGeneric and cClip. It inherits all of the functions from AppGeneric, as shown here: Listing 21.14 bool cWinApp_defproc (struct cWinApp *ptr_win_app, struct Message *ptr_message) bool cWinApp_pause (struct cWinApp *ptr_win_app, clock_t timeout) struct DisplayGraphics* cWinApp_init_display (void) void cWinApp_clear_screen (void) void cWinApp_cancel_shutup (struct cWinApp *ptr_win_app) void cWinApp_ext_cancel_shutup (void) struct Process* cWinApp_request_focus (struct cWinApp *ptr_win_app) bool cWinApp_has_focus (struct cWinApp *ptr_win_app) struct DisplayGraphics* cWinApp_get_display (void) struct Message* cWinApp_peek_message (struct cWinApp *ptr_win_app, bool remove, int min, int max) struct Message* cWinApp_get_message (struct cWinApp *ptr_win_app, long timeout, int min, int max) void cWinApp_put_message (struct cWinApp *ptr_win_app, struct Message *ptr_message) char* cWinApp_get_name (struct cWinApp *ptr_win_app)
The only difference here is that instead of having a cWinApp_proc, the name of the function is cWinApp_defproc. Otherwise, it’s the same idea. Also, it inherits all of the functions from cClip, as shown in the following listing: Listing 21.15 void cWinApp_Disconnect (struct cWinApp *ptr_win_app) bool cWinApp_Select (struct cWinApp *ptr_win_app) void cWinApp_update (struct cWinApp *ptr_win_app) struct cClip* cWinApp_GetParent (struct cWinApp *ptr_win_app) void cWinApp_Hide (struct cWinApp *ptr_win_app) void cWinApp_Show (struct cWinApp *ptr_win_app) void cWinApp_Disable (struct cWinApp *ptr_win_app) void cWinApp_Enable (struct cWinApp *ptr_win_app)
Team LRN
272
Chapter 21: Modules and Processes
void
cWinApp_AddObj (struct cWinApp *ptr_win_app, struct cObject *ptr_object, int x, int y) void cWinApp_InsObj (struct cWinApp *ptr_win_app, struct cObject *ptr_object, int x, int y, int index) void cWinApp_RemObj (struct cClip *ptr_win_app, struct cObject *ptr_object) bool cWinApp_SelectFirst (struct cWinApp *ptr_win_app) bool cWinApp_SelectPrev (struct cWinApp *ptr_win_app, bool round) bool cWinApp_SelectNext (struct cWinApp *ptr_win_app, bool round) void cWinApp_Scroll (struct cWinApp *ptr_win_app, struct rect_t *rectangle) void cWinApp_Scroll_Ex (struct cWinApp *ptr_win_app, int x, int y) void cWinApp_SendScroll (struct cWinApp *ptr_win_app) int cWinApp_GetShifty (struct cWinApp *ptr_win_app) int cWinApp_GetShiftx (struct cWinApp *ptr_win_app) int cWinApp_GetCount (struct cWinApp *ptr_win_app) struct cObject* cWinApp_get_by_index (struct cWinApp *ptr_win_app, int index) int cWinApp_FindObj (struct cWinApp *ptr_win_app, struct cObject *ptr_object) struct cObject* cWinApp_GetSelectedObject (struct cWinApp *ptr_win_app)
So you can, if you wish, treat your cWinApp object as though it were a form, adding buttons and labels and so on. I personally don’t, because I write games, and games are not often UI based but rather just use a few forms and dialogs here or there.
Summary This has taken you through the chain of classes that make up the threads and processes part of the API. Most of this you will likely never use. I included it primarily to be as complete as possible.
Team LRN
Chapter 22
Odds and Ends Overview This chapter contains a number of various topics that weren’t covered elsewhere. These are still important, but there was no other place to put them. You’ll see why after you have read a little ways.
imin and imax These are just two little throw-away functions for finding the lesser or greater of a pair of integers. I’m not going to say too much about them. Here they are. Listing 22.1 int imax(int first,int second); int imin(int first,int second);
Both of these functions take two integer values and return an int. The imin function will return the lesser of the two values, and imax will return the greater of the two values. Before dismissing these functions as completely useless and stupid, consider the alternatives. Here’s the equivalent code for imin and imax: Listing 22.2 //imax equivalent code if(first>second) answer=first; else answer=second; //imin equivalent code if(first<second)
273
Team LRN
274
Chapter 22: Odds and Ends
answer=first; else answer=second;
I don’t know about you, but I much prefer “answer=imin(first,second);” or the equivalent line using imax to the four-line equivalent code shown in Listing 22.2. These may be silly functions to have, but they are darn handy at times.
Memory Management Everything on the Cybiko dealing with your application resides in memory, including the code the application is running on and the variables that the code manipulates. There is extra memory not involved in either of these tasks, and that memory is free for the taking. You can set aside a bit of memory to store additional variables, and access everything through pointers. This is usually a good idea when dealing with large local variables, since there is a stack limitation. Setting aside memory for storage is called dynamic allocation. I’ve already touched on using malloc to dynamically allocate objects. Now I’m going to cover the full gamut of memory management functions for the Cybiko. If you have used ANSI C before, you should recognize many of these functions as being derived from ANSI C. No matter what platform you are programming for, there are a certain number of memory management functions that you just cannot do without.
Allocation, Reallocation, and Deallocation The most common memory management functions are those that allocate memory (set aside a block of memory) and deallocate memory (tell the computer that a block of memory that you set aside is no longer being used by you and can be freed to be used for other things). The inner workings of a memory management system are way beyond our scope, but suffice it to say that it is pretty sophisticated. Luckily, we have some easy functions to simplify the task for us. Listing 22.3 char* malloc (size_t size) void* calloc (size_t number, size_t size) void* realloc (void *ptr_memory_block, size_t new_size) void free (void *ptr_memory_block)
The first function, malloc, is short for “memory allocation.” It takes a single parameter, the number of bytes you would like to set aside. It returns a pointer to the newly allocated memory, or NULL if there wasn’t a large enough block available. The next function, calloc, is short for “count allocation,” and it is used for allocating arrays. This has two parameters, the number of bytes in each element and the number of elements in the array. Essentially, calloc(number,size) is like calling
Team LRN
Chapter 22: Odds and Ends
275
malloc(number*size). Like malloc, it returns a newly allocated pointer, or NULL if not enough space was available. The third function, realloc, is for resizing an allocated block of memory. You pass to it a pointer to an already allocated block of memory, you supply a size, and it allocates a block of memory of the new size for you, copies the contents of the old block, and returns a pointer to the new block if space is available. This is an expensive operation, and you definitely don’t want to be doing it often. Finally, the free function returns a block of memory back to the memory pool. When using objects, you are indirectly calling free whenever you put FREE_MEMORY as the second parameter of a destructor.
Running on Empty The Cybiko is a memory constrained environment. That means you can’t go hog wild in your program with memory allocation. Eventually (meaning “sooner than you think”), you will run out of memory, especially if you are dealing with huge blocks of memory. To help counteract this, there are two functions you can check to see how much memory is available. Listing 22.4 bool is_low_memory (void) size_t get_safety_pool_size (void)
The is_low_memory function returns TRUE if memory is “dangerously low.” When memory is dangerously low, heaven help you. Pretty much all you can do is free as much memory as you can. The get_safety_pool_size function will return the largest block of memory that you can allocate. If you are dealing with a lot of memory allocation, you might want to check the value of this function before allocating or reallocating anything.
Memory Manipulation The following functions are absolutely essential for memory manipulation. Don’t leave home without them. All three of these are based on ANSI C. Listing 22.5 void* void* void*
memset (void *ptr_buffer, int value, size_t size) memcpy (void *ptr_buffer, void *ptr_source, size_t size) memmove (void *dst, void *src, size_t size)
The memset function fills a set number of bytes (size) to a particular numerical value (value). This is normally used to set all values of a struct to zero, thus “zeroing out” the structure.
Team LRN
276
Chapter 22: Odds and Ends
The memcpy function copies one block of memory (ptr_source) into another block of memory (ptr_buffer) and copies a set number of bytes (size). If ptr_buffer and ptr_source overlap, there is a good chance that the copy operation will garble the data. To avoid this garbling, use memmove. It has essentially the same parameter list as memcpy, except that in the case of overlapping memory it will work just fine, copying either from front to back or back to front as appropriate to make sure that the memory is not garbled.
Other Memory Management Functions These are just a couple of odds and ends. You may never find a use for these functions, but I could not claim to have a complete reference without presenting these. Listing 22.6 int memcmp (void *ptr_buff_1, void *ptr_buff_2, size_t size) void memory_dump (void *ptr_memory, int size, int string_length)
The memcmp function will compare two blocks of memory, pointed to by ptr_buff_1 and ptr_buff_2, respectively. The size of the blocks to compare is given by the size parameter. This function returns –1 if the first block is “less than” the second block, 0 if the blocks are “equal,” or 1 if the first block is “greater than” the second block. The method of comparison is the same as for the strncmp function. The memory_dump function is useful for debugging (sometimes). It will dump a block of memory (pointed to by ptr_memory) of a particular size (according to the size parameter) onto the console. The string_length parameter specifies how many characters to dump on each line.
Random Numbers Random numbers (actually, pseudo-random numbers, as a computer is an ordered beast where chaos’s fingers cannot intrude) have many uses, especially in games. You might need to simulate the throwing of a six-sided die, or a random percentage, or whatever. The only times you don’t need random numbers is when making something like a word processing program, which has no need for such things. To generate a random number, you use a function called random. This function is shown below. Listing 22.7 long random(int max);
This function takes a number and returns a random number that is not less than zero but less than max. So, if you have “random(6);” you will get a number between 0 and 5 inclusively.
Team LRN
Chapter 22: Odds and Ends
277
The Cybiko random function is a bit of a departure from ANSI C, which does indeed have a function to generate random numbers called rand. Unlike rand, you can specify a range for your random numbers, so I guess this departure isn’t totally a bad thing. Most of the time, you can get away with generating a random number between 0 and (max–1). Other times, you want to just generate a number that is between a minimum and maximum value (min and max). If you need this, go ahead and use the following macro. Listing 22.8 #define RANDRANGE(min,max) (random((max)–(min)+1)+min)
This will give you a random number with a minimum value of min, a maximum value of max, provided that max is greater than min, so rolling a six-sided die is as easy as RANDRANGE(1,6).
Randomizer Seeds Sounds like I’m planting something, doesn’t it? The random numbers in games are important, and because of this, the way random numbers are generated is also important. Within the computer, stored somewhere in memory, is a number that is the “randomizer seed.” When you request a random number, the computer multiplies, divides, adds, subtracts, folds, spindles, and mutilates this number according to the algorithm it uses for generating random numbers, and spits out a value. This happens every time you use the random function. After this seed value has been mutilated, it is stored back in position as the new seed, and it waits for another random number request. So, starting with a given seed, the sequence of random numbers will be the same, assuming we are always asking for random numbers with the same range, e.g., random numbers between 1 and 6. After a very long period of time and many random number requests, the cycle will start over again. Most of the time, the numbers supplied by the random function are random enough to be usable. Other times, you might want to “stack the deck” by setting a particular value for the random seed, and other times you want to make sure that there is a good randomizer value so that the numbers you get are as random as a computer can make them. The function for setting the randomizer seed is called srand, and it is shown below. Listing 22.9 void srand(int seed);
This function returns no value and takes a single parameter, an int containing the desired seed. If you want to “stack the deck” so to speak, you can put a fixed value into this function, such as 100 or something, and you will always get the same sequence of random numbers. If you want your numbers to be as random as you can
Team LRN
278
Chapter 22: Odds and Ends
make them, you call srand with clock() as the seed parameter, making the clock the randomizer seed.
Score and score_t A common thread between Cybiko games is a high score list. In fact, some of these high scores are used in contests on Cybiko.com. In almost all cases, you have the same type of information stored as part of a high score table. The engineers that developed the SDK realized that score would be important, so they created a score record type (score_t) and a Score class to help manage a high score table.
score_t Fundamentally, you need to know only a few things about a score. You need to know who got the score, you need to know when this score was gotten, and you need to know what the score was in the first place. For the Cybiko, there is one additional piece of information stored with a score record, and that is the cyid of the Cybiko device on which the score was gotten. Here’s what score_t, the score record type, looks like: Listing 22.10 struct score_t { long score; cyid_t cyid; time_t time; char nickname[8]; };
The score member is the score itself. This is a long, so you can have scores as high as around two billion stored in a score_t. The cyid member is the ID of the Cybiko on which the score was gotten. The time member is a time_t, stating when the score was gotten. Finally, nickname is an array of eight characters storing the short name of the person who got the score. The score record is your atomic unit of score. You might only store one score with your game. You might store a top ten list. It is really up to you.
score.inf In order to operate in a standard way, you have to have a resource included in your application called Score.inf. This resource must not be compressed (i.e., you must use a minus sign in front of it in the Filer.list file). Also, it has to have enough room set aside to store all of the scores you are storing. Each score_t takes up 20 bytes (four for score, four for cyid, four for time, and eight for nickname, 4+4+4+8=20), so if you
Team LRN
Chapter 22: Odds and Ends
279
are storing only one score, put 20 bytes in the file. If you are storing 10 scores, set aside 200 bytes.
Score class So far this has been pretty simple stuff. The Score class is also rather simple. Its purpose is simply to open up the score resource (Score.inf), and read or write score_t records to it. To construct a Score object, you make use of its constructor, Score_ctor. To destroy a Score object, you use its destructor, Score_dtor. Both of these functions are shown below. Listing 22.11 struct Score* Score_ctor (struct Score *ptr_score, struct Module *ptr_module) void Score_dtor (struct Score *ptr_score, int memory_flag)
The constructor is like most others. The ptr_module parameter is a pointer to a module (in most cases, you put main_module.m_process–>module into this parameter). You can, in all honesty, retrieve and modify the scores from another application if you so desire. I’m not actually going to show you how to do that here, because I think if I did that Vadim (head of large application development at Cybiko, Inc.) would kill me. I’ll let you figure that stuff out on your own.
Retrieving Information about a Score Object There are two things that you will probably want to know about your Score object. One, is this a valid Score object? Two, how many records are stored within the Score object? When you create your Score.inf, you will probably just start it out as a number of space characters, which are completely meaningless as far as score_t records are concerned. Also, if for some reason, your Score.inf resource gets corrupted (for example, if you shut off your Cybiko as a Score object is writing to the Score.inf resource, it will also be invalid. Whenever you have an invalid Score object, you will want to reinitialize it with some default values. To check if a Score object is valid, you use Score_is_valid, shown below. Listing 22.12 bool Score_is_valid (struct Score *ptr_score)
This function takes a pointer to an Score object, and returns TRUE if the Score object is valid, and FALSE if it is invalid. For the second question, you likely already know how many records are in the Score.inf resource, since you are the one who made it. However, if you later decide to change the number of records stored within the Score.inf resource, you would have to change the code that reads in the high score list. If you make a habit of using a
Team LRN
280
Chapter 22: Odds and Ends
function for dealing with the number of records in the Score object, you will never have to rewrite this code. Listing 22.13 int Score_get_record_count (struct Score *ptr_score)
This function takes a pointer to a Score object and returns the number of records scored within it.
Reading and Writing to and from a Score Object Naturally, you will want to read and write the score records in a Score object. Otherwise, there is no point to having the score records in the first place, right? You can read in a score_t from your Score object with Score_read. You can write a score record to a Score object with either Score_write or Score_write_Ex. These three functions are shown below. Listing 22.14 bool bool bool
Score_read (struct Score *ptr_score, int index, struct score_t *ptr_result) Score_write (struct Score *ptr_score, int index, struct score_t *ptr_result) Score_write_Ex (struct Score *ptr_score, int index, long score, char *sz_nickname, cyid_t cyid, time_t time)
Each of these functions returns a bool. The returned value will be TRUE if the function successfully performed the operation, and FALSE if it did not. Score_read takes three parameters, a pointer to a Score object, an index into the score list, and a pointer to a score_t. The index parameter is the number of the record you wish to read in. The first record has an index of zero. If successful, this function will put the desired record into the score_t pointed to by ptr_result. Score_write works in the opposite manner as Score_read. It, too, takes a pointer to a Score object, an index, and a pointer to a score_t. If successful, the score_t pointed to by ptr_result will be stored in the Score object at the desired index. Score_write_Ex is for when you want to store a score record, but you do not wish to first fill out a score_t struct. With Score_write_Ex, you can specify the score, nickname, cyid, and time for the score record, and if successful, a score record will be written to the Score object with these values. This function does the compacting into a score_t for you based on the parameters you pass.
String Manipulation When making your games and applications, much of the information will be conveyed to the user by text, which means you will be using strings quite often. Those of you who are familiar with ANSI C are used to the many string manipulations found in the header file string.h. Unfortunately, there is no string.h for the Cybiko (you can always make one, of course, but that means implementing all of the standard functions).
Team LRN
Chapter 22: Odds and Ends
281
Cybiko does supply us with a number of string manipulation functions.
Copying Strings One of the most common things you will want to do with strings is copy them. You do this by using either strcpy (which copies an entire string) or strncpy (which copies a fixed length portion of a string). These are shown below. Listing 22.15 char* strcpy (char *sz_destination, char *sz_source) int strncpy (char *sz_destination, char *sz_source, size_t count)
The strcpy function copies the entire string pointed to by sz_source into the string pointed to by sz_destination. The return value of this function is the destination string (sz_destination). Keep in mind that no error checking is done with this copy, and if sz_destination is not allocated to be long enough to store the string pointed to by sz_source, the results are unpredictable. The strncpy function does essentially the same thing as strcpy, but you supply the maximum number of characters that you want copied from sz_source to sz_destination in the count parameter. No more than (count–1) characters will be copied, in order to leave room for the null terminator. The return value of this function is the actual number of characters copied from sz_source to sz_destination. The strcpy function is most commonly used to initialize strings, as shown below. Listing 22.16 char* AString; strcpy(AString,"Now is the time for...”);
Measuring Strings Another very common task with strings is to determine their length. You do this by using the strlen function, shown below. Listing 22.17 size_t
strlen (char *sz_source)
To make use of strlen, simply pass in a pointer to a string that you wish to find the length of. This function will return the length of the string, not counting the null terminator. The strlen function is quite possibly the most frequently used string manipulation function ever written.
Concatenation The word “concatenation” means to tack something on the end of something else. For strings, this is used to “add” strings together into a single, larger string. The function for doing this is called strcat.
Team LRN
282
Chapter 22: Odds and Ends
Listing 22.18 char*
strcat (char *sz_destination, char *sz_source)
This function adds the contents of sz_source to the end of sz_destination, and returns a pointer to sz_destination, which then contains the new, concatenated string. A quick example: Listing 22.19 char str1[20]; char str2[10]; strcpy(str1,"This is "); strcpy(str2,"a string."); strcat(str1,str2); //str1 now points to "This is a string."
Comparison Another common use for strings is to compare their values. There are two functions for doing this: strcmp and strncmp. These are shown below. Listing 22.20 int int
strcmp (char *sz_string_1, char *sz_string_2) strncmp (char *string_1, char *string_2, size_t count)
These should be reminiscent somewhat of strcpy and strncpy. The strcmp function compares two entire strings, and strncmp compares only a certain numbers of characters in the string. In all other ways, these functions are identical. The return value is one of three values: –1, 0, or 1. If the string pointed to by sz_string_1 is “less than” sz_string_2, it is –1. If they are “equal,” it returns 0. If sz_string_1 is “greater than” sz_string_2, the return value is 1. Notice the quotes around “less than,” “equal,” and “greater than.” These comparisons have meaning in relation to numbers, and their equivalent meaning as far as strings are concerned is not as well established. This brings up the topic of lexical order. Lexical order is sort of like alphabetical order, but only very slightly. To compare strings, you start with the first character in each sequence. Let us call these strings str1 and str2. If the first character in str1 has a value less than the first character in str2, then we call str1 “less than” str2, and return a –1. If the first character in str1 is greater in value than the first character in str2, we call str1 “greater than” str2. If both characters are equal in value, then we have to move on to the next character in each string, and do further comparison. If we run out of characters in one of the strings, but still have characters in the other string, and up until this point the strings have been “equal,” then the shorter string will be “less than” the longer string. If and only if the characters are exactly the same length and contain exactly the same characters throughout are the strings considered to be “equal.”
Team LRN
Chapter 22: Odds and Ends
283
Of course, this is if we are talking about strcmp. If instead we use strncmp, only the first count characters matter as far as this comparison goes. Strings that are not actually “equal” might be “equal” if using strncmp and a low enough value in the count parameter. A word about upper- and lowercase. The strcmp and strncmp don’t care one lick about letters in the alphabet, only numerical values. So, “A” might be less than “B” which is less than “C,” but all three of these are less than “a.” This is just something to keep in mind.
Searching for Characters and Strings We’re starting to get into the less often used string manipulation functions. Another task you might find yourself needing is to find a certain character, or a certain substring, within a string. Maybe you want to find the first “S” in a string, or the last “S” in a string, or you may want to see if “qu” exists somewhere in the string. Generally, you’ll only use these functions if you are making some sort of application that relies heavily on text editing, but it doesn’t hurt to know them anyway. Listing 22.21 char* char* char*
strchr (char *sz_source, char character) strrchr (char *sz_source, int character) strstr (char *sz_str_1, char *sz_str_2)
strchr (two r’s total) and strrchr (three r’s total) function similarly. They both look for the occurrence of a character (specified by the character parameter). The difference is in where they start looking for it. The strchr function starts at the beginning, and strrchr starts at the end. The return value is a pointer to where that character exists within the string, or NULL if the character does not exist in the string. If you want to see if a substring is within a string, you use strstr. The first parameter (sz_str_1) is the string you are scanning, and the second parameter (sz_str_2) is the string you are looking for. If the function finds a match, it will return a pointer to that match. If no match is found, then it will return NULL.
Alpha and Omega Now we are getting specialized. Occasionally (rarely, actually), you might want to know whether or not a string starts or ends with a particular character sequence. There are two Cybiko-specific functions for doing this: strstarts and strends. Listing 22.22 bool bool
strstarts (char *sz_source, char *sz_start_string) strends (char *sz_source, char *sz_tail_string)
The use of these functions should be pretty obvious. The strstarts function checks to see if sz_source starts out with the character sequence pointed to by sz_start_string. The strends function checks to see if sz_source ends with the character sequence
Team LRN
284
Chapter 22: Odds and Ends
pointed to by sz_tail_string. If they match, the function returns TRUE. If they don’t match, the function returns FALSE. Truth be told, you can use strstarts almost interchangeably with strncpy. They do pretty much the same thing.
The Match Game If you find yourself doing searches with wildcards (i.e., * and ?), then the next two functions are for you. Listing 22.23 bool bool
strmatch (char *sz_pattern, char *sz_source) is_pattern (char *sz_source)
The strmatch function checks a pattern string (pointed to by sz_pattern) against a source string (pointed to by sz_source). If the source string fits the pattern, it returns TRUE. Otherwise, it returns FALSE. The is_pattern function checks to see if a string is a pattern or not. Essentially, this just checks the string for a * or a ?. If either character is found, this function returns TRUE. Otherwise, it returns FALSE. Patterns are used commonly for looking at files, such as using “*.txt” to search for all files with a .txt extension. These functions are used internally by the FileFind class to read a list of filenames. They do have other uses as well, mostly in applying filters to lists of strings.
White Space And now for something completely different: a function with two names. Well, not really. They are still two functions. They simply do exactly the same thing. What they do is remove white space from strings. White space is a space, a newline character, or a tab character. These functions remove all of these things from a string, and return a pointer to the newly de-whitespaced string. Listing 22.24 char* char*
skipws (char *sz_source) trunc_spaces (char *sz_text)
Got pesky white space? Use skipws! Eradicate infernal tab characters! Eliminate spaces! Send newline characters to heck in a handbasket! And... it eliminates odors!
Time Keeping track of time can be very important in an application or game. You might want to supply only five minutes to solve a puzzle, or you might want to measure how much time a level took, or any number of other things that depend on the passage of time.
Team LRN
Chapter 22: Odds and Ends
285
Time itself, as far as the Cybiko is concerned, exists only as a number, a long integer in fact. There are two typedefs for measuring time. These are clock_t and time_t. Both of these are longs. While both of these types measure the same thing, they are intended to be used in slightly different ways.
clock_t A clock_t is just a typedef for a long. It is used to measure the time since the system (the Cybiko) was last started. The function for determining how many milliseconds have elapsed since system start is called clock, as shown here. Listing 22.25 clock_t clock();
This function takes no parameters and returns a clock_t whose value is the number of milleseconds since the system was restarted. Normally, you won’t care how many milleseconds your Cybiko has been active. That’s okay—nobody does. That isn’t the primary use of the clock function. The clock function is most commonly used for measuring increments of time that pass. For example, let’s say you want to know how long it takes to save a file. Before opening the file, you call the clock function, and save the value in some variable somewhere. You can then proceed to save the file. After you have finished, you get the value of clock again and store it somewhere else. Now you can take the second value, subtract the first value, and you know how long it took to save the file. In this way, you can measure how long just about anything took to do.
time_t time_t is the same as clock_t, with one exception. While clock_t is the number of milleseconds since the system was started, time_t measures the number of milleseconds since, as the Cybiko documentation puts it, “an arbitrary point in time.” I’m not sure what point of time they mean, but apparently it is an arbitrary one. To fetch the current value of this type of time, you call the time function. Listing 22.26 time_t time();
This function takes no values and returns a time_t. It is much like the clock function, other than the “arbitrary point in time” peculiarity. You can use the time function in a similar manner to the clock function for measuring periods of time by checking the difference between times.
Timer Resolution Just a quick note here. Both clock() and time() have a resolution of 10 milliseconds. By resolution I mean that it is no more accurate than 10 milliseconds. Ergo, when using
Team LRN
286
Chapter 22: Odds and Ends
clock() or time(), you might be off by 10 milliseconds, which may not be much in the real world, but it can be in a computer game.
Trusted Time Since two types of time just aren’t enough, there is a third function we can use to give us the time. The function is called get_trusted_time. Listing 22.27 time_t get_trusted_time();
The “trusted” time is set by Cybiko’s web servers whenever you use CyberLoad. You cannot change it. Like time() or clock(), you can use get_trusted_time to measure a given period of time. In addition, get_trusted_time tells you what time it “really is,” if there is such a thing.
The Time Struct If clock_t and time_t aren’t enough for you, there is yet another way to represent time. There is a Time struct, which looks as follows. Listing 22.28 struct Time{ char year; char month; char day; char hour; char minute; char second; char hundreds; };
Each of the members of the Time struct should be fairly obvious, with the possible exception of year. Year is the number of years since 1900. The year 2001 would be 101. The rest of the members have ranges appropriate to the increment of time.
Encoding and Decoding You cannot read the value of a Time struct directly. You first have to use time() or get_trusted_time, and then decode it. For this you use the Time_decode function. Listing 22.29 void
Time_decode (struct Time *ptr_time, time_t time);
Two parameters. The first is a pointer to a Time struct, and the second is a time_t containing the time you wish to decode. After this function is called, the Time struct pointed to by ptr_time will have the time information corresponding to the time value stored in the time parameter.
Team LRN
Chapter 22: Odds and Ends
287
On the flip side, you can go backward and take the information in a Time struct and encode it in a time_t variable. This is useful if you want to manipulate the time stored as a file’s “last modification” time. Listing 22.30 time_t
Time_encode (struct Time *ptr_time);
This function takes one parameter, a pointer to a Time struct. It returns a time_t with the encode value corresponding to the values stored in the Time struct pointed to by ptr_time. One extra piece of information that is not stored in a Time struct is the day of the week. You can obtain the day of the week by applying the Time_get_weekday function. Listing 22.31 int
Time_get_weekday (struct Time *ptr_time);
This function takes a pointer to a Time struct, and returns an integer. This value will be a value from 0 to 6 inclusively: 0 represents Sunday, 1 represents Monday, 2 represents Tuesday, and so on, with 6 representing Saturday.
The Real-Time Clock So far, we’ve been over clock_t, time_t, the Time struct, trusted time, time since an arbitrary date, and time since system start. Now we are going to add one more: the real-time clock. At this point, you might be wondering if “real” time even exists, since there are so many ways to represent time on the Cybiko. The real-time clock is the time that shows up on the desktop of your Cybiko. You can modify it programmatically. Listing 22.32 bool bool
Time_set_RTC (struct Time *ptr_time); Time_get_RTC (struct Time *ptr_time);
As you might expect, you use Time_get_RTC to retrieve the value of the real-time clock in a Time struct, and you use Time_set_RTC to set the time of the RTC to the values in a Time struct. Very likely, you will want to use Time_get_RTC a lot more often than you will need to use Time_set_RTC, and you will probably not even use Time_get_RTC that often unless you really need to know what time it is for some reason.
Alarm On, Alarm Off We’ve got two more functions, and then we are done with time. Both of these functions govern the alarm. We cannot programmatically set when the alarm should go off. We can, however, turn the alarm off and on. Listing 22.33 bool Time_enable_alarm_int (bool enable) int Time_clear_alarm_flag (void)
Team LRN
288
Chapter 22: Odds and Ends
The Time_enable_alarm_int function takes a bool parameter, specifying whether or not we want the alarm on (TRUE) or off (FALSE). This function likewise returns a bool containing the previous state of the alarm before this function was called. The Time_clear_alarm_flag is like calling Time_enabled_alarm_int with FALSE passed as the parameter. The return values are similar, except that it will return –1 if the alarm flag could not be read. You might be inclined to turn the alarm off when your program starts and return its previous state when your program exists, thus guaranteeing that your program will not be interrupted by the alarm. I wouldn’t suggest this. For one thing, there is no guarantee that the user will exit the program before shutting off his Cybiko. He might just turn it off instead, so your program never has a chance to turn the alarm back on. Then, the alarm doesn’t go off, the owner of the Cybiko is late for a job interview, he doesn’t get the job, and winds up homeless for the rest of his life. Well, maybe that’s putting a little too much importance on the Cybiko alarm. My point is: don’t mess with the alarm. You don’t need to.
Team LRN
Chapter 23
Libs and Classes Overview Well, we are at the end of the book (other than the appendices), and I’ve shown you just about everything the Cybiko platform has to offer to developers. It’s a limited platform, graphics-wise and sound-wise, but it does have potential. Also, it’s a great platform to ease yourself into the growing world of handheld programming. Before I go, I wanted to share with you some libraries and classes that I have developed over the last few months. They’ve helped me out a great deal, especially recently. The rest of this chapter is divided into three main sections, “Utility Libraries,” “Utility Classes,” and “Games.” “Utility Libraries” contains function libraries. “Utility Classes” contains Cybiko classes for doing various things that the SDK currently does not have the ability to do. Finally, “Games” has a few games that I have written for the Cybiko, with source code, so you can see how I did them. Some of the items in “Games” are not games at all, but rather applications that are level editors for games.
Utility Libraries Several months ago, I found myself repeatedly writing some of the same code to do the same tasks, again and again, in every application that I wrote. After about a week, I grew tired of this and decided to start writing some function libraries. Some of these have been surprisingly useful.
DynAlloc.h This lib is perhaps the most useful of all, and it doesn’t even have any functions in it! It has only two #defines, NEW and NEWARRAY. If you’re a C++ programmer, it’ll be pretty obvious what they are for. For you C guys, here they are:
289
Team LRN
290
Chapter 23: Libs and Classes
Listing 23.1 #define NEW(x) (x*)malloc(sizeof(x)) #define NEWARRAY(x,y) (x*)malloc(sizeof(x)*(y))
These are perhaps the most often used #defines in my libs. Take, for example, a case where you need to dynamically allocate a BitmapSequence object (a common task for me). Here’s the Cybiko way: Listing 23.2 struct BitmapSequence ptr_bseq; ptr_bseq=(struct BitmapSequence*)malloc(sizeof(struct BitmapSequence));
I don’t know about you, but I certainly get tired of typing “struct BitmapSequence” more than once on a line. So, make use of DynAlloc.h. The following is the equivalent of Listing 23.2: Listing 23.3 struct BitmapSequence ptr_bseq; ptr_bseq=NEW(struct BitmapSequence);
I feel that this is much better. The NEWARRAY macro works about the same way. You just add how many of the items you want to allocate an array for.
coord.h/c This next lib is coord.h and coord.c. They deal with the new type I added called coord_t, shown here: Listing 23.4 typedef struct _coord_t { int x; int y; } coord_t;
Pretty much, coord_t is the Cybiko version of the Windows POINT struct. There are other structs for the Cybiko that do much the same job as coord_t, but I wanted my own to work with. Here’s a list of functions that work with coord_t: Listing 23.5 coord_t coord_set(int x,int y); coord_t coord_add(coord_t dst,coord_t src); coord_t coord_sub(coord_t dst,coord_t src); coord_t coord_scale(coord_t dst,int mult,int div); coord_t coord_clip(coord_t dst,int x,int y,int w,int h,bool wrapx,bool wrapy); coord_t coord_clip_Ex(coord_t dst,struct rect_t* cliprect,bool wrapx,bool wrapy); int coord_x(coord_t src); int coord_y(coord_t src);
Team LRN
Chapter 23: Libs and Classes
291
While coord_t is a new type and thus can rightly be called an object, I feel it’s more appropriate to lump it here in the libs section, since you don’t “construct” or “destruct” coord_t objects. The functions in Listing 23.5 range from setting the coord_t to adding and subtracting the coord_t’s, retrieving the x or y values, and even clipping. Their use should be fairly obvious. I patterned most of these functions after existing functions in the SDK (namely, the rect_t functions).
IO_Ext.h/c This next lib is the IO_Ext.h and .c lib. These are a number of functions that extend the use of the Input, Output, FileInput, and FileOutput functions. This lib adds five functions. Listing 23.6 //input class extensions char* Input_load_string(struct Input* ptr_input); char Input_preview_byte(struct Input* ptr_input); char* Input_read_line(struct Input* ptr_input); //output class extensions long Output_store_string(struct Output* ptr_output,char* str); long Output_write_string(struct Output* ptr_output,char* str);
There are FileInput versions of the Input functions and FileOutput versions of the Output functions as well. These functions came about because of the limited way in which the Input and Output objects would deal with strings. It was nonexistent. So, I built functions that load and store strings (they do this by writing the null terminator into the file itself). Also, I built functions for reading lines and writing strings (without the null terminator). Finally, the most useful function in the bunch is Input_preview_byte. It allows you to see the next character in the file, without moving forward in the file.
string_ext.h/c The string_ext.h and string_ext.c functions implement most of the ANSI C string functions, and a number of others as well. I don’t know how I got along before I put these together. I seem to use them a lot. I’m going to let this heavily commented file speak for itself. Listing 23.7 /********************************************************** *'to' functions, from ctype.h * *Not Implemented: toascii * *Note: all of these functions, in their ctype.h *versions took an int parameter, and returned an int.
Team LRN
292
Chapter 23: Libs and Classes
*I have replace the parameter type with char, and the *return type with char. **********************************************************/ /////////////////////////////////////////////////////////// //toupper—converts lowercase to uppercase /////////////////////////////////////////////////////////// char toupper(char c); /////////////////////////////////////////////////////////// //tolower—converts uppercase to lowercase /////////////////////////////////////////////////////////// char tolower(char c);
/********************************************************** *'is' functions, from ctype.h * *Note: all of these functions, in their ctype.h *versions took an int parameter, and returned an int. *I have replaced the parameter type with char, and the *return type with bool. **********************************************************/ /////////////////////////////////////////////////////////// //isupper—TRUE if character c is an uppercase letter /////////////////////////////////////////////////////////// bool isupper(char c); /////////////////////////////////////////////////////////// //islower—TRUE if character c is a lowercase letter /////////////////////////////////////////////////////////// bool islower(char c); /////////////////////////////////////////////////////////// //isalpha—TRUE if character c is a letter /////////////////////////////////////////////////////////// bool isalpha(char c); /////////////////////////////////////////////////////////// //isdigit—TRUE if character c is a digit (0-9) /////////////////////////////////////////////////////////// bool isdigit(char c); /////////////////////////////////////////////////////////// //isalnum—TRUE if character c is alphanumeric /////////////////////////////////////////////////////////// bool isalnum(char c); ///////////////////////////////////////////////////////////
Team LRN
Chapter 23: Libs and Classes
//iscntrl—TRUE if character c is a control character /////////////////////////////////////////////////////////// bool iscntrl(char c); /////////////////////////////////////////////////////////// //isspace—TRUE if character c is a space /////////////////////////////////////////////////////////// bool isspace(char c); /////////////////////////////////////////////////////////// //isgraph—TRUE if character c is printable character other // than a space /////////////////////////////////////////////////////////// bool isgraph(char c); /////////////////////////////////////////////////////////// //isprint—TRUE if character c is a printable character /////////////////////////////////////////////////////////// bool isprint(char c); /////////////////////////////////////////////////////////// //ispunct—TRUE if character c is a punctuation mark /////////////////////////////////////////////////////////// bool ispunct(char c); /////////////////////////////////////////////////////////// //isxdigit—TRUE if character c is a hexadecimal digit /////////////////////////////////////////////////////////// bool isxdigit(char c); /////////////////////////////////////////////////////////// //isascii—TRUE if character c is an ascii character /////////////////////////////////////////////////////////// bool isascii(char c); /////////////////////////////////////////////////////////// //iscsym—TRUE if character c is a digit, letter, // or underscore /////////////////////////////////////////////////////////// bool iscsym(char c); /********************************************************** *'str' functions, from string.h * *Note: Some are missing from this list, usually because *they have already been implemented in the Cybiko SDK. **********************************************************/ /////////////////////////////////////////////////////////// //strcspn—finds the instance of a character in a set
Team LRN
293
294
Chapter 23: Libs and Classes
/////////////////////////////////////////////////////////// size_t strcspn( const char *string, const char *strCharSet); /////////////////////////////////////////////////////////// //strdup—duplicates a string /////////////////////////////////////////////////////////// char *strdup( const char *strSource); /////////////////////////////////////////////////////////// //strlwr—converts string to lowercase /////////////////////////////////////////////////////////// char *strlwr( char *string); /////////////////////////////////////////////////////////// //strncat—concatenates a portion of a string onto another /////////////////////////////////////////////////////////// char *strncat( char *strDest, const char *strSource, size_t count); /////////////////////////////////////////////////////////// //strncpy—copies part of a string /////////////////////////////////////////////////////////// char *strncpy( char *strDest, const char *strSource, size_t count); /////////////////////////////////////////////////////////// //strnicmp—case-insensitive comparison /////////////////////////////////////////////////////////// int strnicmp( const char *string1, const char *string2, size_t count); /////////////////////////////////////////////////////////// //strnset—initializes characters of a string to a // particular format /////////////////////////////////////////////////////////// char *strnset( char *string, int c, size_t count); /////////////////////////////////////////////////////////// //strninc—increments a string pointer /////////////////////////////////////////////////////////// char *strninc( const char *string, size_t count); /////////////////////////////////////////////////////////// //strpbrk—scans string for character in a character set /////////////////////////////////////////////////////////// char *strpbrk( const char *string, const char *strCharSet); /////////////////////////////////////////////////////////// //strrev—reverses a string /////////////////////////////////////////////////////////// char *strrev( char *string); ///////////////////////////////////////////////////////////
Team LRN
Chapter 23: Libs and Classes
295
//strset—sets characters of a string to a character /////////////////////////////////////////////////////////// char *strset( char *string, int c); /////////////////////////////////////////////////////////// //strspn—finds a substring from a character set /////////////////////////////////////////////////////////// size_t strspn( const char *string, const char *strCharSet); /////////////////////////////////////////////////////////// //strupr—converts a strng to uppercase /////////////////////////////////////////////////////////// char *strupr( char *string); /////////////////////////////////////////////////////////// //strtok—tokenizes a string /////////////////////////////////////////////////////////// char *strtok( char *strToken, const char *strDelimit); /////////////////////////////////////////////////////////// //strtol—converts a string to a long /////////////////////////////////////////////////////////// long strtol(char *string); /////////////////////////////////////////////////////////// //strisnum—TRUE if a string contains a numeric value /////////////////////////////////////////////////////////// bool strisnum(char *string);
Need I say more? I didn’t think so.
Dialogs and Forms This is actually a collection of libs, contained in StdDlg.h/c, MenuForm.h/c, and FileListForm.h/c. These were the very first libs I ever wrote for the Cybiko. They help to minimize the amount of UI code necessary for most simple needs.
StdDlg.h/c This lib has a total of two functions (making it the largest of the three). The functions are MessageBox and InputBox, shown below: Listing 23.8 //message box function(returns a modal result (mrXXXX constant) int MessageBox( char *title, //(in)title of the message box char *message, //(in)message for this message box long style, //(in)style (combination of mbXXXX constants) struct cWinApp *ptr_win_app //(in)pointer to application(main_module.m_process) );
Team LRN
296
Chapter 23: Libs and Classes
//input box function(returns a modal result (mrXXXX constant) int InputBox( char* title, //(in)title of the input box char* message, //(in)message for this input box long style, //(in)style (combination of mbXXXX constants)— //NOTE: mbEdit does NOT need to be specified int edit_size, //(in)max length of the string for input box char* edit_text, //(in/out)input box text struct cWinApp *ptr_win_app //(in)pointer to application(main_module.m_process) );
These functions are similar to those that we built during the discussion of the UI system. A call to MessageBox will give you a simple message box, with a title, a message, and some buttons. A call to InputBox allows you to retrieve textual data from the user. I use this function for practically every text input I make. (I try to avoid text input as a rule, and when absolutely necessary, I use this function.)
MenuForm.h/c MenuForm.h/c contain the code for the SimpleMenuForm function, shown below. Listing 23.9 //SimpleMenuForm function(returns item selected, or –1 if cancelled using <Esc> int SimpleMenuForm( char* title, //(in)title of the menu char** ItemList, //(in)list of items (array of char*, with last item being “”) int x, //(in) x coordinate of upperleft int y, //(in) y coordinate of upperleft int width, //(in) width of form int height, //(in) height of form bool round, //(in) TRUE=rounded, FALSE=rectangular struct cWinApp* ptr_win_app //(in)application pointer(main_module.m_process) );
We built a similar function to this one. This gives you a basic menu with a list of items, and allows the user to pick one. The key here is the ItemList parameter. It is a char** variable. This points to a list of strings. There is no parameter telling the function how many strings to use, and the way SimpleMenuForm is structured, you place an empty string at the end of the list of strings, like so: Listing 23.10 char* MainMenu[]= { "New", "Open...", "Save", "Save As...",
Team LRN
Chapter 23: Libs and Classes
297
"Exit", "" //this line specifies the end of the list. };
Then you can simply call SimpleMenuForm with MainMenu as the ItemList parameter. This should do for all of your basic menu needs. The return value is 0 or higher if a menu item was selected, –1 if cancelled by pressing Esc, and –2 if a quit message was received.
FileListForm.h/c This function was originally developed for AppHack. I figured a standard menu form for loading in files would be a good idea. The FileListForm function is the result. Listing 23.11 //FileListForm function(returns item selected, or –1 if cancelled using <Esc> int FileListForm( char* title, //(in)title of the form char* filter, //(in)filter for file list char* filename, //(out)filename selected struct cWinApp* ptr_win_app //(in)application pointer(main_module.m_process) );
This function is really useful. You can specify a title and a filter as “*.lvl” to only list those files that match the mask (rather than having to sort through all of the files on the Cybiko). The result is placed in the buffer pointed to by filename. The return value is much the same as SimpleMenuForm.
Utility Classes My utility classes consist of two primary groups and a third group of miscellaneous classes. The first group is for images and animation sequences. The second group is for linked lists and various types of nodes for such lists. The third group consists of a parser class (it depends on a linked list of keywords), a cursor class, handy for handling ranges of x and y coordinates, and a view class, which is sort of like a Graphics+ type of class.
Image, Animation Sequence, and Animation Sequence Set If you work any amount of time with Bitmap and BitmapSequence objects, and you are dealing with transparency and flipping, you’ll notice after a while how much of a pain it is. You are constantly switching draw modes and background colors. I got tired of it, so I wrote an Image class. An Image object contains a Bitmap object, a draw mode, a background color, and a flips member (for the various horizontal and vertical flips you can do with bitmaps). I set all of this up beforehand, when I’m loading in the bitmap.
Team LRN
298
Chapter 23: Libs and Classes
Afterwards I simply call Gfx_draw_image, and all of the draw mode flipping, background color changes, and flipping is done for me. It makes life a lot easier graphic-wise. The Image class is wonderful, but I decided to take it a step further. Individual Image objects can become a part of an animation sequence, and this is represented by the AniSeq class. An AniSeq object holds a number of pointers to Image objects, and you can draw any of the images in that sequence just by calling Gfx_draw_from_aniseq. Using an AniSeq object, you can have a single object represent all of the frames of animation for, let’s say, a character walking to the north. This keeps all of the images in one spot, and you can draw from the same thing, just using the number of the frame you are worried about. Finally, the animation sequence set, or AniSeqSet class, is a group of AniSeq objects. With this, you can use one AniSeqSet for all of the animation needed for a character, perhaps with your character moving north, south, east, and west, and you need only specify which sequence to use and which image from that sequence in a call to Gfx_draw_from_aniseqset.
Image The Image class contains a pointer to a Bitmap object, a drawmode_t variable, a color_t variable (for background/transparent color), and an int for flipping flags. Here’s what it looks like: Listing 23.12 //_Image structure struct _Image { struct Bitmap* drawmode_t color_t int };
m_bmp; m_dm; m_col; m_flips;
//bitmap pointer //draw mode(DM_*) //background color(CLR_*) //flips(BM_*)
This has everything you need in order to customize how a bitmap should be drawn. Here are the functions for Image class. Listing 23.13 //constructors PIMAGE Image_ctor(PIMAGE this,struct Bitmap* pBitmap,drawmode_t dm,color_t col,int flips); PIMAGE Image_new(struct Bitmap* pBitmap,drawmode_t dm,color_t col,int flips); PIMAGE Image_ctor_Ex(PIMAGE this,char* filename,drawmode_t dm,color_t col,int flips); PIMAGE Image_new_Ex(char* filename,drawmode_t dm,color_t col,int flips); //destructors void Image_dtor(PIMAGE this,int mem_flag); void Image_del(PIMAGE this); //setters void Image_set_bitmap(PIMAGE this,struct Bitmap* pBitmap); void Image_set_draw_mode(PIMAGE this,drawmode_t dm);
Team LRN
Chapter 23: Libs and Classes
299
void Image_set_bkcolor(PIMAGE this,color_t col); void Image_set_flips(PIMAGE this,int flips); //getters struct Bitmap* Image_get_bitmap(PIMAGE this); drawmode_t Image_get_draw_mode(PIMAGE this); color_t Image_get_bkcolor(PIMAGE this); int Image_get_flips(PIMAGE this); //graphics class draw image function void Gfx_draw_image(struct Graphics* this,PIMAGE pImage,int x,int y);
Everything is pretty simple here. The constructors can either construct an Image object from a Bitmap object or load one in from a file. You still have to specify draw mode, color, and flips in each of these. The new function will dynamically allocate the space for an Image object for you, and they are the suggested way of creating Image objects, especially if you are using AniSeq or AniSeqSet. There are a number of setter and getter functions. The most important thing you need to know about the Image class is that it makes a copy of any bitmap you send to it, and manages that copy on its own, so you don’t have to worry about keeping the bitmap in memory (and in fact, you shouldn’t keep it in memory at all). Finally, the Gfx_draw_image function is what you use to draw an Image object onto either a Graphics object or a DisplayGraphics object. You specify the destination, the source Image object, and a position in which to draw it.
AniSeq The AniSeq class manages a list of animation frames, which boils down to a list of Image objects. Here’s what the structure looks like. Listing 23.14 struct _AniSeq { int m_size; PIMAGE* m_images; };
This is pretty simple. The m_size member is how many Image pointers are in the list pointed to by m_images. Here are the functions for dealing with AniSeq objects. Listing 23.15 PANISEQ AniSeq_ctor(PANISEQ this,int size); PANISEQ AniSeq_new(int size); PANISEQ AniSeq_ctor_Ex(PANISEQ this,struct BitmapSequence* pBSeq,int start,int size,drawmode_t dm,color_t col,int flips); PANISEQ AniSeq_new_Ex(struct BitmapSequence* pBSeq,int start,int size,drawmode_t dm,color_t col,int flips); void AniSeq_dtor(PANISEQ this,int mem_flag); void AniSeq_del(PANISEQ this); int AniSeq_get_size(PANISEQ this);
Team LRN
300
Chapter 23: Libs and Classes
void AniSeq_set_image(PANISEQ this,int index,PIMAGE pimage); PIMAGE AniSeq_get_image(PANISEQ this,int index); void Gfx_draw_from_aniseq(struct Graphics* this,PANISEQ paniseq,int index,int x,int y);
There are two ways to create an AniSeq object. One, you can specify the size yourself and set the images manually through AniSeq_set_image. Two, you can specify a BitmapSequence pointer, an index at which to start, and a number of indexes, plus the details like draw mode, color, and flips, and it will fill out the images for you. This is the suggested method, but the manual way is sometimes useful as well. The Gfx_draw_from_aniseq function is the way to draw from an AniSeq object. It can be used on a Graphics object or a DisplayGraphics object. You specify the destination, the AniSeq source object, the index into the image list, and an x,y position. This function, in turn, calls the Gfx_draw_image function for that image.
AniSeqSet This class is like AniSeq, with the only difference being that AniSeqSet contains a list of AniSeqs rather than a list of images. Here’s the structure. Listing 23.16 struct _AniSeqSet { int m_size; PANISEQ* m_aniseqs; };
And here are the functions. Listing 23.17 PANISEQSET AniSeqSet_ctor(PANISEQSET this,int size); PANISEQSET AniSeqSet_new(int size); PANISEQSET AniSeqSet_ctor_Ex(PANISEQSET this,struct BitmapSequence* pBSeq,int start,int setsize,int seqsize,drawmode_t dm,color_t col,int flips); PANISEQSET AniSeqSet_new_Ex(struct BitmapSequence* pBSeq,int start,int setsize,int seqsize,drawmode_t dm,color_t col,int flips); void AniSeqSet_dtor(PANISEQSET this,int mem_flag); void AniSeqSet_del(PANISEQSET this); int AniSeqSet_get_size(PANISEQSET this); PANISEQ AniSeqSet_get_aniseq(PANISEQSET this,int index); void AniSeqSet_set_aniseq(PANISEQSET this,int index,PANISEQ paniseq); void Gfx_draw_from_aniseqset(struct Graphics* this,PANISEQSET paniseqset,int seqindex, int imgindex,int x,int y);
Like AniSeq, you can set up an AniSeqSet manually or with a BitmapSequence (the BitmapSequence being the suggested route). You draw from it using the Gfx_draw_ from_aniseqset function. It filters down to Gfx_draw_from_aniseq and finally to Gfx_draw_image.
Team LRN
Chapter 23: Libs and Classes
301
Linked Lists and Nodes For those who don’t know, and without getting into a big discussion of the theory behind them, linked lists are a dynamic container for data. You can store many items, or just a few, and adding and deleting items is relatively simple. The only problem is that searching for items can take some time, especially if the list is big. This part of the utility classes is divided into two sections: nodes and lists. Nodes are the basic unit of storage, and lists are the containers themselves.
Nodes I have supplied four types of nodes: the basic Node class, which stores nothing and serves as a “base class” for other types of nodes, and string nodes, long nodes (stores a long), and table nodes (which stores a string and a long). Node The Node class is your basic node. It is completely useless other than as a base class for other node types. Here’s the structure: Listing 23.18 struct _Node { struct _Node* prev; struct _Node* next; };
As you can see, this is pretty light. It simply contains the stuff necessary to make linked lists work, i.e., a pointer to the next node and a pointer to the previous node. The functions are pretty self-explanatory. Here they are: Listing 23.19 PNODE Node_ctor(PNODE this,PNODE prev,PNODE next); PNODE Node_new(PNODE prev,PNODE next); void Node_dtor(PNODE this,int mem_flag); void Node_del(PNODE this); PNODE Node_get_next(PNODE this); PNODE Node_get_prev(PNODE this); void Node_set_next(PNODE this,PNODE next); void Node_set_prev(PNODE this,PNODE prev); void Node_pre_insert(PNODE this,PNODE node); void Node_post_insert(PNODE this,PNODE node);
Most of these functions deal with managing the two members of Node objects, the next and prev pointers. Other than that, there are two utility functions, Node_pre_ insert and Node_post_insert. These help node management considerably. You can use these functions to insert other nodes both before and after the current node.
Team LRN
302
Chapter 23: Libs and Classes
StringNode A StringNode takes your basic Node class, and adds a string, as shown below. Listing 23.20 struct _StringNode: public _Node { char* str; };
Most of the StringNode functions are simply #defines for the same function in the Node class, so I won’t list them all here. Instead, I will concentrate on those functions unique to StringNode. Listing 23.21 PSTRINGNODE StringNode_ctor(PSTRINGNODE this,char* str,PNODE prev,PNODE next); PSTRINGNODE StringNode_new(char* str,PNODE prev,PNODE next); void StringNode_dtor(PSTRINGNODE this,int mem_flag); void StringNode_del(PSTRINGNODE this); void StringNode_set_string(PSTRINGNODE this,char* str); char* StringNode_get_string(PSTRINGNODE this);
An important note: the string contained by StringNode is a copy of whatever you send to StringNode_set_string. LongNode In many ways, LongNode is a lot like StringNode, with the only difference being what is being stored. A LongNode stores a long int. Here’s the structure: Listing 23.22 struct _LongNode: public _Node { long m_value; };
Also like StringNode, LongNode has #defines for all of the various node functions. Here’s a list of functions unique to LongNode. Listing 23.23 PLONGNODE LongNode_ctor(PLONGNODE this,long val,PNODE prev,PNODE next); PLONGNODE LongNode_new(long val,PNODE prev,PNODE next); void LongNode_dtor(PLONGNODE this,int mem_flag); void LongNode_del(PLONGNODE this); void LongNode_set_value(PLONGNODE this,long val); long LongNode_get_value(PLONGNODE this);
Basically, the only functions added are LongNode_set_value and LongNode_get_value.
Team LRN
Chapter 23: Libs and Classes
303
TableNode The TableNode class is sort of a combined StringNode and LongNode. It contains both a string and a long. It derives from StringNode. Listing 23.24 struct _TableNode: public _StringNode { long id; };
Most of the TableNode functions are just #defines for the StringNode versions of the function. Here’s a list of TableNode-specific functions: Listing 23.25 PTABLENODE TableNode_ctor(PTABLENODE this,char* str,long id,PNODE prev,PNODE next); PTABLENODE TableNode_new(char* str,long id,PNODE prev,PNODE next); void TableNode_dtor(PTABLENODE this,int mem_flag); void TableNode_del(PTABLENODE this); void TableNode_set_id(PTABLENODE this,long id); long TableNode_get_id(PTABLENODE this);
Unlike LongNode, the long int from a TableNode is called an “id,” rather than “value.” This is mainly because of the common usage of TableNode, which is for dictionaries and look-up tables.
Containers The two container classes are List and Table. They are pretty much interchangeable as far as functionality goes. The Table class has some extra functions specifically for dealing with TableNodes, whereas the List class just has basic functionality for linked lists in general. List The List class is most correctly described as a collection of Node objects, although you can store StringNodes, LongNodes, or TableNodes in it (or another type of node, if you come up with a new one). Listing 23.26 struct _List { PNODE m_head; PNODE m_current; };
Surprisingly, there are only two members, both pointers to Node objects. The m_head member is a special node, which is not actually part of the list. The m_current member is used for iteration (walking though the list).
Team LRN
304
Chapter 23: Libs and Classes
Listing 23.27 shows the List functions. Listing 23.27 PLIST List_ctor(PLIST this); PLIST List_new(); void List_dtor(PLIST this,int mem_flag); void List_del(PLIST this); PNODE List_move_first(PLIST this); PNODE List_move_last(PLIST this); PNODE List_move_next(PLIST this); PNODE List_move_prev(PLIST this); PNODE List_get_current(PLIST this); bool List_at_end(PLIST this); bool List_at_beginning(PLIST this); bool List_empty(PLIST this); void List_prepend(PLIST this,PNODE node); void List_append(PLIST this,PNODE node); void List_clear(PLIST this); long List_get_size(PLIST this);
These functions should be relatively self-evident. Most of them deal with maneuvering through the list itself. Others, like List_prepend and List_append, insert nodes into the beginning of the collection or the end of the collection. Finally, List_clear and List_get_size are miscellaneous functions that operate on the list. A warning: The List class can be used with LongNodes, StringNodes, and TableNodes with few problems, except when using List_clear or List_dtor. If you are using StringNode or TableNode, use a Table. If you are using StringNode, just don’t make use of the search functions for the Table class. This ensures that the strings are cleaned up properly. Table The Table class is for lists of StringNodes and TableNodes. TableNodes adds capabilities for searching for particular items by string or by ID. Table derives from List, and so most of the Table functions are just #defines for the List versions. The structure for Table is the same as for List, so there is no need to show it. Here is a list of functions specific to the Table class. Listing 23.28 bool Table_has_name(PTABLE this,char* str); bool Table_has_id(PTABLE this,long id); PTABLENODE Table_find_id(PTABLE this,long id); PTABLENODE Table_find_name(PTABLE this,char* str); PTABLE Table_ctor_Ex(PTABLE this,struct Input* ptr_input); PTABLE Table_new_Ex(struct Input* ptr_input); PTABLE Table_load_res(char* resname); PTABLE Table_load_file(char* filename);
Team LRN
Chapter 23: Libs and Classes
void void void void
305
Table_store(PTABLE this,struct Output* ptr_output); Table_dtor(PTABLE this,int mem_flag); Table_del(PTABLE this); Table_clear(PTABLE this);
Most of these functions deal with checking for strings (names) and IDs within the table, and searching for nodes with particular names or IDs. There are also a number of functions for loading a table from a file or resource. This is handy because you won’t want to put Tables together manually in code. The source file or resource has to be text and consist of a number of lines. The first line tells how many items are in the list, and the lines from then on are NAME=ID, like so: Listing 23.29 5 THIS=12 IS=17 A=3 DICTIONARY=8 FILE=–5
This will get loaded in no problem.
Miscellaneous Classes The classes in this last group are completely unrelated to one another, although in the case of Parser, there is a relationship to the Table class. The three classes are Cursor, View, and Parser.
Cursor Often, you will have a need to store an (x,y) pair of coordinates, and these coordinates will have to be within a specified range. A good example of this would be a chess board, where both x and y have to be in the range from 0 to 7, or perhaps on a screen, where x must be between 0 and 159 and y must be from 0 to 99. The Cursor class helps to manage this. It keeps an (x,y) pair, and a width and a height of the field. The x value may range from 0 to width–1, and the y value may range from 0 to height–1. The Cursor class also does either clipping or wrapping. In clip mode, an attempt to move outside of the range will stop just short of leaving it, and with wrapping, you can go off one end and come back onto the opposite side. Here’s the structure for the Cursor class: Listing 23.30 typedef struct _Cursor { int m_x;
Team LRN
306
Chapter 23: Libs and Classes
int m_y; int m_w; int m_h; bool m_hwrap; bool m_vwrap; } Cursor;
The m_x and m_y members are for keeping track of the current (x,y) position. The m_w and m_h members are the width and height. m_hwrap and m_vwrap are the clip/wrap flags. If TRUE, then wrapping is in effect for that axis. If false, then clipping is in effect for that axis. Here is the list of functions for the Cursor class. Listing 23.31 PCURSOR Cursor_ctor(PCURSOR pCursor,int x,int y,int w,int h,bool hwrap,bool vwrap); PCURSOR Cursor_new(int x,int y,int w,int h,bool hwrap,bool vwrap); void Cursor_dtor(PCURSOR pCursor,int memflag); void Cursor_del(PCURSOR pCursor); void Cursor_set_x(PCURSOR pCursor,int x); void Cursor_set_y(PCURSOR pCursor,int y); void Cursor_set_w(PCURSOR pCursor,int w); void Cursor_set_h(PCURSOR pCursor,int h); void Cursor_set_hwrap(PCURSOR pCursor,bool hwrap); void Cursor_set_vwrap(PCURSOR pCursor,bool vwrap); int Cursor_get_x(PCURSOR pCursor); int Cursor_get_y(PCURSOR pCursor); int Cursor_get_w(PCURSOR pCursor); int Cursor_get_h(PCURSOR pCursor); bool Cursor_get_hwrap(PCURSOR pCursor); bool Cursor_get_vwrap(PCURSOR pCursor); void Cursor_move(PCURSOR pCursor,int x,int y); void Cursor_move_rel(PCURSOR pCursor,int dx,int dy);
Most of the functions deal with setting or retrieving one of the members of the Cursor class. Any member can be set at any time, making cursor reuse rather easy. The two most useful functions, though, are Cursor_move and Cursor_move_rel. These functions move a cursor either to an absolute location (via Cursor_move) or by a relative distance (through Cursor_move_rel). In both cases, the effects of clipping or wrapping are applied. This sort of thing can really cut down on your logic for moving around in a 2D world.
View If I had to describe the View class, I would call it “an Image that you can draw on.” View is the one class that actually derives from a pre-existing Cybiko class, namely the Graphics class. It also creates a Bitmap object onto which it draws. Here’s the structure:
Team LRN
Chapter 23: Libs and Classes
307
Listing 23.32 typedef struct _View: public Graphics { struct Graphics* m_target; int m_xdst; int m_ydst; drawmode_t m_targetdm; color_t m_targetbc; int m_flips; } View;
As you can see, there are some members similar to those of the Image class—drawmode, background color, and flips. In addition, there is an (x,y) coordinate where View draws itself. Finally, there is a destination member, onto which the View class will draw itself when you call View_show. Here’s a list of functions dealing with the View class. There are also #defines for each of the Graphics functions for use with the View class, not listed here. Listing 23.33 PVIEW View_ctor(PVIEW pView,struct Graphics* pTarget,int x,int y,int width,int height,drawmode_t dm,color_t col,int flips); PVIEW View_new(struct Graphics* pTarget,int x,int y,int width,int height,drawmode_t dm,color_t col,int flips); void View_dtor(PVIEW pView,int memflag); void View_del(PVIEW pView); void View_set_x(PVIEW pView,int x); void View_set_y(PVIEW pView,int y); void View_set_target(PVIEW pView,struct Graphics* pTarget); void View_set_target_draw_mode(PVIEW pView,drawmode_t dm); void View_set_target_bkcolor(PVIEW pView,color_t col); void View_set_flips(PVIEW pView,int flips); int View_get_x(PVIEW pView); int View_get_y(PVIEW pView); struct Graphics* View_get_target(PVIEW pView); drawmode_t View_get_target_draw_mode(PVIEW pView); color_t View_get_target_bkcolor(PVIEW pView); int View_get_flips(PVIEW pView); void View_show(PVIEW pView);
All of the additional capabilities of the View class deal with setting and retrieving the values of the members. Of particular importance is the View_show function, which will automatically draw the view’s bitmap onto the target. The View class is one of those classes that is a good idea from a design perspective, but fails to meet expectations in performance. In order to use the View class, you first have to draw to the view, and then draw from the view to the target, which means you are drawing the same pixels twice. This is useful for editors but not for more seriously demanding apps.
Team LRN
308
Chapter 23: Libs and Classes
Parser The Parser class is pretty neat. With it, you can convert a string of words into an array of numbers with the assistance of a Table object containing a dictionary. Listing 23.34 struct _Parser { PTABLE m_table; char* m_delimiters; long* m_argv; size_t m_argc; };
//[in]list of words //[in]delimiters //[out]array of tokens //[out]number of tokens
The m_table member is a pointer to a Table object that acts as a dictionary for the Parser. The m_delimiters member is a string containing “delimiters.” A delimiter is little more than a separator character, such as a space, comma, newline character, and so on. The m_argv and m_argc members work in tandem. m_argc is the number of items in the array, and m_argv is the array of those items. Listing 23.35 shows the functions for dealing with Parser objects. Listing 23.35 PPARSER Parser_ctor(PPARSER this,PTABLE table,char* delimiters); PPARSER Parser_new(PTABLE table,char* delimiters); void Parser_dtor(PPARSER this,int mem_flag); void Parser_del(PPARSER this); void Parser_set_table(PPARSER this,PTABLE table); void Parser_set_delimiters(PPARSER this,char* delimiters); PTABLE Parser_get_table(PPARSER this); char* Parser_get_delimiters(PPARSER this); long Parser_get_argv(PPARSER this,size_t index); size_t Parser_get_argc(PPARSER this); parseresult_t Parser_parse(PPARSER this,char* string);
Most of these are setters and getters and aren’t too complicated to figure out. The main appeal of the Parser class is the Parser_parse function. It takes a string, takes it apart, and looks up the terms in the dictionary; afterward, the m_argv and m_argc array are filled with the appropriate numbers found from the dictionary.
Games On the companion CD, in the third party folder, I have a number of games—some with full source, others without. Check them out. It wouldn’t be a game developer’s guide without games, right?
Team LRN
Chapter 23: Libs and Classes
309
Summary Well, that’s it from me. I hope you enjoyed this book. Be sure to stop by http://www.cybikodev.com, because you can never be sure what sort of crazy stuff I’ll do. Also, there is a good deal of lag time between when I finished writing this and when it arrived in your hands, so you will probably find a great deal of new information on the site.
Team LRN
Appendix A
The C Programming Language Overview This is an appendix for those of you who have never programmed before (or did so a long time ago and may need a refresher course in programming concepts). This small appendix cannot do the same job that a large book on the topic of C programming can, of course, so this will be more of a crash course in programming. I do suggest getting a good reference on the C language, or any language in which you are going to be programming. There are lots of them out there, and I own several. Even after 14 years of programming, I still need to refer to them now and again.
What is Programming? Programming is the art, science, and discipline of creating programs. A program is nothing more than a set of instructions that some sort of computing device uses to do something. This is a rather simplistic view of what a programmer is and what a programmer does, but it’s the best I could come up with off the top of my head.
How Do Computing Devices Work? In order to be a programmer, you must have at least a passing knowledge of how a computing device works. Don’t get me wrong, you can program without knowing anything about how computers do what they do, but this knowledge will make you a better programmer. A computing device (I say computing device rather than computer, since we normally use “computer” to refer to the desktop variety of computing device and things
310
Team LRN
Appendix A: The C Programming Language
311
like the Cybiko are referred to as handheld computing devices) has at its heart one or more central processing units (CPU). The CPU is the thing that makes stuff happen. In reality, it is nothing but a very small calculator made of silicon. It cannot do things on its own, of course. Someone has to tell it what to do. That someone is a programmer. In addition to a CPU, most computing devices have some sort of volatile storage area (referred to most commonly as memory). It is called “volatile” (the definition of volatile is “prone to evaporate”) because when the power is disconnected from the computing device, anything stored in this area is lost. Another place where information can be stored on a computing device is in a more permanent storage area. On a PC or other desktop computing device, this is usually a hard drive, a floppy disk, a CD-ROM. On the Cybiko, there is a special non-volatile memory that does this. Whatever is stored in non-volatile storage is not lost when power is disconnected from the computing device. Finally, a computing device also has peripheral devices—for example, a keyboard, the screen, a communication port or device, and so on. A peripheral (meaning “on the side”) device does specialized tasks, like displaying graphics, accepting input, communicating with other computing devices, and so on. Both volatile and non-volatile storage areas are in a way peripherals. When we speak of a Cybiko computing device, we really mean the CPU of that device, plus the volatile and non-volatile storage areas, plus all of the built-in peripherals, the clock, the keyboard, the LCD screen, the speaker, the vibrator, the radio frequency antenna, the serial port, the reset button or on/off switch, and whatever we may have attached through the expansion slot, like a memory card or an MP3 player. When we speak of a desktop computing device, we also consider anything that is attached to the computer to be part of the computer—the mouse, the keyboard, the monitor, the sound card, the CD-ROM drive, and so on. We do not usually consider a printer as being part of the computer itself, but that is an exception. So, back to how these things work. When power is connected to the device, some data from a non-volatile storage area is transferred into a volatile storage area. This data contains instructions for the CPU, telling it what to do when the device is activated. During this time, the various peripherals are checked to make sure they are not damaged or non-functional, and checks are made for optional peripherals, such as those placed in the expansion slot. Once the device determines that everything is okay, it loads the operating system from a non-volatile storage area into a volatile storage area, and it, too, is initialized. An operating system (OS) is a rather simple thing, really. Its only purpose is to allow human interaction with the computing device. In the case of Cybiko OS (CyOs), the main function of the operating system is to allow you to browse through the applications currently stored on the device and execute them. There are also some instructions running in the background for wireless communications and power supply monitoring.
Team LRN
312
Appendix A: The C Programming Language
It is entirely possible for us to write our own OS. However, most of us are happy with CyOS as it is, and would rather just write applications or games, games especially. The operating system remains in volatile storage while you are using the device, even when you execute an application or game. This is because certain tasks in your application are handled by the OS so you don’t have to. This makes your job of programming a little easier.
How Does a Program Work? A program does three things. It brings in data (input), it performs calculations on that data (processing), and it sends out data (output). The input can have several forms, including keyboard input, messages received by radio frequency, data from the serial port, a file, a resource, and so on. Similarly, output has many forms, including the screen, the speaker, radio frequency signals, vibration, a file, or a resource. The processing takes even more forms, and depends on the nature of the input and the nature of the output. The manner in which this inputting, processing, and outputting is done is by the CPU executing various instructions from a program. These instructions start out in non-volatile storage, are transferred into volatile storage, and then executed one by one by the CPU. These instructions are nothing more than numerical values that have meaning only to the CPU and the handful of engineers who created the CPU. They are not easily read by human beings. Programmers had to generate these numerical instructions by hand during the dark age of computer programming. In modern times, we make use of programming languages that make the task much easier. We write the instructions for a computer to follow in a more human-readable manner, and we make use of other programs (preprocessor, compiler, linker) to read in our code and translate it into the numerical instructions that the computing device understands. It’s kind of like an English to Spanish translator, but for computers. The language to use to make Cybiko programs is a variation of the C language. It does not use the standard C, also called ANSI C, although it is mostly ANSI C compliant. Cybiko C is the language of this book, and Cybiko C is what the remainder of this appendix will be covering. I will do my best to point out where Cybiko C differs from ANSI C.
How is Memory/Storage Structured? The numbers stored in volatile or non-volatile storage are not actual numbers. A computing device is electronic in nature, so it can only store two values, on or off. If we interpret on to be a 1 and off to be a 0, we can now store one of two numbers, 0 or 1.
Team LRN
Appendix A: The C Programming Language
313
This is called a bit. In and of itself, a single bit is not often useful, so we group bits together to give us greater range in storing numerical values. For example, two bits have four different values: 00, 01, 10, and 11. If we take the first digit and make it represent 2 when set and make the second digit represent 1 when set, these two bit values can give us the numbers 0, 1, 2, and 3. We’re making progress. Similarly, three bits will give us eight values, 0-7, four bits will give us 16 values, 0-15, five bits will give us 32 values, 0-31, and so on. The traditional grouping for bits is in groups of eight. We call this grouping a byte (why, I don’t know). A byte contains 256 distinct values, 0-255. This is not the only way to interpret a byte’s value, but I don’t want to get ahead of myself. The byte is the atomic size you can access on a computing device. If you wish to access an individual bit of a byte, you can do so, but you must first access the byte. That is, you cannot get any smaller amount of storage than a byte.
What is Cybiko C? Cybiko C is the language used to make Cybiko programs (at least for us). C has been around for many years, and most types of computing devices can have programs written in C (meaning, that type of computing device has programs to convert from C code to native instructions). Not all C code can run on all computing devices. Most notably, there is no C standard for graphics. C code written so that it can be run on any computing device is called “portable” or “cross-platform”; code that is specific to a particular computing device and operating system is called “non-portable” or “platform-specific.” Most of the time, in Cybiko C, we will be writing platform-specific code, and very little portable code.
How Do I Program in Cybiko C? This is a rather large question, and it’s going to take me a while to fully answer it. Assuming no prior knowledge, let’s take it from the top. When you write a program, you start out with one or more text files. These are called “source files” and contain “source code.” This is the stuff you write, and we will refer to it hereafter as just “source.” Once you have written your source, you apply three programs to it: the preprocessor, the compiler, and the linker. Don’t worry about these too much right now, we’ll get to them later. They convert your source code into the numerical instructions that can be read by the Cybiko’s CPU, and make things happen.
The main Function In ANSI C, here is an example of one of the simplest programs that you can write.
Team LRN
314
Appendix A: The C Programming Language
Listing A.1 int main() { return 0; }
This is a full program. I admit that it doesn’t do anything, but we have to start somewhere. We’re going to take this line by line, and I’ll explain it the best I can.
int main() This is a function header. The name of the function is “main.” A function is just a block of code that is run on a computing device. Every C program has to have a main function, since the main function is where the program starts when you run it. The “int” at the beginning is a type, which we will get to in a moment. An int is a type of number that can range from –32,768 up to +32,767. This is called the “return type” of the function. The opening and closing parentheses () are a part of a function declaration. Without parentheses at the end of the declaration you have a variable, not a function. If you’re confused, don’t worry about it. All will become clear momentarily. The important thing to remember here is that the main function is where the program starts. Without a main function, we don’t have a program.
The Curly Braces The rest of the code in Listing A.1 after int main() is the function body for the main function. It starts with an opening curly brace ({) and ends with a closing curly brace (}). Everything between these two braces is the code itself.
return 0; For every function that returns a value (this main function returns an int), we must have a return statement. This value is returned to wherever the function was called from. In the case of a main function, it is called by the operating system, so the operating system is sent the value following “return.” A special property of the return statement is that no code after it is executed. A return exits out of the function it is in immediately. Notice the semicolon at the end of the return statement. All statements must have a semicolon after them, in order to separate them from other statements.
A Cybiko main Function There is a special format for the main function on the Cybiko. It is shown below. Listing A.2 long main(int argc, char* argv[], bool start) { return 0; }
Team LRN
Appendix A: The C Programming Language
315
The only difference between this main function and the first one is the first line. Instead of an int in front, there is a long. A long is just another type of number that can be returned. In the parentheses there are some extra declarations for what are called parameters. Don’t worry about these too much right now. We’ll talk about them more when we discuss functions. While I may have left some of your questions unanswered while discussing the main function, I beg your patience. C is one of a number of languages where you need to have knowledge before you have knowledge of something. Eventually, it all just clicks together and you have full understanding.
Built-in Types and Variables In order to do anything useful, we must have some way of keeping track of information. The way we do this is through variables. A variable is nothing more than an amount of volatile storage set aside to hold a value. We get to give this piece of memory a name (so that it is more meaningful to us).
int and Binary Representations To set aside some memory for a variable, you simply put the type of variable you want, followed by a name, followed by a semicolon, like so: Listing A.3 int x;
This sets aside enough memory to store a value between –32,768 and 32,767. The amount of memory taken up by x is 2 bytes, or 16 bits. The highest bit is used for the sign. If it is set, the number is negative. The remaining bits are used for a binary value. Each bit has a numerical equivalent if it is set. These equivalents are shown in the following table. Bit 0 is the “lowest bit,” or “least significant bit” (LSB). Bit 15 is the “highest bit” or “most significant bit” (MSB). Table A.1 Bit breakdown of type int Bit#
Value if set
0
1
1
2
2
4
3
8
4
16
5
32
6
64
7
128
8
256
Team LRN
316
Appendix A: The C Programming Language
Bit#
Value if set
9
512
10
1,024
11
2,048
12
4,096
13
8,192
14
16,384
15
–32,768
Up until bit 15, each value in the table is double that of the entry above it. In this way, no two different bit patterns will have the same numerical value, so we have the maximum range that can be stored in an int, since each value is unique and represents a unique number. To convert a number from its binary representation to a number that you or I would use (base ten), you multiply the individual bit’s value by the value if set, and add all of the numbers together. For example, the following binary value (the first digit is bit 15, and the last digit is bit 0): 00110101 01110001 would be calculated as follows: Bits 0, 4, 5, 6, 8, 10, 12, and 13 are set, so we take the values corresponding to those bits from the table and add them together. 1(bit0) + 16(bit4) + 32(bit5) + 64(bit6) + 256(bit 8) + 1024(bit 10) + 4096(bit 12) + 8192(bit 13) = 13681 To convert a base ten number, like 25,000, into its binary equivalent, you start with bit 14 and go downward if the number is positive, or bit 15 if the number is negative. Bit 15 is only set when a number is negative. If the number is greater than or equal to the numerical value for that bit, you set the bit, and subtract the numerical value from the number. Keep going until you reach bit 0. If at any time you are left with 0, all remaining bits will be zero. For an exercise, let’s convert 25,000 to its binary equivalent. Since this number is positive, bit 15 is 0. 25,000 is greater than 16,384 (bit 14), so we set bit 14 and subtract 16,384 from the number to give us 8,616. 8,616 is greater than 8,192 (bit 13), so we set bit 13, and subtract 8,192, leaving us with 424. 424 is not greater than 4,096, 2,048, 1,024, or 512 (bits 12, 11, 10, or 9), so these bits are 0. 424 is greater than 256 (bit 8), so set bit 8, and subtract 256 to leave 168. 168 is greater than 128 (bit 7), so set bit 7, and subtract 128 to leave 40.
Team LRN
Appendix A: The C Programming Language
317
40 is not greater than 64, so bit 6 is 0. 40 is greater than 32, so bit 5 is 1. Subtract 32 to leave 8. 8 is not greater than 16, so bit 4 is 0. 8 is equal to 8, so bit 3 is set. Subtract 3 to leave 0. Since zero is left, bits 2, 1, and 0 are all 0. The binary representation for 25,000 is 01100001 10101000. This method will also work for negative numbers; you simply include bit 15 in the conversion. For example, if you were converting –25,000 into its binary equivalent, you would start with bit 15. Since –25,000 is greater than –32,768, you would set bit 15, and subtract to get 7,768, and proceed on down the line with the rest of the bits. The value of –25,000 in binary winds up being 10011110 01011000. Another method to convert from base ten to binary is the “divide by two” method. With this method, you fill in the bits from LSB to MSB. With this method, you start with your number, and divide by two, placing the remainder in the bit. Whenever you are left with 0, all remaining bits are 0. Let’s try this with 25,000, just to see if it works. 25000/2=12,500, remainder 0 12500/2=6,250, remainder 0 6250/2=3,125, remainder 0 3125/2=1,562, remainder 1 1562/2=781, remainder 0 781/2=390, remainder 1 390/2=195, remainder 0 195/2=97, remainder 1 97/2=48, remainder 1 48/2=24, remainder 0 24/2=12, remainder 0 12/2=6, remainder 0 6/2=3, remainder 0 3/2=1, remainder 1 1/2=0, remainder 1 To make this work for negative numbers, set bit 15, and subtract –32,768 from the value.
Other Built-in Types Besides int, there are two other built-in types on the Cybiko (ANSI C has more). These are char and long. A char has 8 bits, and represents numbers –128 to 127 (bit 7’s value is –128). A long has 32 bits, and represents –2,147,483,648 through 2,147,483,647. Most of the time, you should be able to get away with ints, but you might use a char to save space, or long for when you really need a large number. Chars are also useful for storing strings.
Team LRN
318
Appendix A: The C Programming Language
Following is a table of the built-in types you can use in your programs, the number of bits they require, and the number of bytes they require. Table A.2 Built-in type storage requirements Type
Bits
Bytes
char
8
1
int
16
2
long
32
4
You can declare variables of any of these built-in types, just as we did with an int. Listing A.4 int x; char c; long l;
You can also name the variable almost anything you like, but there are some limitations. The name must start with a letter (either capital or lowercase) or an underscore. A variable name can contain letters (capital or lowercase), numbers, and the underscore. Here are some examples of valid variable names. Listing A.5 char my_char; int x33; long Y_pos; int _blah;
Some other details of which you should be aware. Names are case sensitive, so “int x;” and “int X;” are two different variables. Also, two variables cannot be declared with the same name in the same scope, even if you use the same type. Scope is something we’ll discuss later.
Assigning Values to Variables You can assign values to variables by using the “=” operator (called the assignment operator). Here are a few examples. Listing A.6 char c; int i; long l; c=10; i=500; l=–75000;
Team LRN
Appendix A: The C Programming Language
319
The assignment operator converts the number on the right of the = into the binary equivalent, and stores it in the space set aside for the variable name on the left of the operator. The variable name must always be on the left of the = sign, so “10=c;” won’t work, since you cannot assign a value to 10. Variables to which you can assign values are called “left-side values,” referring to the side of the = they are on when we assign them. For short, these are called “lvalue.” We can also assign variables to the contents of other variables, as shown here: Listing A.7 char c1; char c2; c1=10; c2=c1; //c2 now contains 10 c1=20; //c2 still contains 10
It is important to note here that each variable exists separate from all others, so we may have copied the contents from c1 into c2 above, but when we reassign c1 to a different value, the value in c2 remains unchanged. So, the assignment operator (=) really means “copy the value of what is to my right into the storage area on my left.”
Operators While being able to store values is neat, being able to do something with them would be even better. So, we shall start with mathematical operations we are all familiar with: adding, subtracting, multiplying, and dividing. To do these things, we need operators. To add two values together, you use “+”, to subtract, you use “–”, to multiply, you use “*”, and to divide, you use “/”. Here are some examples: Listing A.8 int i; i=10+10; i=10–10; i=10*10; i=10/10;
//sets //sets //sets //sets
i i i i
to to to to
20 0 100 1
Just like in real math, you cannot divide by zero. The +, –, *, and / are all called operators. This means that they perform some sort of operation on numbers. In particular, these are binary operators. This has nothing to do with the binary representation of numbers in a computing device, but rather how many numbers it needs for an operator, namely two. Each of the numbers on which an operator works is called an operand. Binary operators have two operands. You can perform operations on variables as well as numbers, as shown below. Listing A.9 int i; int i2;
Team LRN
320
Appendix A: The C Programming Language
i=10; i2=i+i; i2=i–i; i2=i*i; i2=i/i;
//10+10=20 //10–10=0 //10*10=100 //10/10=1
Also, you can mix variables and numbers together to perform operations, like “i+10” or “10*i” and so on. The +, –, *, and / operators have equivalents in normal mathematics, so they are good to start out with. However, they are not the only operators. For example, there is also the % operator, which is related to the / operator. The % operator is called the modulus operator. For those of you without a Latin dictionary handy, a modulus is simply the remainder left after division has taken place. For example, 10 divided by 3 is 3 remainder 1, so 10/3 is 3 and 10%3 is 1.
Division and Negative Numbers Most of the arithmetic operators (+, –, and *) work just fine whether or not you are using negative numbers. The same cannot be said for / and %. We are dealing entirely with integer types, so we have no decimals. In integer arithmetic, if division does not give us a whole number result, we are supposed to get a number that when multiplied by the divisor will be less than the number we are dividing into. In other words, we determine the fractional answer, and then round down. For example, 10/3 will give us 3, even though the actual answer is 101 3 or 10.33333333(repeating). For 1/3, we get 0, and for –1/3, we are supposed to get –1 since –1/3 or –0.333333 (repeating) is less than 0 but greater than –1. In math on the computer, this does not happen (at least, not on all platforms, and not on the Cybiko in particular). When we have the expression –1/3, we get 0, not –1. Instead of rounding down, the computer rounds toward 0. This is OK in normal circumstances, if we are only dividing. However, if we consider how the modulus operator works, we can see that it adds some problems. The modulus operator works as shown below: x%y=(x–(x/y)*y)
For sake of exercise, let’s plug in –10 for x and –3 for y. This will give us –10–(–10/3)*3, which solves down to –10–(–3)*3, and then to –10–(–9), then to –10+9, and finally down to –1. So, for all positive numbers, the modulus is in the range between 0 and one less than the number you are dividing by. For negatives it is between one greater than the negative of the number you are dividing by and 0. The modulus, according to mathematics, is supposed to be non-negative and equal to or greater than 0, and less than the divisor. Consider the following table, which shows the results of the / and % operators using a divisor of 3 on numbers from –6 to 6, both as it is supposed to be in mathematics and how it actually is on a computer.
Team LRN
Appendix A: The C Programming Language
321
Table A.3 Computer division by 3 X
X/3(math)
X%3(math)
X/3(comp)
X%3(comp)
–6
–2
0
–2
0
–5
–2
1
–1
–2
–4
–2
2
–1
–1
–3
–1
0
–1
0
–2
–1
1
0
–2
–1
–1
2
0
–1
0
0
0
0
0
+1
0
1
0
1
+2
0
2
0
2
+3
1
0
1
0
+4
1
1
1
1
+5
1
2
1
2
+6
2
0
2
0
As you can see, the mathematics version shows a regular pattern for both / and %. The / operator has a series of three answers in a row the same, then the answer increases by 1, and the % operator has a nice 0 1 2 pattern repeated throughout. The only time that the X/3(math) and X/3(comp) agree is when X%3 is zero. This is just something of which you should be aware, as it can commonly cause problems in logic if negative numbers and division are introduced. Unary Plus and Minus The + and – operators have two functions (more than one function on an operator makes the operator “overloaded”). The + and – also may be used as unary operators, as shown below. What they do is fairly obvious. Listing A.10 x=10; y=+x; z=–x;
Shortcut Assignment Operators Consider the following snippet of code: Listing A.11 int x; x=10;
//assign 10 to x
Team LRN
322
Appendix A: The C Programming Language
x=x+10; //add 10 to x, and assign x to this value //x now is 20
Code like this, where a variable like x is added to, subtracted from, multiplied by, or divided by (including “modulo-ed”), and then reassigned to that variable are so common that the creators of C made special operators for these tasks. These operators take the normal operator (like +), and append the = after it (with no spaces in between). This operator is then used anywhere you might use the = operator. Here are a few examples. Listing A.12 x+=10; x–=10; x*=10; x/=10; x%=10;
//x=x+10; //x=x–10; //x=x*10; //x=x/10; //x=x%10;
I personally call these operators “plus-equals,” “minus-equals,” “times-equals,” “divide-equals,” and “modulus-equals.” This is especially useful when you have a variable name that is long, like time_started. You don’t want to have to write time_started=time_started+10, when time_started+=10 will do. Also, this will save you the grief of having to spell the same name correctly twice in succession. (Programmers are typically not good at spelling; we are also too lazy to type in the same variable name twice, even if it is a short one like “x”.) There are two operations that show up so frequently that they have their own operators. These special operations are x=x+1 and x=x–1, or x+=1 and x–=1. The special operators are ++ and – –, as shown here. Listing A.13 x++; x––;
//x=x+1; //x=x–1;
There is another special thing about the ++ and – – operators. They can be placed either in front of a variable or behind it. The position of the operator in relation to the variable makes the operator behave a little differently. Consider the following code. Listing A.14 int x; int y; x=10; y=x++; //what does y equal? //x currently equals 11 y=++x; //what does y equal? //x currently equals 12 y=x––; //what does y equal? //x currently equals 11
Team LRN
Appendix A: The C Programming Language
323
y=––x; //what does y equal? //x currently equals 10
Now we’re going to get into some pretty weird stuff. It all makes sense after a while though. In the first case, “y=x++;”, x is 10 before this line, and 11 after this line. However, since the ++ is after the x, the value assigned to y is 10. In the second case “y=++x;”, x is 11 before this line, and 12 after this line. The ++ is before the x this time, so the value of 1 is added to x before the value is assigned to y, so y=12. In the third and fourth cases, y is assigned to 11 and 11 respectively. The same rules apply for – – as they do for ++. If it seems a little weird to you, consider these operators (the ++ operator is called the increment operator and the – – operator is called the decrement operator) as either “pre” or “post”; the pre operators come before the variable, and the post operators come after the variable. For the pre operators, the increment or decrement is done first, then other operations are done, and for the post, the value is retrieved first, and then the increment or decrement is done. Don’t worry, you’ll get the hang of it in no time. Besides, it is pretty rare to use the increment operator and the assignment operator in the same statement. Bitwise Operators We talked about the representation of data as a collection of bits a little earlier. There are operators that we can use to treat our variables as collections of bits rather than numbers. These may not have any immediately obvious uses, but trust me, they are as essential as the + operator. For this discussion, we have to speak a little bit about Boolean theory. Boolean theory deals with variables that have only two states, true and false. A computer, which runs on electricity, contains information in circuits with either power on or off. By convention, when power is applied to a circuit, it represents true or 1, and when power is not applied to it, it represents false or 0. So, a computer circuit is ideal for applying Boolean theory. A char, then, represents a set of eight Boolean values, a 16-bit int, and a 32-bit long. For now we will consider only an individual bit, and not a set of bits, since in order to talk about set operations, we have to discuss set theory. We will talk about the values 0 and 1 instead of false and true; just keep in mind that these values are interchangeable. There are six bitwise operations as a part of ANSI C. Cybiko C has all of these. They are &, |, ^, ~, <<, and >>. These are called AND, OR, XOR, NOT, Shift Left, and Shift Right. The shift operators deal with the bits in a byte as a set, so we won’t discuss them just yet. Also, Cybiko C has an additional bitwise operator, >>> (unsigned shift right, which is not a part of ANSI C), which we will tackle later on with the other shifting operators.
Team LRN
324
Appendix A: The C Programming Language
AND (&) The & operator is for applying a bitwise AND. It works pretty much as you would expect it to. If you have two bits, A and B, and apply the & to them, you will get a 1 or true if and only if both A and B are true. The following table is called a “truth table” for the & operator. It shows, with given values in A and B, what the result will be for all combinations of A and B. Table A.4 Truth table for & A
B
A&B
0
0
0
0
1
0
1
0
0
1
1
1
OR (|) The | operator is for applying a bitwise OR. Like &, it works as you might expect it to. If you have two bits, A and B, and apply | to them, you will get a 1, or true, if either A or B is true or if both are true. The following table is a truth table for bitwise OR. Table A.5 Truth table for | A
B
A|B
0
0
0
0
1
1
1
0
1
1
1
1
XOR (^) The ^ operator is called “exclusive OR.” The closest English equivalent to it is “one but not both.” It does have its uses. Here is the truth table for ^. Table A.6 Truth table for ^ A
B
A^B
0
0
0
0
1
1
1
0
1
1
1
0
Team LRN
Appendix A: The C Programming Language
325
The ^ operator is true when A is true or B is true, but when both are true or both are false, it is false. NOT (~) The ~ operator is also called the “bitwise complement.” It reverses a bit, changing true to false or false to true. This makes sense because “not true” is false, and “not false” is true. Unlike &, |, and ^, which are binary operators, ~ is a unary operator, so it only works on a single value. Shortcut Operators The &, |, and ^ each have a corresponding assignment operator, like +, –, and * do. These operators are &=, |=, and ^= (there is no ~=, since ~ is a unary operator). Set Theory You’re about to feel as though you are in a Pre-Algebra class, so hang on (at least, Pre-Algebra is where I had my first encounter with set theory). As stated earlier, bitwise operators operate on chars or ints or longs, but treat them as collections of bits rather than numbers. Now let me introduce you to Tom, Sally, Harry, Sue, Bob, Kim, and Oglethorpe, seven students in a very small school. In this school there are four classes, English, Math, Geography, and Advanced Nuclear Physics. Here are the breakdowns of these classes and who is in each. English: Tom, Harry, Kim, Oglethorpe Math: Sally, Sue, Bob, Oglethorpe Geography: Sally, Harry, Kim Advanced Nuclear Physics: none (for God’s sake, this is only a third-grade class!) Now, it is easy for us human beings to ask ourselves “which student(s) are both in English and Math?” or “which student(s) are either in English or Math?” or “which student(s) are not in Geography?” Human beings have the advantage of being able to reason such things out. Computers do too, but must do so in a more formal way. Luckily for us, there were some really smart guys several hundred years ago who came up with set theory, the formalized analysis of sets. Combined with Boolean theory (also figured out by a smart man), we can do useful things on the computer. A “set” is nothing more than a group of stuff. Anything can be grouped, but to be useful, the “stuff” we are grouping should be meaningful. An individual piece of “stuff” that we are grouping is called an “element.” A set contains zero or more elements. A set with zero elements is called an “empty set.” Elements can be in more than one set at a time. An individual element can only be in a set once (there is no duplication). So, with all of this in mind, let’s make sets from these classes. Set of students in English class: {Tom, Harry, Kim, Oglethorpe} Set of students in Math class: {Sally, Sue, Bob, Oglethorpe} Set of students in Geography class: {Sally, Harry, Kim}
Team LRN
326
Appendix A: The C Programming Language
Set of students in Advanced Nuclear Physics class: {} (empty set) The { and } delineate the start and end of a set. At least, that’s how my seventh-grade teacher Mrs. Easton taught it to me. Now that we have our sets figured out, we can start to ask questions and determine the answers to them in a more formal way. For example, we can ask, what students are both in English and Math? and then proceed to compare the sets, looking for students (elements) that are in both. We find that of all of the students, only Oglethorpe is in both, so the set of students in both English and Math is {Oglethorpe}. A comparison of sets where we are seeking elements that are only in both sets is called an intersection. {Oglethorpe} would be the set found at the intersection of English and Math. Another question we might ask is, what students are in English or in Math? By comparing the sets and adding elements (only adding an element once) to a new set for each element in both sets, we come up with {Tom, Harry, Kim, Oglethorpe, Sally, Sue, Bob} as the set of students in either English or Math. This operation is called a union of two sets. Finally, we might ask, what students are in English or Math, but not in both? Comparing the sets, we determine that this set is {Tom, Harry, Kim, Sally, Sue, Bob}, with only Oglethorpe left out because he is in both. This is the difference of two sets. I admit, the example so far has been a bit juvenile, so let’s actually translate all of this into programming terms. We can consider a char to be a set of eight bits. We have seven students. We can consider each student to have its own bit, like so: Listing A.15 char Harry; char Tom; char Bob; char Oglethorpe; char Sue; char Kim; char Sally; Harry=1; Tom=2; Bob=4; Oglethorpe=8; Sue=16; Kim=32; Sally=64;
//bit //bit //bit //bit //bit //bit //bit
0 1 2 3 4 5 6
In this representation, the variable “Harry” does not actually represent just Harry. It is a char like any other, so it is “the set of students that are Harry,” since it contains a group of 8 bits, but only the “Harry” bit is set. Since each variable is a set, and not just an element, we can combine these sets with unions to make our classes. The bitwise equivalent of “union” is the | operator, since in a union, a new set is made from elements in either set.
Team LRN
Appendix A: The C Programming Language
327
Listing A.16 char English; char Math; char Geography; char AdvancedNuclearPhysics; English=Tom|Harry|Kim|Oglethorpe; Math=Sally|Sue|Bob|Oglethorpe; Geography=Sally|Harry|Kim; AdvancedNuclearPhysics=0; //0 is the empty set, no bits set
Now we can ask the questions we asked earlier. An intersection of sets is a &, and a difference of sets is a ^. We already determined that a union is |. Listing A.17 char EnglishAndMath; char EnglishOrMath; char EnglishOrMathNotBoth; EnglishAndMath=English&Math; EnglishOrMath=English|Math; EnglishOrMathNotBoth=English^Math;
As you can see, set theory can be quite useful, and computers can be used to do it using bits if the bits are meaningful. Bit Shifting The bit shifting operators are pretty neat. They allow us to multiply or divide by powers of two in less time than it normally takes. ANSI C has two of these operators, << and >>. Cybiko C adds a third one, >>>. These are binary operators (taking two operands), so you will always see things like x>>y or x<>>y. The x and y might be any variable or value. Shift Left (<<) Consider the following binary representation of a char: 00110000 This has the numerical value of 48 (bits 4 and 5 are set, so 32+16=48). The << operator will move all bits one position to the left. The rightmost position will be filled in with 0, and any value on the left edge will “fall off.” If we shifted this value one position to the left, we would have the following value. 01100000 This has the numerical value of 96. It just so happens that 96 is double the value of 48, so we have effectively multiplied by two. Shift Right (>>) and Unsigned Shift Right (>>>) Shift Right does the opposite of Shift Left. Consider the same representation of 48 as we used earlier.
Team LRN
328
Appendix A: The C Programming Language
00110000 Now, shift this value to the right by one position. We get the following value. 00011000 This has a value of 24, which is exactly one half of 48, so we have effectively divided by two. Had there been a 1 in bit 0, it would have “fallen off” the end. Now consider the value of –128. 10000000 If we shift this right by one, we get the following value. 01000000 This is 64, which is not –128/2. This would be the result we got from >>>, which fills in a 0 from the left whenever shifting. However, >> checks the value of the leftmost bit, and fills in that value when shifting. The results of 1000000 >> 1 is 11000000, or –64, and the results of 10000000>>>1 is 01000000. So, to make a long story short, if you are using right shifts to divide by a power of two, use >>, but if you only care about shifting bits, use >>>. Shift-Assignment Operators Like other binary operators, <<, >>, and >>> each have assignment operators associated with them as shortcuts. These operators are <<=, >>=, and >>>=. Parentheses and Order of Operations Consider the following expression: 5+6*7–8/2. Type it into a handheld calculator (non-scientific), and you will get 34.5. Type it into a scientific calculator, and you will get 43. Assign it to a variable in your program, and you will get 43 also. Why the difference? In mathematics, there is an order of operations. Multiplication and division are done before addition and subtraction, so the 6*7 and 8/2 are calculated before the addition and subtraction are done. C also has an order of operations for all of its operators. This order is shown below, from highest priority to lowest priority, as well as whether they are figured out from right to left or from left to right (called their “associativity”). I haven’t shown all of these operators yet, so don’t worry about those I haven’t covered; we’ll get to them. Table A.7 Operator precedence Operator
Name
Associativity
++
Post-increment
Left to right
––
Post-decrement
*
()
Function call
*
[]
Array element
*
Team LRN
Appendix A: The C Programming Language
Operator
Name
Associativity
–>
Pointer to structure member
*
.
Structure or union member
*
++
Pre-increment
Right to left
––
Pre-decrement
*
!
Logical NOT
*
~
Bitwise NOT
*
–
Unary minus
*
+
Unary plus
*
&
Address
*
*
Indirection
*
sizeof
Size in bytes
*
(type)
Typecast (for example, (float) i)
*
.*
Pointer to member (objects)
Left to right
–>*
Pointer to member (pointers)
*
*
Multiply
Left to right
/
Divide
*
%
Remainder
*
+
Add
Left to right
–
Subtract
*
<<
Left shift
Left to right
>>
Right shift
*
>>>
Unsigned right shift
*
<
Less than
Left to right
<=
Less than or equal to
*
>
Greater than
*
>=
Greater than or equal to
*
==
Equal
*
!=
Not equal
*
&
Bitwise AND
Left to right
^
Bitwise exclusive OR
Left to right
|
Bitwise OR
Left to right
&&
Logical AND
Left to right
||
Logical OR
Left to right
?:
Conditional
Right to left
Team LRN
329
330
Appendix A: The C Programming Language
Operator
Name
Associativity
=
Assignment
Right to left
*=, /=, %=, +=, –=, <<=, >>=,>>>=, &=, ^=, |=
Compound assignment
*
,
Comma
Left to right
* This operator is at the same precedence levels as the one above it, and has the same associativity.
As you can see, we have already covered most of the operators in this list. The others will be coming soon. So, when a computer sees an expression like 5+6*7–8/2, it takes the following steps to determine the value. It checks for each of the operators, starting at the top of the list and proceeding downward. In this case, the first one it would find is *, or the multiply operator. It would then go from left to right, solving any * operations. This leaves the expression as 5+42–8/2. The next operator it would find is /, so it again scans from left to right, solving any / operations, leaving us with 5+42–4. The next operator found would be +, which after solving is 47–4, leaving the last operator, –, which solves to 43. However, this isn’t always the behavior you want. So, there is a way to override the default behavior. You can use parentheses. For example, you might want to have the following expression solved: ((5+6)*(7–8)+2)/3. To solve this, the computer will go into the deepest set of parentheses (in our case the parentheses surrounding either 5+6 or 7–8), and solve them first. It continues to go into the deepest set of parentheses until there are no parentheses left, and then it solves the expression normally. In this case, the deepest set of parentheses surround 5+6 and 7–8, so it would solve down to (11*–1+2)/3. This leaves us with parentheses around (11*–1+2), which solves to –11+2, or –9. Finally, we are left with –9/3, which is –3. Comparison Operators and if Statements So far, we have dealt with operators that assign values or perform operations on assigned values. You should be able by now to do just about any arithmetic or bitwise operation on the planet. This is not the sum total of operators. Indeed, assigning value is useless unless we can ask questions about those values in order to do something meaningful. We may wish to ask, is x equal to y? or is x greater than y? Sometimes we may wish to ask, is x greater than or equal to y? We do this in an “if” statement. An if statement tests a condition, like “is x equal to y,” and performs some action if the condition is true. To test whether or not two values are equal, the operator is ==. To test whether or not one value is less than the other, you use <. To test if one value is greater than another, you use >. Here are a few examples of if statements.
Team LRN
Appendix A: The C Programming Language
331
Listing A.18 if(x==y) z=0; if(xy) z=1;
You can combine testing for equality with testing for greater than or less than, making a >= and a <= operator, which are greater than or equal to and less than or equal to, respectively. To test simply that a value is not equal to another, you use the != operator. You can consider the >= to be sort of a “not less than” operator and <= to be a “not greater than” operator. To negate the comparison (i.e., to test if the opposite condition is false), you can use the ! operator. Here are a few examples. Listing A.19 if(!(x==y)) z=0;//same as testing x!=y if(!(x=y if(!(x>y)) z=1;//same as testing x<=y
Notice the parentheses around (x==y) and the other expressions. The ! has a higher priority than ==, <, or >, so it would be evaluated first, essentially making !x==y be (!x)==y. So, this begs the question, what happens during the following if statement? Listing A.20 if(x) z=0;
We’ll say for the sake of argument that x is a char. An if statement only evaluates whether something is true or false, and doesn’t care about any other value. So, it converts x into a Boolean value. Converting from a type to a Boolean is a comparison with zero. A value of zero means false, and all other values mean true. So, “if(x) z=0;” translates to “if(x!=0) z=0;”. Similarly, the “if(!x) z=0;” translates to “if(!(x!=0)) z=0;” or “if(x==0) z=0;”. So, just putting a variable in an if statement is essentially testing it against zero, which has its uses. Now, what if we wish to see if x is between 0 and 10? We could do it with two if statements, of course, but that would be inefficient. Ideally, we should be able to do it in one, and we can, by using what is called a logical operator. Logical operators work on Boolean values. There are two of these, && and ||. These might look familiar to you because they are just doubled up AND (&) and OR (|) operators. They work just like & and | do, only on Boolean expressions rather than on chars or ints. We can check to see that x is between 0 and 10 with the following if statement. Listing A.21 if(x>=0 && x<=10) z=0;
We don’t have to use parentheses here, since && has a lower precendence than >= or <=. However, it is easier to read with parentheses in there, and it is good
Team LRN
332
Appendix A: The C Programming Language
programming practice to do so. Anything that makes code easier to read (both to yourself and to others) is considered “good programming practice.” NOTE: Be careful with = and ==. Placing = in an if statement where a == should be will wind up assigning a variable rather than comparing it. I’ve been programming for over 14 years, and I still occasionally do this!
Or else what? When using an if statement, you test to see whether or not a condition is true. We can structure a condition to test for just about anything. But, what if you want to do one thing if the condition is true, and another thing if a condition is false? You might, for the sake of argument, want to do one thing if x is less than zero, and another thing if it is non-negative. You might consider the following: Listing A.22 if(x<0) z=–1; if(x>=0) z=1;
This will work, but it is inefficient. These choices are mutually exclusive. When one is true, the other is never true. So, why test both conditions? We know that if the first condition is false, the other condition is true, so by testing both, we are doing more than we have to. The C language has a way to handle such things. We can put an “else” clause in as follows. Listing A.23 if(x<0) z=–1; else z=1;
Don’t be concerned that these are on separate lines. I did so on purpose. I also indented all of the lines differently so that we can better see what is going on here. In the above code, if x is negative, z is set to –1; otherwise, z is set to 1. This is better, since we aren’t testing x twice, which means we save a few precious processor cycles. Blocks of Code So far, during an if statement, we have only been doing a single thing if the condition is true. While this does happen quite a bit, more often than not there are a number of things that you want to do in response to a condition that is true. To do this, you use a statement block, like so. Listing A.24 if(x<0) { //statement#1
Team LRN
Appendix A: The C Programming Language
333
//statement#2 //et cetera }
You can place as many statements into the block as you like. If you are making use of an else clause, you can also make the else clause a statement block, like so: Listing A.25 if(x<0) { //statements go here } else { //statements go here }
The use of statement blocks is not limited to if statements. You can in fact put a statement block anywhere in your code where a statement can go. The Conditional Operator ( ?: ) While on the topic of if statements, I wanted to talk a bit about the conditional operator, ?:. This is the one and only “tertiary” operator in C. You can use it as shorthand for very simple if statements. For example, let us say you have three variables, x, y, and z. At the start, x and y already have values, and you want z to be assigned to the lesser of these values. You might use the following if else statement. Listing A.26 if(x
This will work, but in such a simple case, you might want to use ?: instead, like so. Listing A.27 z=(x
This does the exact same thing as the code in Listing A.26. Essentially, the ?: operator tests the condition in front of the ?. If the condition is true, the expression is the value of whatever is after the ? and before the :. If the condition is not true, then the expression is the value of whatever follows the :. Keep in mind that ?: has a very low operator precedence, so parentheses are almost always required.
Team LRN
334
Appendix A: The C Programming Language
Arrays We’ve covered just about everything we can cover about operators that work with numerical values. If you look back at Table A.7 that lists all of the operators available, you’ll see that we only have a few left, and we will get to them soon. Now, we are going to talk about arrays and the operator [ ]. You should already be quite comfortable with the idea of a variable. An array is also a variable, but instead of containing a single value, it contains a number of values. The usual example for this is a list of scores from some athletic event, like basketball, baseball, or whatever. Who am I to mess with tradition? Let’s say that there are two teams, A and B. During the season, they played five games against one another. (The game that they play is unimportant, but if you like, consider it to be basketball.) Here’s a table with all of the games listed, and their scores. Table A.8 A v. B for 2001 season Game#
A’s Score
B’s Score
1
45
67
2
52
39
3
66
80
4
91
21
5
76
74
To represent this on a computer, we might make a variable for each game for each team, but that is rather silly. We should instead have a list of scores for each team. This is where an array comes in. Listing A.28 char A[5]; char B[5]; A[0]=45; A[1]=52; A[2]=66; A[3]=91; A[4]=76; B[0]=67; B[1]=39; B[2]=80; B[3]=21; B[4]=74;
Now we have arrays of scores, rather than a bunch of variables. The line that declares the array for A, char A[5], is saying “I would like a list of 5 char sized variables,” and
Team LRN
Appendix A: The C Programming Language
335
sets up space in the volatile storage area for them. To access these variables, we again make use of [ and ], with a number inside saying which of the chars we would like to access. This is called the subscript. A[0] is the first number in the array, and A[4] is the last number in the array. We should not access A[5], since it references memory outside of the array. When you declare an array with N members, the subscript should range from 0 to N–1. If you try to access outside of this range, you will probably cause an error (although the nature of the error is likely not to show up immediately). We can use A[0] any place where we would use any other char. The same for A[1], A[2], B[3], and so on. It’s just a number, the only difference is that it is in an array. The for Loop Consider for a moment arrays A and B, which contain scores for basketball. Now let us say that we want to determine some information not directly in evidence. Perhaps we want to count how many games each team won. The code that follows will do this for us. Listing A.29 int AWins; int BWins; int Ties; //initialize the variables to 0 AWins=0; BWins=0; Ties=0; //check game 1 (subscript 0) if(A[0]>B[0]) AWins++; if(B[0]>A[0]) BWins++; if(A[0]==B[0]) Ties++; //check game 2 (subscript 1) if(A[1]>B[1]) AWins++; if(B[1]>A[1]) BWins++; if(A[1]==B[1]) Ties++; //check game 3 (subscript 2) if(A[2]>B[2]) AWins++; if(B[2]>A[2]) BWins++; if(A[2]==B[2]) Ties++; //check game 4 (subscript 3) if(A[3]>B[3]) AWins++; if(B[3]>A[3]) BWins++; if(A[3]==B[3]) Ties++; //check game 5 (subscript 4) if(A[4]>B[4]) AWins++; if(B[4]>A[4]) BWins++; if(A[4]==B[4]) Ties++;
This will work, but if you’re like me, you’ll see this as way too much code to be useful. If we were going to do this, we might as well just be using individual variables for each score!
Team LRN
336
Appendix A: The C Programming Language
Essentially the same code is written here five times. Each set of three if statements has only a different subscript. It would be nice if we could simply write these three if statements one time, and have a variable represent the subscript, and make that variable start at 0 and end at 4. We can do this with a for statement. The for statement is a very powerful statement. Here is the same code, rewritten as a for statement. Listing A.30 int AWins; int BWins; int Ties; int Game; //initialize the variables to 0 AWins=0; BWins=0; Ties=0; for(Game=0;Game<5;Game++) { if(A[Game]>B[Game]) AWins++; if(B[Game]>A[Game]) BWins++; if(A[Game]==B[Game]) Ties++; }
As you can see, this is a lot shorter, uses less code, and doesn’t require us to duplicate code. The for statement itself contains three parts within the parentheses, separated by a semicolon. The first part is the initial condition. Here I have set Game to zero, since zero is the subscript of the first game. The second part is a test condition. At the beginning of the statement block, this condition is evaluated. If the condition is true, then the statement block is executed; otherwise the code continues after the statement block. The third part is some sort of action to perform after the statement block has been executed. This type of thing is called a loop, and the for statement with its statement or statement blocks are called a for loop. Let’s step through this for loop from start to finish. First, Game is set to zero. Next, Game is compared to 5, and if it is less than five, it executes the statement block. Since 0<5, the statement block is executed, and the if statements are executed. After the statement block has been executed, Game++ is executed, making Game now equal to 1. Now it starts over. 1<5, so execute the statement block, then add 1 again to make Game equal to 2. 2<5, so execute statement block again, add one to Game to make 3. 3<5, so execute the statement block again, add one to Game to make 4. 4<5, so execute the statement block again, add one to Game to make 5. 5 is not less than 5, so stop executing the statement block, and move to just after the statement block and continue on. This is a typical scenario of a for loop, although it is not the only version you might encounter. The initial condition can be just about anything, as can the action at the end. The test condition must be a Boolean expression (or something convertible to a
Team LRN
Appendix A: The C Programming Language
337
Boolean expression). We will have more on loops later. I just wanted to talk about the for loop since they are rather important for arrays. Multi-Dimensional Arrays So far, we have looked only at single-dimension arrays, i.e., those with only one subscript. However, we can have as many dimensions as we want. When we do this, we are essentially making an array of arrays, or an array of an array of arrays of arrays, and so on. Usually, it isn’t very useful to make arrays larger than three dimensions. Here’s how to make a two-dimensional array: Listing A.31 int Board[8][8];
In this array, you have two subscripts, which you can reference by different variables if you like. The number of elements in this array is 8x8, or 64. You could also make Board[8][8][8], which would be an 8x8x8 array, containing 512 values, and so on. Most of the time you will use single-dimension arrays, and occasionally a two-dimensional array, but higher orders (i.e., more dimensions) are rarely used.
Pointers Welcome to your first brick wall. We are now going to talk about pointers. For people who start into programming, this is the first trial by fire. Many people never “get” pointers, and are thus limited in how effectively they can program in C. It’s not that they are terribly difficult or anything, it’s just the first major hurdle in programming. I will explain pointers as I have in many one-on-one sessions in the past. Most people that I’ve spoken to about them have gotten the concept down. We spoke earlier about volatile storage, or memory. We have also spoken about variables, and I told you that when you declare a variable, some space is set aside in volatile storage for them. So you know that somewhere in memory is a value that you refer to as “x” or “z” or whatever. A pointer is a variable that tells us where something is. Every variable has its own location in memory. Consider the street you live on. Your house has some numbers on it (usually). These numbers allow the postal service to get information to you through the mail. This is your address. When you write down your address, you are describing where on the planet you live. Now consider a single, really long street, with houses on only one side. Each house contains a single byte. Each house has an address, just like where you live. This address is what is stored in a pointer. It is the location (address) in memory (the really long street) at which something (a byte) lives. If you know where a byte is, you can change it without having to know the name of the byte. This is pretty simple, right? Now for the thing that really screws people up. A pointer is a variable as well. This means that there is memory set aside for the pointer to be stored in. The location that a pointer is stored in has an address, to which you can have a pointer, and so on,
Team LRN
338
Appendix A: The C Programming Language
theoretically with infinite regression. This is some mind-blowing stuff. Don’t think about that stuff yet. Think only of a single pointer that points to a variable somewhere in memory. To declare a pointer variable, you must have a “base type” for the pointer. For the most part, a pointer points to something, be it a char, an int, or a long. To declare a pointer, you do this: Listing A.32 int *int_ptr; char *char_ptr; long *long_ptr;
Normally, * means “multiply,” so the above code may seem a little strange to you. This is normal. The * operator is overloaded (there just aren’t enough symbols on a keyboard). The variable name all by itself, i.e., “int_ptr” is the variable that contains the address of an int. See the following example. Listing A.33 int x; int *int_ptr; int_ptr=&x;
Here, I have used the & operator as a unary operator. It is overloaded also. When we use & on a variable, we are saying “give me the address at which I can find this variable.” So, &x is the address of x, which we then store in int_ptr. So, int_ptr points to the value stored in x. Now, if we want to manipulate the value of x, without actually referencing x, we can do the following. Listing A.34 (*int_ptr)=12;
Here is * again, with another meaning. Using * on a pointer variable is like saying “access whatever is at the address stored in this pointer,” which in this case is the value of x. After this line, x now equals 12. Cool, no? We could then set int_ptr to point to something else, and affect its value similarly. “Well, what good is that?” you are asking. I know. Right now, this does not seem particularly useful. Reasons to use pointers will become clear as time goes on. You can write many very functional programs without ever once using a pointer. Some languages, like BASIC, don’t even have pointers, and there are billions of lines of code written in BASIC. Still, C makes heavy use of pointers, and so are required knowledge to get the full use of the language. Pointers and Arrays Arrays and pointers have a special relationship. They can be “converted” into one another. You can take an array variable and use it like a pointer, or you can take a pointer variable and use it like an array. Here’s an example.
Team LRN
Appendix A: The C Programming Language
339
Listing A.35 int int_array[10]; int *int_ptr; int_ptr=int_array;
When you use the array name all by itself, it is like saying “&int_array[0],” and it is a pointer to the first element in the array. You cannot assign a value to int_array, since it is not really a variable on its own. To explain how a pointer can be used as an array, I must first talk about pointer arithmetic. We have already said that a pointer contains an address or location in memory. This information is numeric, so we can perform operations on it just like we can with other numbers stored in memory. Pointer arithmetic works in a similar but not the exact same manner as arithmetic on a long or int. For example, if you have a pointer called int_ptr, which is a pointer to an int, typing int_ptr++ doesn’t add 1 to the address. Instead, it adds the size of what int_ptr points to (an int) to the address. Since an int takes up two bytes, 2 is added to the address, so that the pointer now points to the next int after the one it formerly pointed to. Similarly, using ++ on a long pointer will add 4 to the address, and using ++ on a char pointer will add 1. Using – – on a pointer similarly subtracts 1, 2, or 4 depending on if the pointer is a pointer to char, int, or long. Similarly, int_ptr+1 points to two bytes after int_ptr (1 represents how many integer-sized steps to take forward), and int_ptr–1 points to two bytes prior to int_ptr (1 again representing how many integer-sized steps to take backward). The reason for this is so that int_ptr+1 references a totally separate int than int_ptr does, and the same idea for int_ptr–1. Adding or subtracting from a pointer still gives you a pointer when you are done. So, if you wanted to work on the int following the int pointed to by int_ptr, you would use the expression *(int_ptr+1). Other operations, like multiplication and division, are not useful for pointer arithmetic. You will never need to use operators other than +, –, ++, and – – on pointers, and even those you should try and keep to a minimum. So, back to how arrays are related to pointers. With pointer arithmetic in mind, the expression (int_array[5]) is the same as *(int_array+5). So the [ ] really just converts pointer arithmetic into a more human-readable form. So, you can use [ and ] on a pointer just like you can on an array. Really, there is almost no difference between a pointer and an array. Arrays are just easier to think about. NOTE: While I have discussed pointer arithmetic in this section, you shouldn’t use it except in extreme cases where there is no other way. Pointer arithmetic leads to errors that are extremely hard to find.
Char Pointers and Strings Up until now, we have been talking about numerical data. However, this is not the only type of data with which we are concerned. Indeed, the ability to store and manipulate
Team LRN
340
Appendix A: The C Programming Language
text is of great importance to us as programmers. Text is stored in what are called strings. A string is just a number of characters. A char represents a single character. You can assign either a numerical value, or a “string literal” to a char. A string literal is some character surrounded by single quote marks, like ‘A’, or ‘a’, or ‘<’. You can use ‘A’, ‘a’, or ‘<’ anywhere you would use any number. Although they don’t look like it, these are numerical values. ‘A’ for example, is the ASCII equivalent of the character A, or 64. Listing A.36 char x; x=’A’;
//same as saying x=64
Some string literals take more than one character to represent. For example, there is no way to represent the newline character (which is what the Enter key means), but we can use ‘\n’ to represent this character. There are a number of characters that you can put after the \ to make non-standard character codes, including ‘\t’ for a tab. To make an actual \, you have to use ‘\\’. A string is just an array of characters, represented like this: “This is a string.” In an array of chars, the first element would be ‘T’, the second ‘h’, and so on. After the ‘.’ character, there would be placed one additional character, ‘\0’, which has a numeric value of 0, so “This is a string.” turns into the string literals: ‘T’,‘h’,‘i’,‘s’,‘ ’,‘i’,‘s’,‘ ’,‘a’, ‘ ’,‘s’,‘t’,‘r’,‘i’,‘n’,‘g’,‘\0’. Any information in quotes is considered to be a string, and all strings are char pointers. Since arrays and pointers are interchangeable, arrays of char are often treated as strings as well. Unfortunately, you can’t just do the following: Listing A.37 char mystring[40]; mystring=”This is a string.”
You have to instead use a function (and we haven’t gotten to functions yet, but this is important) called strcpy, which stands for “string copy.” So, to set mystring to “This is a string.”, you must use the following code. Listing A.38 char mystring[40]; strcpy(mystring,”This is a string.”);
We’ll talk more about functions later on. Void Pointers The word “void” means “nothing,” so you might think that a void pointer points to nothing. This isn’t strictly so. A void pointer points to “nothing in particular,” so it can point to anything, be it a char, an int, a long, or something else. A void pointer is a “pure” pointer. It doesn’t know what it is pointing to. You can’t use pointer arithmetic on it, you can’t use * to access what it is pointing to, and you can’t use array
Team LRN
Appendix A: The C Programming Language
341
subscripts on it, since all of these things are meaningless when you don’t know what the pointer is pointing to. Here’s how to declare a void pointer. Listing A.39 void *void_ptr;
I know at the moment void pointers seem, well, pointless (no pun intended). They do have their uses, however. For example, when you might be pointing to just about anything, you can use void pointers. Also, when what you are pointing to doesn’t matter, void pointers can be used. I’ll show you some uses for these later. NULL Pointers There is one special value that a pointer can have that indicates that it points to nothing. This value is 0. You can also use NULL (in Cybiko C, NULL is a pointer to a char equal to zero, so you will get warnings anytime you are using it on a non-character pointer, but it will work just fine).
Structures Sometimes, the data you wish to store cannot be adequately stored in a simple type like a char, int, long, string, or pointer. A good example of this is a date. A date is a grouping of three numbers: a month, a day, and a year (or a day, a month, and a year if you are in Europe). In order to store these three numbers, we need three variables. However, the individual numbers in a date are meaningless (or less meaningful) separately, so it only makes sense to keep them together somehow. To put these numbers together in a meaningful way, we use a structure, or a struct in C lingo. This is how you would declare a date struct in C. Listing A.40 struct date { char day; char month; int year; };
The word “struct” tells us that we are now defining a structure. “date” is the name of our struct, and the stuff between the { and the } is what the struct contains. A date contains three numbers, two chars and an int, called day, month, and year respectively. This structure is now a new type, which we can use elsewhere in our program. To get a variable of the date type, we must do the following declaration: Listing A.41 struct date MyDate;
MyDate is now of type struct date. You can now store a day, a month, and a year in MyDate. The “day,” “month,” and “year” are called members of the struct, also known
Team LRN
342
Appendix A: The C Programming Language
as fields. To access them, you put a period (.) after a variable of type date, then the name of the member, like so: Listing A.42 MyDate.day=1; MyDate.month=1; MyDate.year=2000;
Each of these values is stored in the appropriate place in MyDate. You use them just as though they were actual variables. You can make arrays of structs or pointers to structs as shown below. Listing A.43 struct date date_array[10]; struct date *date_ptr;
//an array of 10 dates //a pointer to a date
To access a member of a struct that is declared as an array, you can do the following: Listing A.44 date_array[2].day=2;
To access a member of a struct using a pointer to that struct, you can do one of two things: Listing A.45 (*date_ptr).day=2; date_ptr–>day=2;
Both of these lines do the exact same job. However, the second one (using operator –>) is by far cleaner looking and is more widely used. Within a struct, you can have arrays and pointers as members. You can even have other structs as members of a struct, or pointers to structs as members. See the following struct definition. Listing A.46 struct Person { char FirstName[30]; char LastName[30]; struct date DateOfBirth; int Height; int Weight; struct Person *Mother; struct Person *Father; };
We have two arrays to store the strings containing a person’s first and last names. We have a date to store the person’s date of birth, we have height and weight as ints, and we have pointers to other persons to store the mother and father of a person. You can have a pointer to the same struct as a member of a struct, as I have done here.
Team LRN
Appendix A: The C Programming Language
343
So, we can get pretty fancy with our structs. The main point I want you to get here is that structs are a great mechanism for grouping data together into a cohesive whole. We certainly could store data for a person in a different way than using struct Person, but without the structure, it may as well just be a meaningless jumble of numbers.
Loops We have already spoken about the for loop. There are two other types of loops—while and do while. Neither of these are as often used as for, but they do have their uses, and they are a part of C, so they deserve to be discussed. while A while loop is a loop that is executed zero or more times if a condition is true. Here is an example of a while loop: Listing A.47 int x; int y; x=100; y=0; while(x>y) { y++; }
This loop will continually add 1 until (x>y) is no longer true (i.e., when y==100). If (x>y) was never true, then the loop would not be executed even once. In this case, the loop will be executed 100 times, and afterward, the value of y will be 100. You have to be somewhat careful with while loops. In some circumstances, you get into an infinite while loop without meaning to, especially if you accidentally put a semicolon at the end of the while statement, like so: Listing A.48 while(x>y); //this line will be executed forever, doing nothing, if x>y. {//if x>y, you will never reach this line. y++; }
This is another one of the common mistakes people make (like the using = instead of == mistake). do while The do while loop is kind of like the while loop, except the test for a condition is done at the end of the loop, so a do while loop’s body will be executed at least once. Here’s an example of a do while loop.
Team LRN
344
Appendix A: The C Programming Language
Listing A.49 int x; int y; x=100; y=0; do { y++; } while (x>y);
This loop will be executed a total of 100 times, and afterward, y will be equal to 100. The difference between this loop and the while loop is that were x>y not true at the outset, the while loop would not be executed even once, but the do while loop would be executed exactly once. continue There are times when you want some of the code in a loop to be executed, but not the rest. In these circumstances, you use continue. The continue statement moves immediately to the } of the loop, and another iteration of the loop can start, if the condition running the loop is still true. break break is related to continue in that it stops running the code in a loop. It differs in that after a break, code execution continues after the loop. The break statement effectively exits out of a loop. Nested Loops Another thing you can do with loops is place one loop inside another loop. You can even do this with different types of loops, like putting a while loop inside a for loop, or a for loop inside a do while loop. This is called “nesting” loops. By far the most common type of nested loop is the for loop. Consider the following. Listing A.50 int Board[8][8]; int x; int y; for(x=0;x<8;x++) { for(y=0;y<8;y++) { Board[x][y]=0; } }
Team LRN
Appendix A: The C Programming Language
345
This bit of code clears out a two-dimensional array to contain all zeros. Nested for loops are common when two-dimensional arrays are used. They are also used for sorting values and other uses.
Switches A switch is also called a case statement. It bears some resemblance to a more complicated if statement. For example, if you needed to do different things based on the value of x being 1, 2, 3, or 4, you might use the following code: Listing A.51 if(x==1) { //do } if(x==2) { //do } if(x==3) { //do } if(x==4) { //do }
stuff for when x is 1
stuff for when x is 2
stuff for when x is 3
stuff for when x is 4
This works, of course, but is inefficient. For example, if x==1, then we know that x!=2 and so forth, so why compare them? We might rewrite the above code like so: Listing A.52 if(x==1) { //do stuff for when x is 1 } else { if(x==2) { //do stuff for when x is 2 } else { if(x==3) { //do stuff for when x is 3 } else
Team LRN
346
Appendix A: The C Programming Language
{ if(x==4) { //do stuff for when x is 4 } } } }
This may be slightly more efficient than the four if statements separated, but as you might imagine, this type of thing can quickly become rather ugly. After all, we only have four items here, and we are already most of the way across the page. What if we had ten items? or a hundred? The code would be indented for miles! To our rescue comes the switch statement. The equivalent switch statement for the above code is as follows. Listing A.53 switch(x) { case 1: { //do }break; case 2: { //do }break; case 3: { //do }break; case 4: { //do }break; default: { //do }break; }
stuff for when x is 1
stuff for when x is 2
stuff for when x is 3
stuff for when x is 4
stuff for when x is none of the above
This is rather like a compound if statement. It checks the value of x, and depending on whether it is 1, 2, 3, 4, or something else (default is done whenever none of the other cases apply), the appropriate statement block is called. Notice the use of the break statement after each statement block. These are not strictly necessary, but without a break, the code will continue executing in the next case, and will continue to do so until it hits a break or reaches the end of the switch. This is so that you can have multiple cases for the same block of code, as shown in Listing A.54:
Team LRN
Appendix A: The C Programming Language
347
Listing A.54 switch(x) { case 1: case 2: { }break; case 3: {
//code to run if x is 1 or 2 //does not continue running code
//code to run if x is 3 }break; case 4: { } case default: {
//code to run is x is 4 //no break statement, so the code under default will also be executed
//code to run in default case and if x is 4 } }
A common problem with switch statements is forgetting to use break when you need to. Also, it isn’t required to put the code for a case in braces, but doing so helps you to separate the code in your mind, so I do suggest at least giving it a try.
typedef It doesn’t always make sense to think of things as chars, ints, longs, pointers, or structs. Sometimes it makes more sense to call a particular type by another name. For example, in the struct example, where we made a date struct, we put the month and day into chars, and the year into an int. However, we may want to refer to these types in code as day_t (day type), month_t (month type), and year_t (year type). By doing this, we can later change what a day_t really means without having to rewrite all of the code dealing with the date. This is called type-aliasing. It is done through the use of typedef, as shown here. Listing A.55 typedef char day_t; typedef char month_t; typedef int year_t;
We can now treat day_t, month_t, and year_t as though they were their own types. They aren’t actually new types, however. They are just new names for chars and ints. We could now revise our definition of the date struct as follows. Listing A.56 struct date {
Team LRN
348
Appendix A: The C Programming Language
day_t day; month_t month; year_t year; };
This does not really change the definition of struct date. Day and month are still chars, and year is still an int. However, it is more human readable this way, and we see the intent of day, month, and year more clearly. Seeing the intent more clearly is good programming practice.
Functions We’ve been through a lot of stuff so far—assigning values to variables, testing variables, making structures, and looping. Now, we’re going to talk about functions. Functions are nothing more than subprograms. We talked earlier about the main function, which is executed when your program is run. You can make other functions as well. A function is great for taking a large task and dividing it up into smaller pieces. A good candidate for a function is something that takes more than one line and gets repeated often. Here are a couple of examples of functions. Listing A.57 void func1() { } int func2(int x) { return(x+1); } long func3(int x,int y); { return(x+y); } char* func4(char* str1,char* str2) { strcpy(str1,str2); return(str1); }
There are three parts to a function definition: the return type, the function identifier, and the parameters. The return type can be any type, including pointers and structs. In addition, you can have a return type of void, which means that the function returns no value. The function identifier follows the return type. You can name a function just about anything, but the characters allowed in a function identifier are the same as those allowed in variable names.
Team LRN
Appendix A: The C Programming Language
349
The parameters follow the function identifier and are surrounded by parentheses and separated by commas. You can have zero or more parameters. When a function takes no parameters, you simply put a () right after the function identifier. Alternately, you can put (void) after the function identifier. They both mean the same thing: the function takes no values. Parameters themselves are within a single pair of parentheses; they are declared exactly like variables (in fact, they are variables). If you have more than one parameter, you separate them by commas. After the function declaration is a code block. Whenever this function is called, the code in the code block will be executed. Somewhere in the code block, there will be a return statement, unless the return type is void, in which case you might not have one. A function takes some values (the parameters) and sends back one value (a value of the return type). You “call” a function from within another function. You send to it any values you want, and retrieve whatever value is returned in a variable. Here’s a pretty simple example. Listing A.58 z=Add(x,y);
In this example, we pass the values stored in x and y to the function Add. The value returned by this function is then stored in z. Here is the Add function itself. Listing A.59 int Add(int a,int b) { return(a+b); }
Notice that there is no x, y, or z in the Add function. The parameters are named a and b. When we call this function, the values are “passed” in the parameter list in order, so the value of a becomes the value in x, and the value in b becomes the value in y. The return statement adds a and b together, and send the value back to wherever called it. A return statement also exits out of the function. Nothing after a return statement will be executed. So, the value of a+b is sent back and stored in z. Earlier, we looked at the main function (the function that makes the program go). Now that we have talked about a number of things, let’s take another look at it. Listing A.60 long main(int argc, char* argv[], bool start) { return 0; }
As we can see, the main function returns a long, and has three parameters: argc, argv, and start. Within the code block, we simply return 0 without actually doing anything. One thing that might throw you a bit is the argv declaration. You can see that its type is a character pointer (char*), but the empty brackets ([ ]) after it are a little
Team LRN
350
Appendix A: The C Programming Language
strange. Empty brackets indicate that a variable is an array but of indefinite size. This type of declaration isn’t used often (I avoid it whenever possible because it is ugly). Suffice it to say, this makes argv an array of pointers to chars. The argc parameter tells us how many elements are in the array argv. This is called the “argument count.” An argument is the same thing as a parameter, but in this case is sent from outside the program. The argv parameter is an array of strings, containing all of the arguments. There is always at least one—the name of the program you are running. For example, if you ran MyApp.app, the value of argc would be 1, and argv would point to one string, “MyApp.app”. However, if you had command-line parameters, and it called “MyApp.app MyFile.txt,” argc would be 2, and argv would have two strings, “MyApp.app” and “MyFile.txt.” Cybiko applications rarely use command-line parameters, but they can, and argc and argv are the way to do it. The final parameter is a bool named start. A bool is a typedef for char and is used to contain Boolean values. There are two values defined for bool, FALSE(0) and TRUE(1), although there is nothing to stop you from putting other values into a bool that could be used for a char. Doing this, however, invalidates the use of bools for Boolean evaluations, so don’t do this. The start parameter is supposed to be TRUE if you want the application to start, and FALSE if you do not. I’ve never personally seen a point for this, so I ignore the start parameter. In the main function in Listing A.60, we do nothing except return 0. So, when CyOS runs this application, we get the appropriate values in argc, argv, and start, and since the function immediately returns 0, that is what is sent to CyOS, or whatever the calling program is. You can run a program from another program. When you do this, you can retrieve the return value yourself. This is about the only time when the return value of the main function will be important to you, if ever. Within a function, you can call other functions, and from within those functions, other functions can be called, and so on. Functions are really good for splitting up a program. Function Prototypes Generally, you will want to declare a function before it is used. Usually, you will declare a function at the top of the source file, and put in the function body code block later on. Also, you might want to put the function declaration and the function itself into completely different files. This is called function prototyping. To do this, you put the function header, take away the code block, and put a semicolon after the function header, like so. Listing A.61 int Add(int a, int b);
Team LRN
Appendix A: The C Programming Language
351
This tells us that the Add function is implemented somewhere, but we are declaring it here, so that we can use it. After a function is declared, it can be used by other functions, even if the implementation of it comes later. Scope No, not the mouthwash. In programming, scope has to do with the accessibility of a variable. You can, for example, declare variables within a function. Listing A.62 void f() { int x; int y; x=0; y=1; }
Any variables within a function must be declared at the top of the function, before anything else takes place. These variables exist as long as we are still within the function. After the function returns, they are gone. The same thing goes for parameters, which you can treat just like variables declared in the function body. The only difference between a parameter and a variable declared within a function is that a parameter is given a value when the function is called. These values are said to be “local variables.” Variables that are declared outside of any function are called “global variables.” You can access a global variable anywhere in your program (provided that part of the program can see the declaration for the global). A function cannot see any variables other than its own local variables and globals, so if you are using x and y in one function, and x and y in another function, there is no conflict, since neither function can see or access the variables in any other function. The tricky part comes in when you have a global variable and a local variable with the same name. When this happens, the local variable “overrides” the global variable while in that function, and you cannot access the global variable during this time. You can avoid most of this by giving your globals names that are unlikely to be overridden, like x or y. One way of ensuring this is to put g_ in front of all global variables, such as “int g_x;”. The g_ lets you know that this is a global variable. I personally don’t do this, but it is an option, and is used by many programmers throughout the world with great success.
#define You will often see #define in programs, especially on the Cybiko. #define is called a “preprocessor directive,” which sounds really intimidating. When you have written some source files and are ready to make them into a program, the code goes through several steps. The first step is a preprocessor. The preprocessor handles any preprocessor directives. It sounds like circular logic, I know, but it’s the truth.
Team LRN
352
Appendix A: The C Programming Language
You can use #define several ways. The first way is to just “define” something, like so. Listing A.63 #define DEBUG
Notice that no semicolon follows this line. Preprocessor directives are not code. They are separate from code and do not wind up in the final executable. This use of #define has some great uses, especially with other preprocessor directives. The second use for #define makes constants. This is an example of a constant made with #define. Listing A.64 #define SCREENWIDTH 160
Now, SCREENWIDTH is “defined” to be 160. Anyplace in code after this line that has SCREENWIDTH in it will be modified to replace SCREENWIDTH with 160. Consider the following line: x=SCREENWIDTH/2;
After the preprocessor is done, this line will look like this: x=160/2;
So, why don’t we just put 160 wherever we would put SCREENWIDTH? Sure, we could do that. However, if for some reason we needed to change the value to something else, we would have to hunt down all of these 160s, and replace them with the new value. If we instead were using #define to give us a value of SCREENWIDTH, we only have to change the value there, and the rest of the values will change when we run the code through the preprocessor. The last way to use #define is to make a macro. A macro looks kind of like a function, but is really just for replacing code. Let’s say you want a macro to find the average of two numbers. This is how you would make it. Listing A.65 #define AVG(a,b) ((a)+(b))/2
Now, you could do the following: z=AVG(x,y);
And the preprocessor would convert it into this: z=((x)+(y))/2;
In macro form it looks a lot like a function call. However, it is just a template for code replacement. It replaces a and b with whatever “parameters” you put into the macro. It doesn’t check that these parameters are the correct type. In fact, it is quite easy to make a macro that doesn’t work properly.
Team LRN
Appendix A: The C Programming Language
353
You may have noticed that in the definition for AVG, I placed a and b in their own parentheses, which seems to be using altogether too many parentheses. There is a good reason for this. Let’s say you made a macro to give you the square of a number, like so: #define SQUARE(x) x*x
And then you used this macro to square 3 and store it in z: z=SQUARE(3);
The preprocessor would translate this into z=3*3, which is correct, in this case. Now, let’s say you wanted to square x+y, like so: z=SQUARE(x+y);
This time, the preprocessor spits out the following: z=x+y*x+y;
Because of order of operations, the multiplication is done first, multiplying x*y, then adding x and y to this product. This is not x+y squared. So, the simple application of parentheses in the macro, like so: #define SQUARE(x) (x)*(x)
Will make the code change to: z=(x+y)*(x+y);
And this will give us the proper answer. So, the moral of the story is to get used to putting parentheses around “parameters” of macros.
#ifndef, #ifdef, #else, and #endif There are several more preprocessor directives that I consider to be really useful. These are #ifndef, #ifdef, #else, and #endif. These are conditional preprocessor directives. We can use them to change the meaning of code when certain definitions exist. We can also use them to be sure that code is only included once. For example, the following code will only be included in a program if DEBUG is defined. Listing A.66 #ifdef DEBUG x=0; #endif
Often this is used for debugging code. All of the error trapping and diagnostic code depends on whether DEBUG is defined. After you have thoroughly debugged your code, you remove the definition of DEBUG, and all of the trapping and diagnostic code disappears. Similarly, we can ensure that code is only added once to a project. You usually want to do this when you are using more than one source file, which is almost always.
Team LRN
354
Appendix A: The C Programming Language
You can do the following to ensure that the given code is only included once. Listing A.67 #ifndef INCLUDEONCE #define INCLUDEONCE //code goes here #endif
Anything between the #ifndef and the #endif are included only once, because thereafter, INCLUDEONCE is defined, so this same block would give you nothing. The final directive I want to talk about here is #else. As you might imagine, #else is used when you want to do one thing if there is or is not something defined, and another thing if the condition you are testing for is false. See below. Listing A.68 #ifdef DEBUG //stuff to do if DEBUG is defined #else //stuff to do if DEBUG is not defined #endif
#include The last preprocessor directive I’m going to show is #include. It is of great importance in programming. The #include directive brings the contents of another file into the file you place the #include in. Cybiko C has very little built-in functionality. We’ve essentially gone through all of it prior to this point. Functions that deal with the platform on which you are programming (i.e., the Cybiko), are stored in binary form in libraries, or exist in dynamic libraries on the device itself. This is handy, as you then are freed from rewriting basic functionality. To use these functions, you must include a header file (usually with a .h extension) into your programs. A header file contains all of the declarations and function prototypes to make use of something. Most of the time, the functions in a header file are related in some way, like one header file for bitmaps, another for sounds, and so on. For Cybiko programs, you will need at least the following #include directive. Listing A.69 #include “cywin.h”
Sure, you can have #include directives only for those header files that you are using functions from, but why? The purpose of cywin.h is to include all of the others. Also, when your source is made into a program, anything that isn’t used is left out. Other things you might include are function libraries of your own creation. Let’s say you have a group of useful functions, perhaps in dealing with strings or something like that. When grouping functions, there should always be a “theme” of some kind between them; otherwise you have to hunt through dozens of files for the functions you want. So, let’s say that you made a string library of functions, which we will call
Team LRN
Appendix A: The C Programming Language
355
stringlib. For stringlib, you make two files: a header file and a source file. In the header file (stringlib.h), you have declarations for all of the structs and functions you have in stringlib. Also, you have any #include directives that are needed for stringlib. When making a function library, it is best if you only include those things you need rather than everything on the planet. In the source file (stringlib.c), you implement all of the functions declared in stringlib.h. Also, you #include stringlib.h into stringlib.c. In addition, you put “include guards” or “sentinels” on both of these files. At the top of stringlib.h, you put the following: Listing A.70 #ifndef __STRINGLIB_H__ #define __STRINGLIB_H__
At the bottom of the file, you add an “#endif” line. In stringlib.c, you do a similar thing, except that you use a C rather than an H. This will guarantee that you can #include stringlib.h or stringlib.c more than once without duplicating anything (duplicating things will give you an error). In your main program file (which is normally the name of the app with a .c after it), to make use of stringlib, you only need to put in the following line: #include “stringlib.c”
For this, you have to have stringlib.h and stringlib.c in the same directory or in the path. Now all of the functions in stringlib are available to you. NOTE: This is not the only way to do this, of course. The Cybiko make utility (the program that converts source code into a program) does support separate compilation. However, the makefiles (instructions on how a program is to be built) are somewhat complicated to explain, so I will be #including .c files in other .c files for simplicity. In this way, we only have to have a single makefile that we change one line of when we switch projects.
Summary Well, this has been your crash course in the C language. To be honest, I have only scratched the surface here. This short tutorial cannot possibly do the same job as a several hundred page book on the C language, and that’s about the length of most of them. Hopefully, I’ve given you at least a start on this topic. If you don’t have one, I do suggest getting a book on the topic of the C language. It will fill in the gaps I have left open.
Team LRN
Appendix B
File Format for Pic Files Overview A pic file is any file that contains a four-color bitmap or series of bitmaps for use on the Cybiko. It does not have to have the .pic extension. It does not have to have any extension at all. Most of the time, you will want your bitmaps and bitmap sequences to have a .pic extension, just so you have an easier time finding them among your resources. The most notable exception is Root.ico, which is a pic file but doesn’t use the .pic extension. During this discussion, I will refer to pic files as containing a bitmap sequence, even if there is only one bitmap present. This is not to be confusing or cryptic. A single bitmap in a pic file is just a bitmap sequence containing a sequence of one.
Contents of a Pic File A pic file consists of two parts. First is the file header, which gives us valuable information about the bitmap sequence stored in this file. After the file header, there is a bitmap record for each bitmap in the sequence.
File Header The file header consists of four bytes at the very beginning of the file. From this four-byte header, we can determine whether this is a pic file (first byte), how many bitmaps are in the bitmap sequence (second byte), and the greatest width and height (third and fourth bytes) that can be found in the bitmap sequence. Bytes three and four are not terribly important. Table B.1 shows the header bytes, their range of values, and the meaning of these bytes.
356
Team LRN
Appendix B: File Format for Pic Files
357
Table B.1 Pic file header Offset
Name
Values
Meaning
0
FILEID
2
FileID; must be 2 for a Pic file
1
BMPCNT
1-255
Number of bitmaps in the file
2
MAXWID
1-255
Maximum width found in the bitmap sequence
3
MAXHGT
1-255
Maximum height found in the bitmap sequence
The value of 2 in the first byte indicates that this file is a pic file. If you have a file with this byte equal to 1, you have a monochrome bitmap sequence (i.e., a font), and if you have a 0 in this location, you have a music sequence. No other values are currently being used. Please note that the “Name” for each of these bytes are my own and are not official. They are just to help me remember what they are by name rather than by offset.
Bitmap Record After the file header, there are a number of bitmap records (equal to BMPCNT). A bitmap record is itself divided into two sections, a bitmap record header and the bitmap data itself.
Bitmap Record Header The bitmap record header consists of four bytes, and contains a horizontal offset (first byte), a vertical offset (second byte), a width (third byte), and a height (fourth byte). Table B.2 shows the layout of the bitmap record header. Table B.2 Bitmap record header Offset
Name
Values
Meaning
0
BMPXOF
0-255
Horizontal offset for bitmap
1
BMPYOF
0-255
Vertical offset for bitmap
2
BMPWID
1-255
Width for bitmap
3
BMPHGT
1-255
Height for bitmap
BMPWID and BMPHGT should be fairly self-explanatory. BMPXOF and BMPYOF are less so.
Team LRN
358
Appendix B: File Format for Pic Files
When drawing a bitmap (with Graphics_draw_bitmap or whatever), you specify an x and y position at which to write the bitmap. This location is the upper-left corner of where you want the bitmap drawn. BMPXOF and BMPYOF modify the x and y location, thus allowing entire rows and columns of pixels that might be transparent to be left out of the bitmap record, thereby making the bitmap record smaller and the file smaller as well. This is usually only used for bitmaps that are partially transparent. When drawn onto the screen BMPXOF is added to the x value at which you are drawing the bitmap, and BMPYOF is added to the y value at which you are drawing the bitmap, and the bitmap is drawn there. BMPWID and BMPHGT are the width and height of the bitmap in pixels.
Bitmap Data Finally, we get to the bitmap data itself. The bitmap data is divided into horizontal rows (scan lines). There are BMPHGT scan lines. Each scan line is the same size, and depends on BMPWID. Since we are dealing with four-color images, it takes two bits to store a pixel color. There are eight bits in a byte, so a byte can contain data for up to four pixels at a time. So, for every four pixels in BMPWID, we need 1 byte in the scan line. If we have any extra bits at the end, we need another extra byte for the scanline. There is a formula you can use to determine how many bytes you will need per scan line. It is (BMPWID+3)/4. So, from 1 to 4, one byte is needed per scan line, from 5 to 8, two bytes, from 9 to 12, three bytes, and so on. Within each scan line, you move from the first byte toward the last byte and from left to right (four pixels at a time) as far as the display is concerned. The byte itself, however, is slightly backward. In an individual byte, the first pixel is stored in bits 6 and 7, the second in bits 4 and 5, the third in bits 2 and 3, and the fourth in bits 0 and 1. Within these pairs of bits, 00 means white, 01 means light gray, 10 means dark gray, and 11 means black.
Conversion from Image to Bitmap Record Talking about the scan lines and headers and pixel formats doesn’t really help when it actually comes down to converting from real images to binary data. The best way to learn it is to actually do it, which is what we’re going to do here. Figure B.1 is a simple image that we are going to convert to its binary equivalent.
Team LRN
Appendix B: File Format for Pic Files
359
Figure B.1
X Offset: 0 Y Offset: 0 Width: 10 Height: 10 Transparent Color: White Since we have a transparent color (white), we first need to crop this image if we can. Since the entire image is bordered by white rows and columns of pixels, we can get rid of this, since it won’t be needed when rendering. First, we will chop off the right side, as shown in Figure B.2.
Figure B.2
X Offset: 0 Y Offset: 0 Width: 9 Height: 10 Transparent Color: White
Team LRN
360
Appendix B: File Format for Pic Files
Note that the width has changed to reflect the new size of the image. Next, in Figure B.3, we will take off the bottom row of pixels.
Figure B.3
X Offset: 0 Y Offset: 0 Width: 9 Height: 9 Transparent Color: White The height has been changed to reflect the new size. Now, we will take off the left column of pixels in Figure B.4.
Figure B.4
X Offset: 1 Y Offset: 0 Width: 8 Height: 9 Transparent Color: White This time, both the X offset and the width have changed. Finally, we shall remove the top row of pixels. See Figure B.5.
Team LRN
Appendix B: File Format for Pic Files
Figure B.5
X Offset: 1 Y Offset: 1 Width: 8 Height: 8 Transparent Color: White We can no longer remove entire rows or columns from this image (note that both Y offset and height changed). We now have enough information to put together our bitmap record header. Bitmap Record Header: BMPXOF=1 BMPYOF=1 BMPWID=8 BMPHGT=8 Binary Representation: 01 01 08 08 Now, we can do the job of making the bitmap data itself. First, we change all of the pixel values into numbers (0, 1, 2, or 3), as shown in Figure B.6. 0
3
3
3
3
3
3
0
3
1
1
1
1
1
1
3
3
1
3
1
1
3
1
3
3
1
1
1
1
1
1
3
3
1
3
1
1
3
1
3
3
1
1
3
3
1
1
3
3
1
1
1
1
1
1
3
0
3
3
3
3
3
3
0
Figure B.6
Team LRN
361
362
Appendix B: File Format for Pic Files
Next, we determine the number of bytes needed for each scan line. Since our BMPWID is 8, which is evenly divisible by 4, we need only two bytes per scan line. If we had left the other columns of pixels in this image, we would have needed 3 bytes per scan line to accommodate the extra two pixels. Because we have a transparent color and were able to chop off those two columns of pixels, we saved ourselves 1 byte per scan line, so it will only take two-thirds as much space to store this image. Now, we are going to divide up the numerical data in Figure B.6 into its constituent bytes. See Figure B.7. 0333 3330 3111 1113 3131 1313 3111 1113 3131 1313 3113 3113 3111 1113 0333 3330
Figure B.7
Now the image looks almost nothing like an actual image. We are getting closer to having a binary representation of this image. Next, we convert this representaion (which is in base four) into the hex representation. Two base four digits make a single hex digit, and Table B.3 is a conversion table. Table B.3 Base four to hexadecimal conversion values Base Four
Hexadecimal
00
0
01
1
02
2
03
3
10
4
11
5
12
6
13
7
20
8
21
9
22
A
23
B
30
C
31
D
Team LRN
Appendix B: File Format for Pic Files
Base Four
Hexadecimal
32
E
33
F
363
Using Table B.3, we convert the base four representation in Figure B.7 into the hexadecimal below: 3F FC D5 57 DD 77 D5 57 DD 77 D7 D7 D5 57 3F FC Two of the base four digits make a single hex digit, so for the first number, 0333, you convert the 03 to hex, which is 3, and you convert the 33 to hex, which is F, and put them together to make 3F. Next, string it all together on one line, like so: 3F FC D5 57 DD 77 D5 57 DD 77 D7 D7 D5 57 3F FC Then, place the bitmap record header in front of it. 01 01 08 08 3F FC D5 57 DD 77 D5 57 DD 77 D7 D7 D5 57 3F FC We would do this to any other bitmaps in the sequence. For now, we will pretend there is only one bitmap in this sequence and set up a file header for it. FILEID=2 BMPCNT=1 MAXWID=9 MAXHGT=9 You might be thrown a little by the MAXWID and MAXHGT. To calculate them, you take the maximum width and height of the bitmaps in the sequence. The BMPWID and BMPHGT listed in the bitmap record is only 8, so where does the 9 come from? Well, the MAXWID and MAXHGT are affected by the BMPXOF and BMPYOF, so the “actual” width of a bitmap is BMPXOF+BMPWID, and the “actual” height is BMPYOF+BMPHGT, so each of these in this case are 9. Finally, place the file header in front of the bitmap record, and we’re done: 02 01 09 09 01 01 08 08 3F FC D5 57 DD 77 D5 57 DD 77 D7 D7 D5 57 3F FC This string of numbers and letters is the exact representation of our image as it would appear in a hex editor. In fact, you could type this into a hex editor, save it, and use it as a bitmap in a Cybiko application.
Team LRN
364
Appendix B: File Format for Pic Files
Conversion from Binary Data to Image For my next trick, I’m going to convert from a string of hexadecimal data to an image. For this, I will use the string of hex data that we generated from the image, and we’ll see if we wind up with the same image (kind of a double-check of ourselves). Here’s the hex string, again. 02 01 09 09 01 01 08 08 3F FC D5 57 DD 77 D5 57 DD 77 D7 D7 D5 57 3F FC First, we’ll divide this into file header and bitmap records, like so: 02 01 09 09 01 01 08 08 3F FC D5 57 DD 77 D5 57 DD 77 D7 D7 D5 57 3F FC The file header tells us that this is a four-color image file (the 02), there is one bitmap in the sequence (the 01), and the maximum width and height are both 9 (the 09 and 09). This leaves us with the bitmap record. 01 01 08 08 3F FC D5 57 DD 77 D5 57 DD 77 D7 D7 D5 57 3F FC We will again split this into the bitmap record header and the bitmap data, as shown below: 01 01 08 08 3F FC D5 57 DD 77 D5 57 DD 77 D7 D7 D5 57 3F FC The bitmap record header shows us a BMPXOF of 1, a BMPYOF of 1, a BMPWID of 8, and a BMPHGT of 8, which is correct so far. Next, calculate the number of bytes per scan line (BMPWID+3)/4, which solves to 2, so divide the bitmap data into its scan lines, like so: 3F FC D5 57 DD 77 D5 57 DD 77 D7 D7 D5 57 3F FC Now, using the hex to base four table, change this into the base four representation. 0333 3330 3111 1113 3131 1313 3111 1113 3111 1313 3113 3113
Team LRN
Appendix B: File Format for Pic Files
365
3111 1113 0333 3330 Convert to a table of pixels, shown in Figure B.8. 0
3
3
3
3
3
3
0
3
1
1
1
1
1
1
3
3
1
3
1
1
3
1
3
3
1
1
1
1
1
1
3
3
1
3
1
1
3
1
3
3
1
1
3
3
1
1
3
3
1
1
1
1
1
1
3
0
3
3
3
3
3
3
0
Figure B.8
Since we have a BMPXOF and BMPYOF, we must add a row of pixels at the top and a column of pixels at the left to show how the bitmap will actually be drawn. See Figure B.9. ?
?
?
?
?
?
?
?
?
?
0
3
3
3
3
3
3
0
?
3
1
1
1
1
1
1
3
?
3
1
3
1
1
3
1
3
?
3
1
1
1
1
1
1
3
?
3
1
3
1
1
3
1
3
?
3
1
1
3
3
1
1
3
?
3
1
1
1
1
1
1
3
?
0
3
3
3
3
3
3
0
Figure B.9
The “?” represents whatever value was already there when this picture is rendered. No matter what, this image will not affect those pixels. Does this match our original image? Well, not exactly. Our original image was 10x10, but the rightmost column and bottommost row were transparent, so it doesn’t matter that they aren’t here in the final representation. So, yes, it is a match, as far as usage goes. Either image would be shown the same way, provided we were using white as our transparent color.
Team LRN
366
Appendix B: File Format for Pic Files
Summary In this appendix, I’ve taken you through the four-color bitmap format. While this knowledge is not absolutely essential to programming the Cybiko (indeed, there are utilities for converting from other formats into this one), it is useful to know if you are making applications for, say, Windows, which has to deal with this format.
Team LRN
Appendix C
File Format—Fnt Overview The monochrome bitmap format on the Cybiko (that which I call the “.fnt” format) is actually the same format as the .pic format, with the only difference being the pixel representation. However, the usage of a file containing a monochrome bitmap sequence versus the four-color bitmap sequence is different enough that I feel justified in making a separate appendix for it. There is no standard extension for font files. I use “fnt” to make it easy on myself. You can, of course, use whatever file extension you like. The Cybiko doesn’t care.
Header A font file consists of two parts, a file header followed by a number of bitmap records, just like the pic format. In fact, other than the first byte’s required value, the headers are the same. Table C.1 has the offsets, names, and purposes of the four-byte header. Table C.1 File header Offset
Name
Value
Purpose
0
FILEID
1
File identifier
1
BMPCNT
1-255
Number of bitmaps in the file
2
MAXWID
1-255
Maximum width of the bitmaps in the file
3
MAXHGT
1-255
Maximum height of the bitmaps in the file
So, if the first byte is 1, you have a font file. Otherwise, you don’t. This is an easy check to make.
367
Team LRN
368
Appendix C: File Format—Fnt
Bitmap Records After the header, there are a number of variable length bitmap records. The size of the bitmap record depends on the size of the bitmap. The bitmap record itself is divided into two sections, the bitmap record header and the bitmap data. Table C.2 shows the layout of the bitmap record header (it is identical to the pic bitmap record header). Table C.2 Bitmap record header Offset
Name
Value
Purpose
0
BMPXOF
0-255
Horizontal offset for the bitmap
1
BMPYOF
0-255
Vertical offset for the bitmap
2
BMPWID
1-255
Width of the bitmap in pixels
3
BMPHGT
1-255
Height of the bitmap in pixels
BMPXOF and BMPYOF are added to the x and y coordinate when you call Graphics_ draw_bitmap. For fonts, this is done internally by the function you are using to output text. For fixed-width fonts, BMPWID is unimportant. For proportional fonts, it becomes important, as we shall see later.
Bitmap Data The remainder of the bitmap record contains the data for the bitmap itself. Since we are dealing with monochrome images, each bit represents 1 pixel in the bitmap, which means we can store a row of eight pixels in a single byte. The data is organized into horizontal rows of pixels (scan lines). The number of bytes per scan line is equal to (BMPWID+7)/8, so for a bitmap from 1 to 8 pixels wide, the scan line is 1 byte, for 9 to 16 pixels, 2 bytes, for 17 to 24 pixels, 3 bytes, and so on. The bytes in a scan line are read from left to right, but the bytes themselves are read backward. Bit 7 represents the first pixel in the row of eight pixels, and bit 0 represents the last pixel.
Encoding a Monochrome Bitmap I can talk to you all day about this, but it’s no good unless we do an example. So, consider the monochrome bitmap in Figure C.1.
Team LRN
Appendix C: File Format—Fnt
369
Figure C.1 Brought to you by the letter “B”
Width: 7 Height: 9 We don’t need to crop this image at all. The extra column of white pixels on the right is there because we are going to be using this as a proportional font, and that space is needed to properly draw this letter next to other letters. If we were to remove that column of pixels, the B would be right next to whatever letter came next without any space, making both letters unreadable. If we were creating a fixed-width font, we could get rid of it. Since we have to do no cropping, we can create our bitmap record header: 00 00 07 09 Note that I’m using black as the “foreground” color. This may not actually wind up being shown in black. It may wind up being in gray, but that doesn’t matter, because that stuff is done in the program, not when the font is being created. For all of the black dots, we place a 1. For all of the white dots, we place a 0, as shown in Figure C.2. 1
1
1
1
1
0
0
1
1
0
0
1
1
0
1
1
0
0
1
1
0
1
1
0
0
1
1
0
1
1
1
1
1
0
0
1
1
0
0
1
1
0
1
1
0
0
1
1
0
1
1
0
0
1
1
0
1
1
1
1
1
0
0
Figure C.2
Team LRN
370
Appendix C: File Format—Fnt
From here, we copy the numbers into strings of bits, like so: 1111100 1100110 1100110 1100110 1111100 1100110 1100110 1100110 1111100 This bitmap is only seven pixels across, but a byte is eight bits across, so we need an extra bit. For this, we add on however many bits we need to make the scan line a multiple of eight bits across. Since we already have seven, we only need one extra bit, so we put it in like so: 11111000 11001100 11001100 11001100 11111000 11001100 11001100 11001100 11111000 And voilá, eight bits. Next we move the representation from binary (its current state) to hexadecimal, like so: F8 CC CC CC F8 CC CC CC F8 Finally, string it all together, and put the bitmap record header in front of it. 00 00 07 09 F8 CC CC CC F8 CC CC CC F8 And that’s our letter B, encoded for a font file. You would do this to all of the other letters in your font file, put them all together, put a file header on it, and you’ve got a font file.
Team LRN
Appendix C: File Format—Fnt
371
Summary The font file format is just a modified version of the pic file format. It’s not hard. Always remember that the letters in a font file start with the space character (character 32), and proceed from there in ASCII character order. If you skip a letter, just put in a single white dot (the bitmap record for a single white dot is 00 00 01 01 00). For the most part, you can get away with using only characters 32 through 127, unless you need the foreign characters.
Team LRN
Appendix D
Cybiko Graphics Quick Reference TGraph, Graphics, and DisplayGraphics Class
Constructor
Instantiable
TGraph
No
No
Graphics
Yes
Yes
DisplayGraphics
No
No*
* DisplayGraphics is not directly instantiable. A single instance of DisplayGraphics always exists.
Constructors and Destructors The following functions deal with constructing Graphics objects. Since neither TGraph nor DisplayGraphics can be instantiated, they have no constructors. Function
Class
Purpose
ctor
Graphics
Constructs a Graphics object with no associated bitmap
ctor_Ex
Graphics
Constructs a Graphics object with an associated bitmap
dtor
Graphics
Destroys a Graphics object, optionally freeing the memory associated with it
372
Team LRN
Appendix D: Cybiko Graphics Quick Reference
373
Constructor and Destructor Function Details ctor function: struct Graphics* Graphics_ctor (struct Graphics *ptr_gfx)
Parameter
Type
Meaning
ptr_gfx
struct Graphics*
Pointer to the Graphics object which you are constructing
Action taken: The object is constructed with no associated bitmap object. Return value: Pointer to the newly constructed object NOTE: Use Graphics_ctor to construct a “floating graphics context”, i.e., a Graphics object that will be used with many bitmaps by calls to Graphics_set_bitmap.
ctor_Ex function: struct Graphics* Graphics_ctor_Ex (struct Graphics *ptr_gfx, struct Bitmap *bitmap)
Parameter
Type
Meaning
ptr_gfx
struct Graphics*
Pointer to the Graphics object which you are constructing
bitmap
struct Bitmap*
Pointer to a bitmap object to associate with this object
Action taken: The object is constructed and associated with the supplied bitmap. Return value: Pointer to the newly constructed object dtor function: void
Graphics_dtor (struct Graphics *ptr_gfx, int memory_flag)
Parameter
Type
Meaning
ptr_gfx
struct Graphics*
Pointer to the Graphics object that you are destroying
memory_flag
int
Flag to either keep or release memory
Action taken: Object is destroyed, and the memory associated with it is optionally released. Return value: None
Team LRN
374
Appendix D: Cybiko Graphics Quick Reference
Drawing Primitives The following functions deal with drawing primitives, such as pixels, lines, and rectangles. Function
Class*
Purpose
set_pixel
TGraph
Sets an individual pixel
get_pixel
TGraph
Retrieves the color of an individual pixel
draw_hline
TGraph
Draws a horizontal line segment
draw_vline
TGraph
Draws a vertical line segment
draw_line
TGraph
Draws a line segment between two points
draw_rect
TGraph
Draws a frame around a rectangle
draw_rect_Ex
TGraph
Draws a frame around a rectangle
fill_rect
TGraph
Draws a filled-in rectangle
fill_rect_Ex
TGraph
Draws a filled-in rectangle
fill_screen
TGraph
Fills the screen with a solid color
* Functions available to TGraph are also available to Graphics and DisplayGraphics. Functions available to Graphics are also available to DisplayGraphics but not to TGraph. Functions available to DisplayGraphics are available only to DisplayGraphics.
Drawing Primitives Function Details The following are generic prototypes for the functions that draw primitives. They are in this format: return_type X_function_name(parameter list)
Replace the X with the name of the class with which you are dealing—TGraph, Graphics, or DisplayGraphics. NOTE: Notice that there are no primitive functions for drawing circles, ellipses, arcs, or any other curved primitives.
Pixel Functions set_pixel function: void void void
TGraph_set_pixel (struct TGraph *ptr_graph, int x, int y, color_t color); Graphics_set_pixel (struct Graphics *ptr_graph, int x, int y, color_t color); DisplayGraphics_set_pixel (struct DisplayGraphics *ptr_graph, int x, int y, color_t color);
Team LRN
Appendix D: Cybiko Graphics Quick Reference
375
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object on which you would like to plot a pixel
x
int
Horizontal coordinate
y
int
Vertical coordinate
color
color_t
Desired color of pixel
* X=TGraph, Graphics, or DisplayGraphics
Action taken: If the coordinate (x,y) is within the object’s boundaries and clipping area, a pixel of the color specified by the color parameter will be drawn. Return value: None get_pixel function: color_t color_t color_t
TGraph_get_pixel (struct TGraph *ptr_graph, int x, int y); Graphics_get_pixel (struct Graphics *ptr_graph, int x, int y); DisplayGraphics_get_pixel (struct DisplayGraphics *ptr_graph, int x, int y);
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object from which you would like to read a pixel
x
int
Horizontal coordinate
y
int
Vertical coordinate
* X=TGraph, Graphics, or DisplayGraphics
Action taken: If it exists, the color for the pixel existing at (x,y) will be retrieved. Return value: The color of the pixel specified by x and y
Line Functions Horizontal line function: void void void
TGraph_draw_hline (struct TGraph *ptr_graph, int x, int y, int dx); Graphics_draw_hline (struct Graphics *ptr_graph, int x, int y, int dx); DisplayGraphics_draw_hline (struct DisplayGraphics *ptr_graph, int x, int y, int dx);
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object on which you would like to draw a horizontal line
x
int
Horizontal coordinate
Team LRN
376
Appendix D: Cybiko Graphics Quick Reference
Parameter
Type
Meaning
y
int
Vertical coordinate
dx
int
Horizontal length of the line
* X=TGraph, Graphics, or DisplayGraphics
Action taken: A horizontal line in the current color is drawn starting at (x,y) and extending dx pixels to the right. Return value: None Vertical line function: void void void
TGraph_draw_vline (struct TGraph *ptr_graph, int x, int y, int dy); Graphics_draw_vline (struct Graphics *ptr_graph, int x, int y, int dy); DisplayGraphics_draw_vline (struct DisplayGraphics *ptr_graph, int x, int y, int dy);
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object on which you would like to draw a vertical line
x
int
Horizontal coordinate
y
int
Vertical coordinate
dy
int
Vertical length of this line
* X=TGraph, Graphics, or DisplayGraphics
Action taken: A vertical line in the current color is drawn starting at (x,y) and extending down by dy pixels. Return value: None Free-style line function: void void void
TGraph_draw_line (struct X *ptr_graph, int x1, int y1, int x2, int y2); Graphics_draw_line (struct X *ptr_graph, int x1, int y1, int x2, int y2); DisplayGraphics_draw_line (struct X *ptr_graph, int x1, int y1, int x2, int y2);
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object on which you would like to draw a line segment
x1
int
First horizontal coordinate
y1
int
First vertical coordinate
x2
int
Second horizontal coordinate
y2
int
Second vertical coordinate
* X=TGraph, Graphics, or DisplayGraphics
Team LRN
Appendix D: Cybiko Graphics Quick Reference
377
Action taken: A line segment is drawn between (x1,y1) and (x2,y2) in the current color. Return value: None
Framed Rectangle Functions draw_rect function: void void void
TGraph_draw_rect (struct TGraph *ptr_graph, int x, int y, int w, int h); Graphics_draw_rect (struct Graphics *ptr_graph, int x, int y, int w, int h); DisplayGraphics_draw_rect (struct DisplayGraphics *ptr_graph, int x, int y, int w, int h);
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object on which you would like to draw a framed rectangle
x
int
Left edge of the rectangle
y
int
Top edge of the rectangle
w
int
Width of the rectangle
h
int
Height of the rectangle
* X=TGraph, Graphics, or DisplayGraphics
Action taken: A frame is drawn around a rectangle with the upper-left corner located at (x,y) with a width of w and a height of h. The rectangle is drawn using the current color. Return value: None draw_rect_Ex function: void void void
TGraph_draw_rect_Ex (struct TGraph *ptr_graph, struct rect_t *rc); Graphics_draw_rect_Ex (struct Graphics *ptr_graph, struct rect_t *rc); DisplayGraphics_draw_rect_Ex (struct DisplayGraphics *ptr_graph, struct rect_t *rc);
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object on which you would like to draw a framed rectangle
rc
struct rect_t*
Pointer to a rect_t describing the desired rectangle
* X=TGraph, Graphics, or DisplayGraphics
Action taken: A frame around a rectangle described by rc is drawn. The rectangle is drawn using the current color. Return Value: None
Team LRN
378
Appendix D: Cybiko Graphics Quick Reference
Filled Rectangle Functions fill_rect function: void void void
TGraph_fill_rect (struct TGraph *ptr_graph, int x, int y, int w, int h); Graphics_fill_rect (struct Graphics *ptr_graph, int x, int y, int w, int h); DisplayGraphics_fill_rect (struct DisplayGraphics *ptr_graph, int x, int y, int w, int h);
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object on which you would like to draw a filled-in rectangle
x
int
Left edge of the rectangle
y
int
Top edge of the rectangle
w
int
Width of the rectangle
h
int
Height of the rectangle
* X=TGraph, Graphics, or DisplayGraphics
Action taken: A filled-in rectangle in the current color is drawn with the upper-left coordinate of (x,y) and a width of w and a height of h. Return value: None fill_rect_Ex function: void void void
TGraph_fill_rect_Ex (struct TGraph *ptr_graph, struct rect_t *rc); Graphics_fill_rect_Ex (struct Graphics *ptr_graph, struct rect_t *rc); DisplayGraphics_fill_rect_Ex (struct DisplayGraphics *ptr_graph, struct rect_t *rc);
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object on which you would like to draw a filled-in rectangle
rc
struct rect_t*
Pointer to a rect_t that describes the desired rectangle
* X=TGraph, Graphics, or DisplayGraphics
Action taken: A filled-in rectangle in the current color is drawn. The rectangle is described by rc. Return value: None
Fill Screen Function void void void
TGraph_fill_screen (struct TGraph *ptr_graph, color_t fc); Graphics_fill_screen (struct Graphics *ptr_graph, color_t fc); DisplayGraphics_fill_screen (struct DisplayGraphics *ptr_graph, color_t fc);
Team LRN
Appendix D: Cybiko Graphics Quick Reference
379
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object on which you would like to fill the screen
fc
color_t
The desired color
* X=TGraph, Graphics, or DisplayGraphics
Action taken: The entire canvas reference by ptr_graph is filled with the color specified by fc. Return value: None NOTE: Here the term “filling the screen” refers to the entire Bitmap object that the Graphics or DisplayGraphics object is operating.
Context Attributes The following list of functions deals with context attributes of graphical objects. Function
Class*
Purpose
set_color
TGraph
Sets the current color for drawing primitives
get_color
TGraph
Retrieves the current color used for drawing primitives
set_draw_mode
TGraph
Sets the draw mode
get_draw_mode
TGraph
Retrieves the draw mode
set_bkcolor
TGraph
Sets the background color
get_bkcolor**
TGraph
Retrieves the background color
set_clip
TGraph
Sets the clipping area
set_clip_Ex
TGraph
Sets the clipping area
get_clip
TGraph
Retrieves the clipping area
set_font
Graphics
Sets the current font
get_font
Graphics
Retrieves the current font
set_bitmap
Graphics
Sets the current bitmap
get_bitmap
Graphics
Retrieves the current bitmap
set_page
DisplayGraphics
Sets the current page
get_page
DisplayGraphics
Retrieves the current page
* Functions available to TGraph are also available to Graphics and DisplayGraphics. Functions available to Graphics are also available to DisplayGraphics. Functions available to DisplayGraphics are available only to DisplayGraphics.
Team LRN
380
Appendix D: Cybiko Graphics Quick Reference
** There is no official get_bkcolor function in the SDK. I took it upon myself to write one.
Context Function Details The following are generic prototypes for the functions that draw primitives. They are in this format: return_type X_function_name(parameter list)
Replace the X with the name of the class with which you are dealing—TGraph, Graphics, or DisplayGraphics.
Current Color Functions NOTE: The current color of a graphics context affects all of the primitive drawing functions except for set_pixel, get_pixel, and fill_screen. In addition, it can affect bitmap drawing functions if the bitmap being drawn is monochrome. Finally, all text is drawn using the current color.
set_color function: void void void
TGraph_set_color (struct TGraph *ptr_graph, color_t color) Graphics_set_color (struct Graphics *ptr_graph, color_t color) DisplayGraphics_set_color (struct DisplayGraphics *ptr_graph, color_t color)
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object on which you would like to change the current color
color
color_t
New value for the current color
* X=TGraph, Graphics, or DisplayGraphics
Action taken: A new value for the current color is assigned. Return value: None get_color function: color_t color_t color_t
TGraph_get_color (struct TGraph *ptr_graph) Graphics_get_color (struct Graphics *ptr_graph) DisplayGraphics_get_color (struct DisplayGraphics *ptr_graph)
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object from which you would like to retrieve the current color
* X=TGraph, Graphics, or DisplayGraphics
Team LRN
Appendix D: Cybiko Graphics Quick Reference
381
Action taken: The value of the current color is retrieved. Return value: The value of the current color
Drawing Mode Functions set_draw_mode function: void void void
TGraph_set_draw_mode (struct TGraph *ptr_graph, drawmode_t mode) Graphics_set_draw_mode (struct Graphics *ptr_graph, drawmode_t mode) DisplayGraphics_set_draw_mode (struct DisplayGraphics *ptr_graph, drawmode_t mode)
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object on which you would like to set the draw mode
mode
drawmode_t
A new value for the draw mode
* X=TGraph, Graphics, or DisplayGraphics
Action taken: A new value for the drawing mode is assigned. Return value: None get_draw_mode function: drawmode_t drawmode_t drawmode_t
TGraph_get_draw_mode (struct TGraph *ptr_graph) Graphics_get_draw_mode (struct Graphics *ptr_graph) DisplayGraphics_get_draw_mode (struct DisplayGraphics *ptr_graph)
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object from which you would like to retrieve the draw mode
* X=TGraph, Graphics, or DisplayGraphics
Action taken: The value of the drawing mode is retrieved. Return value: The value of the drawing mode
Background Color Function set_bkcolor function: void void void
TGraph_set_bkcolor (struct TGraph *ptr_graph, color_t color) Graphics_set_bkcolor (struct Graphics *ptr_graph, color_t color) DisplayGraphics_set_bkcolor (struct DisplayGraphics *ptr_graph, color_t color)
Team LRN
382
Appendix D: Cybiko Graphics Quick Reference
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object on which you would like to set the background color
color
color_t
New value for the background color
* X=TGraph, Graphics, or DisplayGraphics
Action taken: A new value is set for the object’s background color. Return value: None NOTE: There is no function to retrieve the value of the background color, and Cybiko, Inc., has no plans to make one in the future. I have written one, but in future versions of the SDK, there is no guarantee that it will work.
get_bkcolor function: DISCLAIMER: This is a function I wrote after hacking the Graphics structure. I make no guarantees that it will always work. color_t TGraph_get_bkcolor(struct TGraph* ptr_graph) { return(((color_t*)(ptr_graph))[1]); } #define Graphics_get_bkcolor TGraph_get_bkcolor #define DisplayGraphics_get_bkcolor TGraph_get_bkcolor
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object for which you would like to retrieve the background color
* X=TGraph, Graphics, or DisplayGraphics
Action taken: The object’s background color is retrieved. Return value: The object’s background color
Clipping Area Functions set_clip function: void void void
TGraph_set_clip (struct TGraph *ptr_graph, int x, int y, int width, int height) Graphics_set_clip (struct Graphics *ptr_graph, int x, int y, int width, int height) DisplayGraphics_set_clip (struct DisplayGraphics *ptr_graph, int x, int y, int width, int height)
Team LRN
Appendix D: Cybiko Graphics Quick Reference
383
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object on which you are setting the clipping area
x
int
Left coordinate for clipping area
y
int
Top coordinate for clipping area
w
int
Width of the clipping area
h
int
Height of the clipping area
* X=TGraph, Graphics, or DisplayGraphics
Action taken: The clipping area is set to a rectangle with the upper-left corner of (x,y) with a width of w and a height of h. Return value: None set_clip_Ex function: void void void
TGraph_set_clip_Ex (struct TGraph *ptr_graph, struct rect_t *rc) Graphics_set_clip_Ex (struct Graphics *ptr_graph, struct rect_t *rc) DisplayGraphics_set_clip_Ex (struct DisplayGraphics *ptr_graph, struct rect_t *rc)
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object on which you are setting the clipping area
rc
struct rect_t*
Pointer to a rect_t describing the desired clipping area
* X=TGraph, Graphics, or DisplayGraphics
Action taken: The clipping area is set to the rectangle described by rc. Return value: None get_clip function: void void void
TGraph_get_clip (struct TGraph *ptr_graph, struct rect_t *rc) Graphics_get_clip (struct Graphics *ptr_graph, struct rect_t *rc) DisplayGraphics_get_clip (struct DisplayGraphics *ptr_graph, struct rect_t *rc)
Parameter
Type
Meaning
ptr_graph
struct X*
Pointer to the object from which you are retrieving the clipping area
rc
struct rect_t*
Pointer to a rect_t to be filled in with the clipping area’s attributes
* X=TGraph, Graphics, or DisplayGraphics
Team LRN
384
Appendix D: Cybiko Graphics Quick Reference
Action taken: The rect_t pointed to by rc will be filled in with the object’s current clipping area’s attributes. Return value: None
Current Font Functions set_font function: void void
Graphics_set_font (struct Graphics *ptr_gfx, struct Font *font) DisplayGraphics_set_font (struct DisplayGraphics *ptr_gfx, struct Font *font)
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to the object on which you would like to set the current font
font
struct Font*
Pointer to a new font to use
* X=Graphics or DisplayGraphics
Action taken: The current font for the object is set to the new font. Return value: None get_font function: struct Font* struct Font*
Graphics_get_font (struct Graphics *ptr_gfx) DisplayGraphics_get_font (struct DisplayGraphics *ptr_gfx)
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to the object from which you would like to retrieve the current font
* X=Graphics or DisplayGraphics
Action taken: The current font used with this object is retrieved. Return value: The current font
Current Bitmap Functions set_bitmap function: void void
Graphics_set_bitmap (struct Graphics *ptr_gfx, struct Bitmap *bmp) DisplayGraphics_set_bitmap (struct DisplayGraphics *ptr_gfx, struct Bitmap *bmp)
Team LRN
Appendix D: Cybiko Graphics Quick Reference
385
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to the object on which you would like to set the bitmap
bmp
struct Bitmap*
A pointer to a new bitmap to associate with this object
* X=Graphics or DisplayGraphics
Action taken: A new bitmap is associated with the object. Return value: None NOTE:
Do not use set_bitmap with the DisplayGraphics object.
get_bitmap function: struct Bitmap* struct Bitmap*
Graphics_get_bitmap (struct Graphics *ptr_gfx) DisplayGraphics_get_bitmap (struct DisplayGraphics *ptr_gfx)
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to the object from which you would like to retrieve the bitmap
* X=Graphics or DisplayGraphics
Action taken: The bitmap currently associated with this object is retrieved. Return value: The current bitmap
Page Functions set_page function: void
DisplayGraphics_set_page (struct DisplayGraphics *ptr_gfx, int page)
Parameter
Type
Meaning
ptr_gfx
struct DisplayGraphics*
Pointer to the DisplayGraphics object on which you would like to set the current page
page
int
The number of the page to set as the current page (0 or 1)
Action taken: The current page is set to the page number indicated. Return value: None
Team LRN
386
Appendix D: Cybiko Graphics Quick Reference
get_page function: int
DisplayGraphics_get_page (struct DisplayGraphics *ptr_gfx)
Parameter
Type
Meaning
ptr_gfx
struct DisplayGraphics*
Pointer to the DisplayGraphics object from which you would like to retrieve the current page
Action taken: The current page number is retrieved. Return value: The current page
Writing Bitmaps, Characters, and Text These are functions dealing with the drawing of bitmaps, characters, and text. Function
Class*
Purpose
draw_bitmap
Graphics
Draws a bitmap
draw_char
Graphics
Draws a character
draw_text
Graphics
Draws a string of characters
draw_text_Ex
Graphics
Draws a string of characters
* Functions available to TGraph are also available to Graphics and DisplayGraphics. Functions available to Graphics are also available to DisplayGraphics. Functions available to DisplayGraphics are available only to DisplayGraphics.
Bitmap, Character, and Text Drawing Function Details The following are generic prototypes for the functions that draw bitmaps, characters, and strings. They are in this format: return_type X_function_name(parameter list)
Replace the X with the name of the class with which you are dealing—TGraph, Graphics, or DisplayGraphics.
Bitmap and Character Drawing Functions draw_bitmap function: void void
Graphics_draw_bitmap (struct Graphics *ptr_gfx, struct Bitmap *bmp, int left, int top, short fm) DisplayGraphics_draw_bitmap (struct DisplayGraphics *ptr_gfx, struct Bitmap *bmp, int left, int top, short fm)
Team LRN
Appendix D: Cybiko Graphics Quick Reference
387
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to the object on which you would like to draw a bitmap
bmp
struct Bitmap*
Pointer to a bitmap to render
left
int
Left coordinate for the bitmap’s left edge
top
int
Top coordinate for the bitmap’s top edge
fm
short
Bitmap drawing flags
* X=Graphics or DisplayGraphics
Action taken: The bitmap is drawn at the specified location with the current drawing mode applied and the bitmap drawing flags in force. This is affected by the current drawing mode and possibly current background color, depending on the current drawing mode. Return value: None draw_char function: int int
Graphics_draw_char (struct Graphics *ptr_gfx, int x, int y, char fc) DisplayGraphics_draw_char (struct DisplayGraphics *ptr_gfx, int x, int y, char fc)
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to the object on which you would like to draw a character
x
int
Horizontal coordinate
y
int
Vertical coordinate
fc
char
Character to be drawn
* X=Graphics or DisplayGraphics
Action taken: Character fc is drawn at (x,y) in the current color. Uses current font. Return value: Horizontal coordinate of the next pixel after the character
String Drawing Functions draw_text function: void void
Graphics_draw_text (struct Graphics *ptr_gfx, char *text, int left, int top) DisplayGraphics_draw_text (struct DisplayGraphics *ptr_gfx, char *text, int left, int top)
Team LRN
388
Appendix D: Cybiko Graphics Quick Reference
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to the object on which you would like to draw a string
text
char*
The character string to be written
left
int
The horizontal coordinate of the left of the string
top
int
The vertical coordinate of the string
* X=Graphics or DisplayGraphics
Action taken: A string of characters is drawn in the current color starting at (left,top). Uses current color and current font. Return value: None draw_text_Ex function: void void
Graphics_draw_text_Ex (struct Graphics *ptr_gfx, char *str, int left, int top, int len) DisplayGraphics_draw_text_Ex (struct DisplayGraphics *ptr_gfx, char *str, int left, int top, int len)
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to the object on which you would like to draw a string
str
char*
String to be written
left
int
Horizontal coordinate of the left of the string
top
int
Vertical coordinate of the top of the string
len
int
Maximum pixel width for the string
* X=Graphics or DisplayGraphics
Action taken: A string is drawn in the current color starting at (left,top), but no wider than len pixels. Uses current font. Return value: None
Text Metrics NOTE: These text metric functions mirror those in the Font class. They are ever-soslightly less efficient than using the Font version of these functions, since they first call Graphics_get_font on the graphics context, then pass the request along to the Font object. The extra function calls can add up if you are doing a lot of stuff that requires text metrics, and you might just be better off calling Graphics_get_font once, then doing the operations on the Font object itself.
Team LRN
Appendix D: Cybiko Graphics Quick Reference
389
The following are functions dealing with text metrics. Function
Class*
Purpose
get_char_height
Graphics
Retrieves the height of the character set
get_char_width
Graphics
Retrieves the width of a given character
string_width
Graphics
Calculates the width of a string
string_width_Ex
Graphics
Calculates the width of a string
* Functions available to TGraph are also available to Graphics and DisplayGraphics. Functions available to Graphics are also available to DisplayGraphics. Functions available to DisplayGraphics are available only to DisplayGraphics
Text Metrics Function Details The following are generic prototypes for the functions that deal with text metrics. They are in this format: return_type X_function_name(parameter list)
Replace the X with the name of the class with which you are dealing—TGraph, Graphics, or DisplayGraphics.
Character Size Functions get_char_height function: int int
Graphics_get_char_height (struct Graphics *ptr_gfx) DisplayGraphics_get_char_height (struct DisplayGraphics *ptr_gfx)
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to the object from which you would like to retrieve a character’s width
* X=Graphics or DisplayGraphics
Action taken: The character set’s height is retrieved. Uses current font. Return value: The height of the character set get_char_width function: int int
Graphics_get_char_width (struct Graphics *ptr_gfx, char chr) DisplayGraphics_get_char_width (struct DisplayGraphics *ptr_gfx, char chr)
Team LRN
390
Appendix D: Cybiko Graphics Quick Reference
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to the object from which you would like to retrieve a character set’s height
chr
char
Character for which to retrieve the width
* X=Graphics or DisplayGraphics
Action taken: The width of the character chr is retrieved. Uses current font. Return value: The width of character chr
String Size Functions string_width function: int int
Graphics_string_width (struct Graphics *ptr_gfx, char *str) DisplayGraphics_string_width (struct DisplayGraphics *ptr_gfx, char *str)
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to the object from which you would like a string’s pixel width calculated
str
char*
String for which to retrieve the width
* X=Graphics or DisplayGraphics
Action taken: The width in pixels for the given string is calculated. Uses current font. Return value: The calculated width of the string string_width_Ex function: int int
Graphics_string_width_Ex (struct Graphics *ptr_gfx, char *str, int len) DisplayGraphics_string_width_Ex (struct DisplayGraphics *ptr_gfx, char *str, int len)
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to object from which you would like a string’s pixel width calculated
str
char*
A string to calculate the width of
len
int
Number of characters in the string you wish to calculate for
* X=Graphics or DisplayGraphics
Action taken: Uses current font. Determines the width of the first len characters of the string str. Return value: The calculated width in pixels
Team LRN
Appendix D: Cybiko Graphics Quick Reference
391
Page Operations The following are functions dealing with page operations. Function
Class*
Purpose
page_copy
DisplayGraphics
Copies a section of one page to another
page_copy_Ex
DisplayGraphics
Copies a section of one page to another
show
DisplayGraphics
Shows the current page
show_Ex
DisplayGraphics
Shows a specific page
* Functions available to TGraph are also available to Graphics and DisplayGraphics. Functions available to Graphics are also available to DisplayGraphics. Functions available to DisplayGraphics are available only to DisplayGraphics.
Page Operation Function Details The following are generic prototypes for the functions that deal with page operations. They are in this format: return_type X_function_name(parameter list)
Replace the X with the name of the class with which you are dealing—TGraph, Graphics, or DisplayGraphics.
Page Copying Functions page_copy function: void
DisplayGraphics_page_copy (struct DisplayGraphics *ptr_gfx, int from_page, int to_page, int x, int y, int w, int h)
Parameter
Type
Meaning
ptr_gfx
struct DisplayGraphics*
Pointer to the object on which you would like to copy pages
from_page
int
Source page for the copy operation
to_page
int
Destination page for the copy operation
x
int
Left coordinate of the update area
y
int
Top coordinate of the update area
w
int
Width of the update area
h
int
Height of the update area
Team LRN
392
Appendix D: Cybiko Graphics Quick Reference
Action taken: The region with the upper-left corner of (x,y) with a width of w and a height of h are copied from the source page to the destination page. Return value: None page_copy_Ex function: void DisplayGraphics_page_copy_Ex (struct DisplayGraphics *ptr_gfx, int from_page, int to_page, struct rect_t *rc)
Parameter
Type
Meaning
ptr_gfx
struct DisplayGraphics*
Pointer to the object on which you would like to copy pages
from_page
int
Source page
to_page
int
Destination page
rc
struct rect_t*
Pointer to a rect_t describing update area
Action taken: The area described by rc is copied from the source page to the destination page. Return value: None
Show Functions show function: void DisplayGraphics_show (struct DisplayGraphics *ptr_gfx)
Parameter
Type
Meaning
ptr_gfx
struct DisplayGraphics*
Pointer to the object on which the operation is being done
Action taken: The current page is shown on the display. Return value: None show_Ex function: void DisplayGraphics_show_Ex (struct DisplayGraphics *ptr_gfx, int page)
Parameter
Type
Meaning
ptr_gfx
struct DisplayGraphics*
Pointer to the object that you would like to have shown
page
int
Page to be shown
Team LRN
Appendix D: Cybiko Graphics Quick Reference
393
Action taken: The specified page is shown on the display. Return value: None
Miscellaneous Functions The following are miscellaneous functions that don’t fit in elsewhere. Function
Class*
Purpose
put_background
TGraph
Copies the background to a graphics context from a memory buffer
scroll
TGraph
Scrolls a graphics context
get_buf_addr
TGraph
Retrieves a pointer to the memory buffer associated with the graphics context
get_bytes_total
TGraph
Retrieves the number of bytes in the memory buffer associated with the graphics context
* Functions available to TGraph are also available to Graphics and DisplayGraphics. Functions available to Graphics are also available to DisplayGraphics. Functions available to DisplayGraphics are available only to DisplayGraphics.
Miscellaneous Function Details The following are generic prototypes for the miscellaneous functions. They are in this format: return_type X_function_name(parameter list)
Replace the X with the name of the class with which you are dealing—TGraph, Graphics, or DisplayGraphics. put_background function: void void void
TGraph_put_background (struct TGraph *ptr_graph, char *ptr_background) Graphics_put_background (struct Graphics *ptr_graph, char *ptr_background) DisplayGraphics_put_background (struct DisplayGraphics *ptr_graph, char *ptr_background)
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to the object on which you are putting a background
ptr_background
char*
Pointer to raw data to copy to the background
* X=TGraph, Graphics, or DisplayGraphics
Team LRN
394
Appendix D: Cybiko Graphics Quick Reference
Action taken: The raw data pointed to by ptr_background is copied over to the object’s memory block. Return value: None scroll function: void TGraph_scroll (struct TGraph *ptr_graph, int left, int top, int width, int height, int dx, int dy) void Graphics_scroll (struct Graphics *ptr_graph, int left, int top, int width, int height, int dx, int dy) void DisplayGraphics_scroll (struct DisplayGraphics *ptr_graph, int left, int top, int width, int height, int dx, int dy)
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to object that you wish to scroll
left
int
Left horizontal coordinate to be scrolled
top
int
Top vertical coordinate to be scrolled
width
int
Width of the scrolling area
height
int
Height of the scrolling area
dx
int
Horizontal offset for scrolling
dy
int
Vertical offset for scrolling
* X=TGraph, Graphics, or DisplayGraphics
Action taken: The area defined by the upper-left corner of (left,top) with the specified width and height are scrolled by dx and dy on their respective axes. Return value: None get_buf_addr function: char* char* char*
TGraph_get_buf_addr (struct TGraph *ptr_graph) Graphics_get_buf_addr (struct Graphics *ptr_graph) DisplayGraphics_get_buf_addr (struct DisplayGraphics *ptr_graph)
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to the object from which you would like to retrieve the memory buffer
* X=TGraph, Graphics, or DisplayGraphics
Action taken: A pointer to the object’s memory block is retrieved. Return value: Pointer to memory block
Team LRN
Appendix D: Cybiko Graphics Quick Reference
395
get_bytes_total function: int int int
TGraph_get_bytes_total (struct TGraph *ptr_graph) Graphics_get_bytes_total (struct Graphics *ptr_graph) DisplayGraphics_get_bytes_total (struct DisplayGraphics *ptr_graph)
Parameter
Type
Meaning
ptr_gfx
struct X*
Pointer to the object from which you would like to retrieve the size of the memory buffer
* X=TGraph, Graphics, or DisplayGraphics
Action taken: The size of the object’s memory block is retrieved. Return value: The size of the object’s memory block
Other Functions Dealing with Drawing Graphics The following functions are not associated with any particular class. Function
Class
Purpose
draw_roundrect
None
Draws a rounded rectangle on the screen
draw_bar
None
Draws a bar on the screen
rect
None
Draws a framed rectangle on the screen
Function Details draw_roundrect function: void
draw_roundrect (struct Graphics *ptr_graphics, struct rect_t *ptr_rectangle, int style, char *sz_caption)
Parameter
Type
Meaning
ptr_graphics
struct Graphics*
Pointer to the object onto which the rounded rectangle is to be drawn
style
int
Style of the rounded rectangle
sz_caption
char*
Caption for the rounded rectangle
prt_rectangle
struct rect_t*
Pointer to a rect_t that describes the rounded rectangle
Action taken: A rounded rectangle with the specified caption and specified style is drawn to the specified Graphics or DisplayGraphics object. Return value: None
Team LRN
396
Appendix D: Cybiko Graphics Quick Reference
NOTE:
ptr_graphics can also be a DisplayGraphics pointer.
draw_bar function: void
draw_bar (int x, int y, int width, int height, color_t color, bool xor)
Parameter
Type
Meaning
x
int
Left coordinate of the bar
y
int
Top coordinate of the bar
width
int
Width of the bar
height
int
Height of the bar
color
color_t
Color of the bar
xor
bool
XOR flag
Action taken: A bar (filled-in rectangle) is drawn with the upper-left corner of (x,y) with the specified width and height in the specified color. If the XOR flag is TRUE, this bar will be drawn to the screen with a bitwise XOR operation. Return value: None rect function: void
rect (int x, int y, int width, int height, color_t color)
Parameter
Type
Meaning
x
int
Left coordinate of the rectangle
y
int
Top coordinate of the rectangle
width
int
Width of the rectangle
height
int
Height of the rectangle
color
color_t
Color of the rectangle
Action taken: A rectangle is drawn on the screen with the upper-left coordinate of (x,y) and the specified width and height in the specified color. Return value: None
Team LRN
Appendix D: Cybiko Graphics Quick Reference
397
Types and Constants Associated with TGraph, Graphics, and DisplayGraphics Objects color_t Values for color_t: Constant
Value
Meaning
CLR_WHITE
0
White
CLR_LTGRAY
85
Light gray
CLR_DKGRAY
171
Dark gray
CLR_BLACK
255
Black
Functions that use color_t: set_pixel, get_pixel, fill_screen, set_color, get_color, set_bkcolor, draw_bar, rect
drawmode_t Values for drawmode_t: Constant
Value
Meaning
DM_PUT
0
Copy image as is
DM_OR
1
Copy image, but use current background color as transparent
DM_XOR
2
Combine source and destination pixels with bitwise XOR
Functions using drawmode_t: set_draw_mode, get_draw_mode
rect_t Members of rect_t: Member
Type
Meaning
x
short
Left edge of the rectangle
y
short
Top edge of the rectangle
w
short
Width of the rectangle
h
short
Height of the rectangle
Team LRN
398
Appendix D: Cybiko Graphics Quick Reference
Functions dealing with rect_t: Function
Class
Purpose
rect_set
rect_t
Set the values of a rect_t’s members
rect_and
rect_t
Find the intersection of two rectangles
rect_or
rect_t
Find the union of two rectangles
in_rect
rect_t
Determine if a point lies within a rectangle
rect_t Function Details rect_set function: void
rect_set (struct rect_t *ptr_rect, int x, int y, int width, int height)
Parameter
Type
Meaning
ptr_rect
struct rect_t*
Pointer to the rect_t of which we are setting the values
x
int
x value for rect_t
y
int
y value for rect_t
width
int
w value for rect_t
height
int
h value for rect_t
Action taken: Assigns the given values to the rect_t’s members. Return value: None rect_and function: bool
rect_and (struct rect_t *ptr_rectangle, struct rect_t *ptr_rectangle_1, struct rect_t *ptr_rectangle_2)
Parameter
Type
Meaning
ptr_rectangle
struct rect_t*
Destination rectangle
ptr_rectangle_1
struct rect_t*
Source rectangle #1
ptr_rectangle_2
struct rect_t*
Source rectangle #2
Action taken: The intersection (overlapping area) of source rectangles #1 and #2 is calculated, if it exists, and the result is placed in the destination rectangle. The intersection is where the two source rectangles overlap. Return value: TRUE if an intersection exists; FALSE if one does not
Team LRN
Appendix D: Cybiko Graphics Quick Reference
399
rect_or function: bool
rect_or (struct rect_t *ptr_rectangle, struct rect_t *ptr_rectangle_1, struct rect_t *ptr_rectangle_2)
Parameter
Type
Meaning
ptr_rectangle
struct rect_t*
Destination rectangle
ptr_rectangle_1
struct rect_t*
Source rectangle #1
ptr_rectangle_2
struct rect_t*
Source rectangle #2
Action taken: The union of source rectangles #1 and #2 is calculated, if it exists, and the result is placed in the destination rectangle. The union is the smallest rectangle that can contain both source rectangles. Return value: TRUE if the union exists; FALSE if it does not in_rect function: bool
in_rect (int x, int y, struct rect_t *ptr_rectangle)
Parameter
Type
Meaning
x
int
Horizontal coordinate
y
int
Vertical coordinate
ptr_rectangle
struct rect_t
Rectangle
Action taken: Checks whether or not point (x,y) is within the rectangle pointed to by ptr_rectangle. Return value: TRUE if the point is within the rectangle; FALSE if it is not
Other Constants Screen Constants Values for screen constants: Constant
Value
Meaning
SCREEN_WIDTH
160
Pixel width of the screen
SCREEN_HEIGHT
100
Pixel height of the screen
Functions using these constants: None
Team LRN
400
Appendix D: Cybiko Graphics Quick Reference
Rounded Rectangle Styles Values for rounded rectangle styles: Constant
Value
Meaning
RR_NONE
0
No special style
RR_ARROWUP
1
Draw the upward scrolling arrow in caption bar
RR_ARROWDOWN
2
Draw the downward scrolling arrow in caption bar
RR_ROUND
4
Draw a rounded rectangle
RR_ERASE
8
Erase the rounded rectangle
Function using these values: draw_roundrect (the style parameter)
Team LRN
Appendix E
Cybiko Bitmap Quick Reference Bitmap, BitmapSequence, and Font Class
Base Class
Instantiable
Bitmap
N/A
Yes
BitmapSequence
N/A
Yes
Font
BitmapSequence
Yes
Constructors and Destructors These are the constructors and destructors for Bitmap, BitmapSequence, and Font. Function
Class(es)
Purpose
ctor
Bitmap
Constructs an empty bitmap
ctor_Ex1
Bitmap
Loads in a bitmap resource
ctor_Ex2
Bitmap
Creates a blank bitmap of a given size
ctor_Ex3
Bitmap
Creates a copy of a bitmap
ctor
BitmapSequence/Font
Creates an empty BitmapSequence or Font
ctor_Ex
BitmapSequence/Font
Loads in a BitmapSequence or Font from a resource
dtor
All
Destroys the object
401
Team LRN
402
Appendix E: Cybiko Bitmap Quick Reference
Constructor and Destructor Details The following are the details for the use of Bitmap, BitmapSequence, and Font’s constructors and destructors. When X appears in the name of the function or as a type for a parameter, replace it with the class with which you are dealing.
Bitmap Constructors ctor function: struct Bitmap*
Bitmap_ctor (struct Bitmap *ptr_bitmap)
Parameter
Type
Meaning
ptr_bitmap
struct Bitmap*
Pointer to the object you wish to construct
Action taken: A blank, empty bitmap is created. Return value: The newly constructed object’s pointer ctor_Ex1 function: struct Bitmap*
Bitmap_ctor_Ex1 (struct Bitmap *ptr_bitmap, char *filename)
Parameter
Type
Meaning
ptr_bitmap
struct Bitmap*
Pointer to the object you wish to construct
filename
char*
Name of the resource you would like to load
Action taken: Bitmap is constructed and loaded with the data found in the specified resource. Return value: The newly constructed object’s pointer ctor_Ex2 function: struct Bitmap*
Bitmap_ctor_Ex2 (struct Bitmap *ptr_bitmap, int width, int height, int bpp)
Parameter
Type
Meaning
ptr_bitmap
struct Bitmap*
Pointer to the object you wish to construct
width
int
Width in pixels of the desired bitmap
height
int
Height in pixels of the desired bitmap
bpp
int
Bits per pixel of the desired bitmap (1 or 2)
Action taken: A new, blank bitmap is created with the specified width, height, and bits per pixel.
Team LRN