Silver Challenge solution


#1

Here is my proposal for the silver challenge.

At first, I thought that the solution had to be somewhere in the endTouches: method of TouchDrawView.m, because that’s the moment when the completed line is added to the NSMutableArray completeLines.
But no, the color is defined in the context, and the context is created in the drawRect: method.

So my idea was:

  1. calculate the angle between
  • an horizontal line beginning at the starting point and going till (starting point y, ending point x),
  • and the actual line defined by the touch
    (thanks trigonometry)
  1. convert this angle in radians in a value between 0 and 1
  2. use this float between 0 and 1 to define RGB values and create a random color
  3. assign this color to the line (to ensure that the color of the line does ot change once drawn)
  4. use this color to draw the line.

[code]- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 10.0);
CGContextSetLineCap(context, kCGLineCapRound);

// Draw complete lines in black

// [[UIColor blackColor] set];
for (Line *line in completeLines) {

    if (![line colorValue]) {

        // SILVER CHALLENGE
        // Color changes according to the angle of the line
        float dx = line.end.x - line.begin.x;
        NSLog(@"Delta x: %f", dx);
        float dy = line.end.y - line.begin.y;
        NSLog(@"Delta y: %f", dy);
        float angle_rad = atan2(dy , dx);
        NSLog(@"Angle in radians: %f", angle_rad);
        // The angle in radians in that case goes from -pi to +pi
        // Get the absolute of this angle, and divide it by pi
        // to get a number between 0 and 1
        float colorComponent = fabsf(angle_rad) / M_PI;
        NSLog(@"Color component value: %f", colorComponent);
        
        CGFloat R = colorComponent * (rand()%101/100.0);
        CGFloat G = colorComponent * (rand()%101/100.0);
        CGFloat B = colorComponent * (rand()%101/100.0);
        CGFloat a = 1;
        [line setColorValue:[UIColor colorWithRed:R green:G blue:B alpha:a]];
    }
    [[line colorValue]set];
    
    CGContextMoveToPoint(context, [line begin].x, [line begin].y);
    CGContextAddLineToPoint(context, [line end].x, [line end].y);
    CGContextStrokePath(context);
}    

// Draw lines in process in red
[[UIColor redColor]set];
for (NSValue *v in linesInProcess) {
    Line *line = [linesInProcess objectForKey:v];
    CGContextMoveToPoint(context, [line begin].x, [line begin].y);
    CGContextAddLineToPoint(context, [line end].x, [line end].y);
    CGContextStrokePath(context);
}

}
[/code]

I set a colorValue parameter for the line, in order to be able to save it too :

Line.h:

[code]
#import <Foundation/Foundation.h>

@interface Line : NSObject

@property (nonatomic) CGPoint begin;
@property (nonatomic) CGPoint end;
@property (nonatomic) UIColor *colorValue;

@end[/code]

Line.m:

[code]#import “Line.h”

@implementation Line

@synthesize begin, end;
@synthesize colorValue;

// BRONZE Challenge
// Archive complete lines

  • (void)encodeWithCoder:(NSCoder *)aCoder
    {
    [aCoder encodeFloat:begin.x forKey:@“begin.x”];
    [aCoder encodeFloat:begin.y forKey:@“begin.y”];
    [aCoder encodeFloat:end.x forKey:@“end.x”];
    [aCoder encodeFloat:end.y forKey:@“end.y”];
    [aCoder encodeObject:colorValue forKey:@“colorValue”];

}

  • (id)initWithCoder:(NSCoder *)aDecoder
    {
    self = [super init];
    if (self) {
    [self setBegin:CGPointMake([aDecoder decodeFloatForKey:@“begin.x”], [aDecoder decodeFloatForKey:@“begin.y”])];
    [self setEnd:CGPointMake([aDecoder decodeFloatForKey:@“end.x”], [aDecoder decodeFloatForKey:@“end.y”])];
    [self setColorValue:[aDecoder decodeObjectForKey:@“colorValue”]];
    }
    return self;
    }

@end[/code]


#2

Hi Freddy. You beat me to the solution by a few minutes. :laughing:

My solution is a bit simpler and used the colorWithHue method to pick the colors and constrains the angles into just 1/4 of the 360’s possible. Here’s my solution which only affects the drawRect: method within TouchDrawView.m. A fun challenge.

