cs193p – Assignment #3 Task #6

Please note, this blog entry is from a previous course. You might want to check out the current one.

Set cards must have the “standard” Set look and feel (i.e. 1, 2 or 3 squiggles, diamonds or ovals that are solid, striped or unfilled, and are either green, red or purple). You must draw these using Core Graphics and/or UIBezierPath. You may not use images or attributed strings. Use the PlayingCardView from the in-class demo to draw your Playing Card game cards.

Adjust the drawRect: method of the set-card view to call a new method to draw the symbal (which is actually not really necessary but makes the code a little bit more sructurized):

- (void)drawRect:(CGRect)rect
{
    ...
    [self drawSymbols];  
}

Define two constants for the symbols (meaning they are the same for all symbols) for how far multiple symbols should be apart and for the line width:

#define SYMBOL_OFFSET 0.2;
#define SYMBOL_LINE_WIDTH 0.02;

The symbols-drawing method defines first the the symbol color (using another helper method) and then draws the different number of symbols at appropriate positions:

- (void)drawSymbols
{
    [[self uiColor] setStroke];
    CGPoint point = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
    if (self.number == 1) {
        [self drawSymbolAtPoint:point];
        return;
    }
    CGFloat dx = self.bounds.size.width * SYMBOL_OFFSET;    
    if (self.number == 2) {
        [self drawSymbolAtPoint:CGPointMake(point.x - dx / 2, point.y)];
        [self drawSymbolAtPoint:CGPointMake(point.x + dx / 2, point.y)];
        return;
    }
    if (self.number == 3) {
        [self drawSymbolAtPoint:point];
        [self drawSymbolAtPoint:CGPointMake(point.x - dx, point.y)];
        [self drawSymbolAtPoint:CGPointMake(point.x + dx, point.y)];
        return;
    }
}

The color helper method returns the color of the symbol as UIColor:

- (UIColor *)uiColor
{
    if ([self.color isEqualToString:@"red"]) return [UIColor redColor];
    if ([self.color isEqualToString:@"green"]) return [UIColor greenColor];
    if ([self.color isEqualToString:@"purple"]) return [UIColor purpleColor];
    return nil;
}

The following helper method chooses which drawing function (according to the actual symbol) should be used:

- (void)drawSymbolAtPoint:(CGPoint)point
{
    if ([self.symbol isEqualToString:@"oval"]) 
        [self drawOvalAtPoint:point];
    else if ([self.symbol isEqualToString:@"squiggle"]) 
        [self drawSquiggleAtPoint:point];
    else if ([self.symbol isEqualToString:@"diamond"]) 
        [self drawDiamondAtPoint:point];
}

The oval consists of rounded rectangle (where the corners are extremely rounded):

#define OVAL_WIDTH 0.12
#define OVAL_HEIGHT 0.4
- (void)drawOvalAtPoint:(CGPoint)point;
{
    CGFloat dx = self.bounds.size.width * OVAL_WIDTH / 2;
    CGFloat dy = self.bounds.size.height * OVAL_HEIGHT / 2;
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(point.x - dx, point.y - dy, 2 * dx, 2 * dy)
                                                    cornerRadius:dx];
    path.lineWidth = self.bounds.size.width * SYMBOL_LINE_WIDTH;
    [self shadePath:path];
    [path stroke];
}

The squiggle is a combination of curves with one and two control points:

#define SQUIGGLE_WIDTH 0.12
#define SQUIGGLE_HEIGHT 0.3
#define SQUIGGLE_FACTOR 0.8
- (void)drawSquiggleAtPoint:(CGPoint)point;
{
    CGFloat dx = self.bounds.size.width * SQUIGGLE_WIDTH / 2;
    CGFloat dy = self.bounds.size.height * SQUIGGLE_HEIGHT / 2;
    CGFloat dsqx = dx * SQUIGGLE_FACTOR;
    CGFloat dsqy = dy * SQUIGGLE_FACTOR;
    UIBezierPath *path = [[UIBezierPath alloc] init];
    [path moveToPoint:CGPointMake(point.x - dx, point.y - dy)];
    [path addQuadCurveToPoint:CGPointMake(point.x + dx, point.y - dy)
                 controlPoint:CGPointMake(point.x - dsqx, point.y - dy - dsqy)];
    [path addCurveToPoint:CGPointMake(point.x + dx, point.y + dy)
            controlPoint1:CGPointMake(point.x + dx + dsqx, point.y - dy + dsqy)
            controlPoint2:CGPointMake(point.x + dx - dsqx, point.y + dy - dsqy)];
    [path addQuadCurveToPoint:CGPointMake(point.x - dx, point.y + dy)
                 controlPoint:CGPointMake(point.x + dsqx, point.y + dy + dsqy)];
    [path addCurveToPoint:CGPointMake(point.x - dx, point.y - dy)
            controlPoint1:CGPointMake(point.x - dx - dsqx, point.y + dy - dsqy)
            controlPoint2:CGPointMake(point.x - dx + dsqx, point.y - dy + dsqy)];
    path.lineWidth = self.bounds.size.width * SYMBOL_LINE_WIDTH;
    [self shadePath:path];
    [path stroke];
}

