Team LRN
Game Developer’s Guide to Cybiko™
Ernest Pazera
Wordware Publishing, Inc.
Team LRN
Library of Congress C...
114 downloads
904 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 $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 $ 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_y0) dude_x—; return(TRUE); }break; case KEY_RIGHT: //move right { if(dude_xmsgid=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;yptr_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;indexm_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 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 &, |, ^, ~, . 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, >. 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 (>>) 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
*
Right shift
*
>>>
Unsigned right shift
*
=
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 . 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 = to be sort of a “not less than” operator and y)) z=1;//same as testing x=0 && x= or 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;GameB[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 0y. {//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