-(void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 5.0);
    CGContextSetLineCap(context, kCGLineCapRound);
    
    for (Line *line in completeLines) {
        // Ch 19 Silver challenge
        // Draw completed lines in a color indicative of the angle of the line
        float delta_x = [line end].x - [line begin].x;
        float delta_y = [line end].y - [line begin].y;
        float hue = fabsf(atanf(delta_y / delta_x)) / ((2.0 * M_PI) / 4.0);
        //NSLog(@"Hue: %.4f ", hue);

        [[UIColor colorWithHue:hue saturation:1.0 brightness:1.0 alpha:1.0] set];
        
        CGContextMoveToPoint(context, [line begin].x, [line begin].y);
        CGContextAddLineToPoint(context, [line end].x, [line end].y);
        CGContextStrokePath(context);
    }
    
    // Draw lines in process in red.
    [[UIColor redColor] set];
    for (NSValue *v in linesInProcess) {
        Line *line = [linesInProcess objectForKey:v];
        CGContextMoveToPoint(context, [line begin].x, [line begin].y);
        CGContextAddLineToPoint(context, [line end].x, [line end].y);
        CGContextStrokePath(context);
    }   
}

#3

That’s smart ! I’m gonna copy your code !!

Thanks
Fred


#4

here’s mine! yet another approach.

i don’t have a strong geometric background, so dealing in radians is quite cumbersome for me… instead i hacked together something simpler - looking for more of a unique but reliable random color for each angle. :slight_smile:

also i broke out a getLineColor: method so I could easily add this to the linesInProgress loop as well - because watching your color change as you draw the line is REALLY COOL :smiley:

[color=#BF0000]drawRect: method[/color] (you can ignore the counters and so on - just there for my own debugging)

[code]- (void)drawRect:(CGRect)rect {

// Set the context (required for Quartz drawing stuff)
CGContextRef context = UIGraphicsGetCurrentContext();

// Try to load completedLines, and if non-null then render - if null then 
// Set line width and style here (all in the context)
CGContextSetLineWidth(context, 10.0);
CGContextSetLineCap(context, kCGLineCapRound);

//int i = 1;

// Apply the following logic to all lines in the completeLines array
for (Line *line in completeLines) {
    
    //=NSLog(@"Line: %d",i);
    
    // Get & set line color
    UIColor *color = [self getLineColor:line];
    [color set];
    
    // Set the start point for the line
    CGContextMoveToPoint(context, [line begin].x, [line begin].y);
    
    // Set the path for the line itself (but don't draw it yet)
    CGContextAddLineToPoint(context, [line end].x, [line end].y);
    
    // Draw the line!
    CGContextStrokePath(context);
    
    //i++;
}

for (NSValue *v in linesInProcess) {
    Line *line = [linesInProcess objectForKey:v];
    
    // Get & set line color
    UIColor *color = [self getLineColor:line];
    [color set];
    
    CGContextMoveToPoint(context, [line begin].x, [line begin].y);
    CGContextAddLineToPoint(context, [line end].x, [line end].y);
    CGContextStrokePath(context);
}

}
[/code]

[color=#BF0000]getLineColor:[/color] (this is where the magic happens)

[code]- (UIColor *)getLineColor:(Line *)line {

if (line) {
    // Get a unique but reliable value for each coordinate
    double yValue = [line begin].y - [line end].y;
    double xValue = [line begin].x - [line end].x;        
    //NSLog(@"xValue: %.3f, yValue: %.3f",xValue,yValue);
    
    // Find the ratio of that value to the total draw area
    double xRatio = xValue / (self.bounds.size.width / 2);
    double yRatio = yValue / (self.bounds.size.height / 2);
    //NSLog(@"xRatio: %.3f, yRatio: %.3f",xRatio,yRatio);
    
    // ..but the range of the above is -1 to 1!
    // so add 1 and divide by 2 to get a value between 0 and 1.
    double xColor = (xRatio + 1) / 2;
    double yColor = (yRatio + 1) / 2;
    //NSLog(@"xColor: %.3f, yColor: %.3f",xColor,yColor);
    
    // set the color :)
    UIColor *color = [UIColor colorWithHue:xColor saturation:yColor brightness:1 alpha:1];
    
    return color;
} else {
    return [UIColor blackColor];
}

}
[/code]


#5

I have a slightly different solution because I interpreted the challenge to mean that different angles get a specific color. So I started off by creating an enumeration to represent a few angles. I also created a method that will return me the slope of a line in degrees (to make things easier I took the absolute of the degrees and returned that). Inside of drawRect: I decide if the slope is within the given ranges and choose a specific color for the line.

TouchDrawView.h

    enum Angles
    {
        horizontal = 0
        , shallow = 15
        , middle = 45
        , steep = 75
        , vertical = 90
    };

- (int)slopeOfLineInDegrees:(Line *)line;

TouchDrawView.m

- (int)slopeOfLineInDegrees:(Line *)line
{
    //Formula for the slope of a line is: (y2 - y1) / (x2 - x1)
    
    //Handle vertical. We don't want to divide by zero, otherwise we rip a black hole in the Universe
    if (([line end].x - [line begin].x) == 0.0) {
        return 90;
    }

    //Calculate the slope
    double yChange = [line end].y - [line begin].y;
    double xChange = [line end].x - [line begin].x;
    double slope = yChange / xChange;
    
    //Get the radians
    double radians = atan(slope);       //atan2(yChange, xChange);
    
    //Convert to degrees
    double degrees = radians * 180 / M_PI;
    
    
    return abs(degrees);
}

TouchDrawView.m (inside of drawRect:)

    //Draw complete lines in black
    //[[UIColor blackColor] set];
    
    for (Line *line in [[LineStore sharedStore] completedLines])
    {
        //Silver Challenge: the angle at which a line is drawn dictates its color
        int slopeOfLine = [self slopeOfLineInDegrees:line];
        NSLog(@"Slope of Completed Line is: %d", slopeOfLine);
        
        if (slopeOfLine == horizontal) {
            [[UIColor blackColor] set];
        }
        else if (slopeOfLine >= horizontal && slopeOfLine <= shallow) {
            [[UIColor greenColor] set];
        }
        else if (slopeOfLine > shallow && slopeOfLine < middle)
        {
            [[UIColor blueColor] set];
        }
        else if (slopeOfLine == middle)
        {
            [[UIColor yellowColor] set];
        }
        else if (slopeOfLine > middle && slopeOfLine < steep)
        {
            [[UIColor orangeColor] set];
        }
        else if (slopeOfLine == steep)
        {
            [[UIColor purpleColor] set];
        }
        else if (slopeOfLine > steep && slopeOfLine < vertical)
        {
            [[UIColor brownColor] set];
        }
        else if (slopeOfLine == vertical)
        {
            [[UIColor magentaColor] set];
        }
        else
        {
            [[UIColor blackColor] set];
        }
        
        CGContextMoveToPoint(context, [line begin].x, [line begin].y);
        CGContextAddLineToPoint(context, [line end].x, [line end].y);
        CGContextStrokePath(context);
    }   //end for

#6

Nice answer Freddy!

Just FYI, you could encode using “encodeCGPoint:forKey” instead of saving each X and Y for begin and end…

Thanks again for that nice solution!

-(void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeCGPoint:begin forKey:@"begin"]; [aCoder encodeCGPoint:end forKey:@"end"]; }


#7

My solution is similar to the ones already posted here, but it is way cooler because I have a picture. :smiley:

viewtopic.php?f=231&t=6911#p19175

[quote=“Plastic”]You should use UIColor’s -colorWithHue instead. That small change will make it look way cooler (if you are one of those colorful kinds anyway).

colorWithHue documentation: xcdoc://ios/documentation/UIKit/Referen … rence.html

I’m using arctan to find the angle (in rads) and then converting to degrees (* 180 / M_PI ), as described in the following article. The if operation after it is just to fix the value in the 3rd and 4th quadrants, as they would return negative in the arctan equation. hyperphysics.phy-astr.gsu.edu/hb … ig.html#c2

My function inside DrawRect:

[code] for (Line *line in [[LineStore sharedStore] allLines]) {
CGContextMoveToPoint(context, [line begin].x, [line begin].y);
CGContextAddLineToPoint(context, [line end].x, [line end].y);

    float angle = atan2f( ([line begin].y - [line end].y), ([line end].x - [line begin].x) ) * 180/M_PI;
    
    if ((([line end].x - [line begin].x) < 0 && ([line begin].y - [line end].y) < 0) ||
        (([line end].x - [line begin].x) >= 0 && ([line begin].y - [line end].y) < 0) ) {
        angle = angle + 360;
    }

    [[UIColor colorWithHue:angle/360 saturation:1.0 brightness:1.0 alpha:1.0] setStroke];        
    CGContextStrokePath(context);
}

[/code][/quote]