The diamond consists of four straight lines:

#define DIAMOND_WIDTH 0.15
#define DIAMOND_HEIGHT 0.4
- (void)drawDiamondAtPoint:(CGPoint)point;
{
    CGFloat dx = self.bounds.size.width * DIAMOND_WIDTH / 2;
    CGFloat dy = self.bounds.size.height * DIAMOND_HEIGHT / 2;
    UIBezierPath *path = [[UIBezierPath alloc] init];
    [path moveToPoint:CGPointMake(point.x, point.y - dy)];
    [path addLineToPoint:CGPointMake(point.x + dx, point.y)];
    [path addLineToPoint:CGPointMake(point.x, point.y + dy)];
    [path addLineToPoint:CGPointMake(point.x - dx, point.y)];
    [path closePath];
    path.lineWidth = self.bounds.size.width * SYMBOL_LINE_WIDTH;
    [self shadePath:path];
    [path stroke];
}

All three drawing methods use another helper method to realize the shading of the symbols. A solid symbol is just filled completely. An open symbol is not filled at all.

For the striped symbol one could imaging different solutions, e.g. using patterns. Here we just draw lines manually and clip them to the symbol. Note in doing so it is essential to use individual graphic contexts:

#define STRIPES_OFFSET 0.06
#define STRIPES_ANGLE 5
- (void)shadePath:(UIBezierPath *)path
{
    if ([self.shading isEqualToString:@"solid"]) {
        [[self uiColor] setFill];
        [path fill];
    } else if ([self.shading isEqualToString:@"striped"]) {
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSaveGState(context);
        [path addClip];
        UIBezierPath *stripes = [[UIBezierPath alloc] init];
        CGPoint start = self.bounds.origin;
        CGPoint end = start;
        CGFloat dy = self.bounds.size.height * STRIPES_OFFSET;
        end.x += self.bounds.size.width;
        start.y += dy * STRIPES_ANGLE;
        for (int i = 0; i < 1 / STRIPES_OFFSET; i++) {
            [stripes moveToPoint:start];
            [stripes addLineToPoint:end];
            start.y += dy;
            end.y += dy;
        }
        stripes.lineWidth = self.bounds.size.width / 2 * SYMBOL_LINE_WIDTH;
        [stripes stroke];
        CGContextRestoreGState(UIGraphicsGetCurrentContext());        
    } else if ([self.shading isEqualToString:@"open"]) {
        [[UIColor clearColor] setFill];
    }
}

… and there was another bug in the last task of this assignment. When removing a card using its index you get a problem if you start from the first card, because the indexes of the following cards will be rearranged. This can be prevented by using another removing method using the card object instead of its index, or just start from the last card:

for (int i = self.game.numberOfCards - 1; i >= 0; i--)

The complete code is available on github.

Facebooktwittergoogle_plusredditpinterestlinkedintumblrmailFacebooktwittergoogle_plusredditpinterestlinkedintumblrmail

Flattr this!

2 thoughts on “cs193p – Assignment #3 Task #6”

  1. This task isn’t supposed to be solved using a custom view and overriding drawRect. UIView methods weren’t still explained at this point of this course. Rather it was supposed to be solved using some attributed string attribute (i.e.: some font having striped circles, triangles and squares).

    1. Please have a look at the description of the task, especially where it says “You must draw these using Core Graphics and/or UIBezierPath. You may not use images or attributed strings. Use the PlayingCardView from the in-class demo to draw your Playing Card game cards.” Could it be you were looking for the current course 2013/14? This post is about the 2013 course.

Leave a Reply

Your email address will not be published. Required fields are marked *