summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorStarla Insigna <starla4444@gmail.com>2013-08-28 19:26:14 -0400
committerStarla Insigna <starla4444@gmail.com>2013-08-28 19:26:14 -0400
commit0fb6c56cd42e0558717a264442436af02441abfb (patch)
tree9e89baaff192329d7fea7f655289001e305814cf
parentefb1489a544847c9009a7ea158162396b00f53c1 (diff)
downloadmazeoflife-0fb6c56cd42e0558717a264442436af02441abfb.tar.gz
mazeoflife-0fb6c56cd42e0558717a264442436af02441abfb.tar.bz2
mazeoflife-0fb6c56cd42e0558717a264442436af02441abfb.zip
Added ability to add highscores to local highscore list
-rw-r--r--gamestate.cpp45
-rw-r--r--hslist.cpp320
-rw-r--r--hslist.h38
-rw-r--r--resources/hlo_paartm.bmpbin76938 -> 76856 bytes
-rw-r--r--resources/hlo_passartm.bmpbin76938 -> 76856 bytes
5 files changed, 385 insertions, 18 deletions
diff --git a/gamestate.cpp b/gamestate.cpp index 323cac5..812f26c 100644 --- a/gamestate.cpp +++ b/gamestate.cpp
@@ -4,6 +4,8 @@
4#include "mazeoflife.h" 4#include "mazeoflife.h"
5#include "highscore.h" 5#include "highscore.h"
6#include "titlestate.h" 6#include "titlestate.h"
7#include <fstream>
8#include "hslist.h"
7 9
8class GameBoard { 10class GameBoard {
9 public: 11 public:
@@ -214,7 +216,48 @@ State* PlayGameState::operator() (SDL_Renderer* renderer)
214 } 216 }
215 217
216 case SDLK_ESCAPE: 218 case SDLK_ESCAPE:
217 return new TitleState(); 219 std::ifstream exists(getDataFile());
220 if (exists)
221 {
222 FILE* hslist = fopen(getDataFile(), "r");
223 int scores;
224 Highscore* h;
225
226 fscanf(hslist, "%d%*c", &scores);
227
228 if (scores < 10)
229 {
230 fclose(hslist);
231
232 return new EnterHighscoreState(level);
233 } else {
234 for (int i=0; i<scores; i++)
235 {
236 int namelen;
237 char namelens[4];
238 char* name = (char*) calloc(25, sizeof(char));
239 int score;
240
241 fscanf(hslist, "%d", &namelen);
242 sprintf(namelens, "%%%dc", namelen);
243 fscanf(hslist, namelens, name);
244 fscanf(hslist, "%d%*c", &score);
245
246 h = new Highscore(name, score);
247 }
248
249 fclose(hslist);
250
251 if (h->getLevel() < level)
252 {
253 return new EnterHighscoreState(level);
254 } else {
255 return new DisplayAndReturnLocalHighscoreListState();
256 }
257 }
258 } else {
259 return new EnterHighscoreState(level);
260 }
218 } 261 }
219 } 262 }
220 } 263 }
diff --git a/hslist.cpp b/hslist.cpp index 3d9f19a..5ebface 100644 --- a/hslist.cpp +++ b/hslist.cpp
@@ -3,8 +3,26 @@
3#include <SDL_net.h> 3#include <SDL_net.h>
4#include <sstream> 4#include <sstream>
5#include <fstream> 5#include <fstream>
6#include <algorithm>
6#include "util.h" 7#include "util.h"
7#include "titlestate.h" 8#include "titlestate.h"
9#include "gamestate.h"
10
11struct hslist_comp {
12 bool operator() (Highscore* lhs, Highscore* rhs) const
13 {
14 return (lhs->getLevel() > rhs->getLevel());
15 }
16} hslist_comp_i;
17
18void resetRanks(hslist_t in)
19{
20 int i=1;
21 for (hslist_t::iterator it = in.begin(); it != in.end(); ++it, ++i)
22 {
23 ((Highscore*)*it)->setRank(i);
24 }
25}
8 26
9SDL_Surface* HighscoreList::render() 27SDL_Surface* HighscoreList::render()
10{ 28{
@@ -16,27 +34,28 @@ SDL_Surface* HighscoreList::render()
16 TTF_Font* dataFont = loadFont(25); 34 TTF_Font* dataFont = loadFont(25);
17 SDL_Color fontColor = {0, 0, 0, 0}; 35 SDL_Color fontColor = {0, 0, 0, 0};
18 36
19 for (int i=0; i<hslist.size(); i++) 37 int i=0;
38 for (hslist_t::iterator it = hslist.begin(); it != hslist.end(); ++it, ++i)
20 { 39 {
21 Highscore h = hslist[i]; 40 Highscore* h = *it;
22 41
23 int posw, posh; 42 int posw, posh;
24 char pos[3]; // 2 max characters in rank plus the colon at the end 43 char pos[3]; // 2 max characters in rank plus the colon at the end
25 sprintf(pos, "%d:", h.getRank()); 44 sprintf(pos, "%d:", h->getRank());
26 TTF_SizeText(posFont, pos, &posw, &posh); 45 TTF_SizeText(posFont, pos, &posw, &posh);
27 SDL_Rect posSpace = {0, (i+1)*40, posw, posh}; 46 SDL_Rect posSpace = {0, (i+1)*40, posw, posh};
28 SDL_BlitSurface(TTF_RenderText_Blended(posFont, pos, fontColor), NULL, tmp, &posSpace); 47 SDL_BlitSurface(TTF_RenderText_Blended(posFont, pos, fontColor), NULL, tmp, &posSpace);
29 48
30 int namew, nameh; 49 int namew, nameh;
31 char name[26]; // 25 max characters in username plus the space at the beginning 50 char name[26]; // 25 max characters in username plus the space at the beginning
32 sprintf(name, " %s", h.getName()); 51 sprintf(name, " %s", h->getName());
33 TTF_SizeText(dataFont, name, &namew, &nameh); 52 TTF_SizeText(dataFont, name, &namew, &nameh);
34 SDL_Rect nameSpace = {posw, (i+1)*40+((posh/2)-(nameh/2)), namew, nameh}; 53 SDL_Rect nameSpace = {posw, (i+1)*40+((posh/2)-(nameh/2)), namew, nameh};
35 SDL_BlitSurface(TTF_RenderText_Blended(dataFont, name, fontColor), NULL, tmp, &nameSpace); 54 SDL_BlitSurface(TTF_RenderText_Blended(dataFont, name, fontColor), NULL, tmp, &nameSpace);
36 55
37 int lvlw, lvlh; 56 int lvlw, lvlh;
38 char lvl[10]; // 10 max characters in level (based off the fact that 2^32-1 is 10 characters long, and is the highest int) 57 char lvl[10]; // 10 max characters in level (based off the fact that 2^32-1 is 10 characters long, and is the highest int)
39 sprintf(lvl, "%d", h.getLevel()); 58 sprintf(lvl, "%d", h->getLevel());
40 TTF_SizeText(dataFont, lvl, &lvlw, &lvlh); 59 TTF_SizeText(dataFont, lvl, &lvlw, &lvlh);
41 SDL_Rect lvlSpace = {480-lvlw, (i+1)*40+((posh/2)-(nameh/2)), lvlw, lvlh}; 60 SDL_Rect lvlSpace = {480-lvlw, (i+1)*40+((posh/2)-(nameh/2)), lvlw, lvlh};
42 SDL_BlitSurface(TTF_RenderText_Blended(dataFont, lvl, fontColor), NULL, tmp, &lvlSpace); 61 SDL_BlitSurface(TTF_RenderText_Blended(dataFont, lvl, fontColor), NULL, tmp, &lvlSpace);
@@ -45,9 +64,9 @@ SDL_Surface* HighscoreList::render()
45 return tmp; 64 return tmp;
46} 65}
47 66
48std::vector<Highscore> HighscoreList::getLocalHighscores() 67hslist_t HighscoreList::getLocalHighscores()
49{ 68{
50 std::vector<Highscore> temp = std::vector<Highscore>(); 69 hslist_t temp;
51 70
52 std::ifstream exists(getDataFile()); 71 std::ifstream exists(getDataFile());
53 if (exists) 72 if (exists)
@@ -68,8 +87,8 @@ std::vector<Highscore> HighscoreList::getLocalHighscores()
68 fscanf(hslist, namelens, name); 87 fscanf(hslist, namelens, name);
69 fscanf(hslist, "%d%*c", &score); 88 fscanf(hslist, "%d%*c", &score);
70 89
71 Highscore h = Highscore(name, score); 90 Highscore* h = new Highscore(name, score);
72 h.setRank(i+1); 91 h->setRank(i+1);
73 92
74 temp.push_back(h); 93 temp.push_back(h);
75 } 94 }
@@ -80,7 +99,7 @@ std::vector<Highscore> HighscoreList::getLocalHighscores()
80 return temp; 99 return temp;
81} 100}
82 101
83std::vector<Highscore> HighscoreList::getGlobalHighscores() 102hslist_t HighscoreList::getGlobalHighscores()
84{ 103{
85 IPaddress ipaddress; 104 IPaddress ipaddress;
86 105
@@ -117,7 +136,7 @@ std::vector<Highscore> HighscoreList::getGlobalHighscores()
117 download.getline(temps,256); 136 download.getline(temps,256);
118 } 137 }
119 138
120 std::vector<Highscore> temp = std::vector<Highscore>(); 139 hslist_t temp;
121 int scores; 140 int scores;
122 download.getline(temps, 256); 141 download.getline(temps, 256);
123 if (sscanf(temps, "%d%*c", &scores) != 1) 142 if (sscanf(temps, "%d%*c", &scores) != 1)
@@ -156,8 +175,8 @@ std::vector<Highscore> HighscoreList::getGlobalHighscores()
156 throw 4; 175 throw 4;
157 } 176 }
158 177
159 Highscore h = Highscore(name, score); 178 Highscore* h = new Highscore(name, score);
160 h.setRank(i+1); 179 h->setRank(i+1);
161 180
162 temp.push_back(h); 181 temp.push_back(h);
163 } 182 }
@@ -170,6 +189,35 @@ LocalHighscoreList::LocalHighscoreList()
170 this->hslist = getLocalHighscores(); 189 this->hslist = getLocalHighscores();
171} 190}
172 191
192int LocalHighscoreList::addHighscore(Highscore* h)
193{
194 hslist.push_back(h);
195 std::sort(hslist.begin(), hslist.end(), hslist_comp_i);
196 resetRanks(hslist);
197
198 if (hslist.size() > 10)
199 {
200 hslist.resize(10);
201 }
202
203 return h->getRank();
204}
205
206void LocalHighscoreList::writeHighscores()
207{
208 FILE* hsfile = fopen(getDataFile(), "w");
209 fprintf(hsfile, "%d ", (int) this->hslist.size());
210
211 for (hslist_t::iterator it = hslist.begin(); it != this->hslist.end(); it++)
212 {
213 Highscore* h = *it;
214
215 fprintf(hsfile, "%d%s%d ", (int) strlen(h->getName()), h->getName(), h->getLevel());
216 }
217
218 fclose(hsfile);
219}
220
173GlobalHighscoreList::GlobalHighscoreList() 221GlobalHighscoreList::GlobalHighscoreList()
174{ 222{
175 fail = false; 223 fail = false;
@@ -290,6 +338,64 @@ State* DisplayLocalHighscoreListState::operator() (SDL_Renderer* renderer)
290 } 338 }
291} 339}
292 340
341State* DisplayAndReturnLocalHighscoreListState::operator() (SDL_Renderer* renderer)
342{
343 SDL_Texture* pointer = loadImage(renderer, "resources/pointer.bmp");
344
345 LocalHighscoreList* lhl = new LocalHighscoreList();
346 SDL_Surface* list_s = lhl->render();
347 SDL_Color fontColor = {0, 0, 0, 0};
348 SDL_Surface* title = TTF_RenderText_Blended(loadFont(40), "Highscore List", fontColor);
349 SDL_Rect tSpace = {240-(title->w/2), 0, title->w, title->h};
350 SDL_BlitSurface(title, NULL, list_s, &tSpace);
351 SDL_FreeSurface(title);
352
353 SDL_Surface* options_s = SDL_LoadBMP("resources/hlo_paartm.bmp");
354 SDL_Rect oSpace = {0, 440, options_s->w, options_s->h};
355 SDL_BlitSurface(options_s, NULL, list_s, &oSpace);
356 SDL_FreeSurface(options_s);
357
358 SDL_Texture* list = SDL_CreateTextureFromSurface(renderer, list_s);
359 SDL_FreeSurface(list_s);
360
361 int selection = 0;
362 SDL_Event e;
363
364 for (;;)
365 {
366 SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
367 SDL_RenderClear(renderer);
368 SDL_RenderCopy(renderer, list, NULL, NULL);
369 applyTexture(renderer, pointer, selection==0?52:225, 447);
370 SDL_RenderPresent(renderer);
371
372 while (SDL_PollEvent(&e))
373 {
374 if (e.type == SDL_QUIT)
375 {
376 return NULL;
377 } else if (e.type == SDL_KEYDOWN)
378 {
379 if ((e.key.keysym.sym == SDLK_LEFT) && (selection != 0))
380 {
381 selection--;
382 } else if ((e.key.keysym.sym == SDLK_RIGHT) && (selection != 1))
383 {
384 selection++;
385 } else if (e.key.keysym.sym == SDLK_RETURN)
386 {
387 switch (selection)
388 {
389 case 0: return new GameState();
390 case 1: return new TitleState();
391 }
392 }
393
394 }
395 }
396 }
397}
398
293State* DisplayGlobalHighscoreListState::operator() (SDL_Renderer* renderer) 399State* DisplayGlobalHighscoreListState::operator() (SDL_Renderer* renderer)
294{ 400{
295 SDL_Texture* pointer = loadImage(renderer, "resources/pointer.bmp"); 401 SDL_Texture* pointer = loadImage(renderer, "resources/pointer.bmp");
@@ -396,3 +502,189 @@ int DisplayGlobalHighscoreListState::LoadHighscoreList(void* pParam)
396 printf("Couldn't lock mutex: %s\n", SDL_GetError()); 502 printf("Couldn't lock mutex: %s\n", SDL_GetError());
397 } 503 }
398} 504}
505
506EnterHighscoreState::EnterHighscoreState(int level)
507{
508 this->level = level;
509}
510
511State* EnterHighscoreState::operator() (SDL_Renderer* renderer)
512{
513 SDL_Texture* pointer = loadImage(renderer, "resources/pointer.bmp");
514
515 // Render highscore list
516 LocalHighscoreList* lhl = new LocalHighscoreList();
517 char* emp = new char[1];
518 emp[0] = 0;
519 Highscore* h = new Highscore(emp, level);
520 int newpos = lhl->addHighscore(h);
521
522 SDL_Surface* list_s = lhl->render();
523
524 SDL_Color fontColor = {0, 0, 0, 0};
525 SDL_Surface* title = TTF_RenderText_Blended(loadFont(40), "New Highscore!", fontColor);
526 SDL_Rect tSpace = {240-(title->w/2), 0, title->w, title->h};
527 SDL_BlitSurface(title, NULL, list_s, &tSpace);
528 SDL_FreeSurface(title);
529
530 this->lp = 0;
531 this->hsname = (char*) calloc(25, sizeof(char));
532
533 SDL_Surface* text = TTF_RenderText_Blended(loadFont(25), "Enter Your Name", fontColor);
534 SDL_Rect oSpace = {240-(text->w/2), 440, text->w, text->h};
535 SDL_BlitSurface(text, NULL, list_s, &oSpace);
536 SDL_FreeSurface(text);
537
538 SDL_Texture* list = SDL_CreateTextureFromSurface(renderer, list_s);
539 SDL_FreeSurface(list_s);
540
541 int selection = 0;
542 SDL_Event e;
543
544 int posw, posh;
545 char pos[3]; // 2 max characters in rank plus the colon at the end
546 sprintf(pos, "%d:", newpos);
547 char name[26]; // 25 max characters in username plus the space at the beginning
548 sprintf(name, " %s", hsname);
549 SDL_Surface* newName_s = TTF_RenderText_Blended(loadFont(25), name, fontColor);
550 TTF_SizeText(loadFont(40), pos, &posw, &posh);
551 SDL_Rect rntSpace;
552 rntSpace.x = posw;
553 rntSpace.y = newpos*40+((posh/2)-(newName_s->h/2));
554 rntSpace.w = newName_s->w;
555 rntSpace.h = newName_s->h;
556 newName = SDL_CreateTextureFromSurface(renderer, newName_s);
557 SDL_FreeSurface(newName_s);
558
559 for (;;)
560 {
561 SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
562 SDL_RenderClear(renderer);
563
564 SDL_Rect eSpace = {0, newpos*40, 480, 40};
565 SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
566 SDL_RenderFillRect(renderer, &eSpace);
567
568 SDL_RenderCopy(renderer, list, NULL, NULL);
569 SDL_RenderCopy(renderer, newName, NULL, &rntSpace);
570
571 SDL_RenderPresent(renderer);
572
573 while (SDL_PollEvent(&e))
574 {
575 if (e.type == SDL_QUIT)
576 {
577 return NULL;
578 } else if (e.type == SDL_KEYDOWN)
579 {
580 if ((e.key.keysym.sym == SDLK_BACKSPACE) && (lp > 0))
581 {
582 hsname[--lp] = 0;
583
584 SDL_Color fontColor = {0, 0, 0, 0};
585 char name[26]; // 25 max characters in username plus the space at the beginning
586 sprintf(name, " %s", hsname);
587 newName_s = TTF_RenderText_Blended(loadFont(25), name, fontColor);
588 rntSpace.w = newName_s->w;
589 rntSpace.h = newName_s->h;
590 newName = SDL_CreateTextureFromSurface(renderer, newName_s);
591 SDL_FreeSurface(newName_s);
592 } else if ((e.key.keysym.sym == SDLK_RETURN) && (hsname[0] != 0))
593 {
594 lhl = new LocalHighscoreList();
595 Highscore* h2 = new Highscore(hsname, level);
596 lhl->addHighscore(h2);
597 lhl->writeHighscores();
598
599 return new NewHighscoreState(h2);
600 }
601 } else if (e.type == SDL_TEXTINPUT)
602 {
603 if (((*e.text.text & 0xFF80) == 0) && (*e.text.text >= 32 && *e.text.text < 127) && (lp < 25))
604 {
605 hsname[lp++] = *e.text.text & 0x7f;
606 hsname[lp] = 0;
607
608 SDL_Color fontColor = {0, 0, 0, 0};
609 char name[26]; // 25 max characters in username plus the space at the beginning
610 sprintf(name, " %s", hsname);
611 newName_s = TTF_RenderText_Blended(loadFont(25), name, fontColor);
612 rntSpace.w = newName_s->w;
613 rntSpace.h = newName_s->h;
614 newName = SDL_CreateTextureFromSurface(renderer, newName_s);
615 SDL_FreeSurface(newName_s);
616 }
617 }
618 }
619 }
620}
621
622NewHighscoreState::NewHighscoreState(Highscore* h)
623{
624 this->h = h;
625}
626
627State* NewHighscoreState::operator() (SDL_Renderer* renderer)
628{
629 SDL_Texture* pointer = loadImage(renderer, "resources/pointer.bmp");
630
631 // Render highscore list
632 LocalHighscoreList* lhl = new LocalHighscoreList();
633 SDL_Surface* list_s = lhl->render();
634
635 SDL_Color fontColor = {0, 0, 0, 0};
636 SDL_Surface* title = TTF_RenderText_Blended(loadFont(40), "New Highscore!", fontColor);
637 SDL_Rect tSpace = {240-(title->w/2), 0, title->w, title->h};
638 SDL_BlitSurface(title, NULL, list_s, &tSpace);
639 SDL_FreeSurface(title);
640
641 SDL_Surface* options_s = SDL_LoadBMP("resources/hlo_passartm.bmp");
642 SDL_Rect oSpace = {0, 440, options_s->w, options_s->h};
643 SDL_BlitSurface(options_s, NULL, list_s, &oSpace);
644 SDL_FreeSurface(options_s);
645
646 SDL_Texture* list = SDL_CreateTextureFromSurface(renderer, list_s);
647 SDL_FreeSurface(list_s);
648
649 int selection = 0;
650 SDL_Event e;
651
652 for (;;)
653 {
654 SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
655 SDL_RenderClear(renderer);
656
657 SDL_Rect eSpace = {0, h->getRank()*40, 480, 40};
658 SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
659 SDL_RenderFillRect(renderer, &eSpace);
660
661 SDL_RenderCopy(renderer, list, NULL, NULL);
662 applyTexture(renderer, pointer, selection==0?13:(selection==1?138:284), 448);
663 SDL_RenderPresent(renderer);
664
665 while (SDL_PollEvent(&e))
666 {
667 if (e.type == SDL_QUIT)
668 {
669 return NULL;
670 } else if (e.type == SDL_KEYDOWN)
671 {
672 if ((e.key.keysym.sym == SDLK_LEFT) && (selection != 0))
673 {
674 selection--;
675 } else if ((e.key.keysym.sym == SDLK_RIGHT) && (selection != 2))
676 {
677 selection++;
678 } else if (e.key.keysym.sym == SDLK_RETURN)
679 {
680 switch (selection)
681 {
682 case 0: return new GameState();
683 //case 1: return new SubmitHighscoreListState(hsname, level);
684 case 2: return new TitleState();
685 }
686 }
687 }
688 }
689 }
690}
diff --git a/hslist.h b/hslist.h index f043336..c1db137 100644 --- a/hslist.h +++ b/hslist.h
@@ -6,21 +6,27 @@
6#ifndef HSLIST_H 6#ifndef HSLIST_H
7#define HSLIST_H 7#define HSLIST_H
8 8
9typedef std::vector<Highscore*> hslist_t;
10
11void resetRanks(hslist_t in);
12
9class HighscoreList 13class HighscoreList
10{ 14{
11 public: 15 public:
12 SDL_Surface* render(); 16 SDL_Surface* render();
13 17
14 protected: 18 protected:
15 std::vector<Highscore> getLocalHighscores(); 19 hslist_t getLocalHighscores();
16 std::vector<Highscore> getGlobalHighscores(); 20 hslist_t getGlobalHighscores();
17 21
18 std::vector<Highscore> hslist; 22 hslist_t hslist;
19}; 23};
20 24
21class LocalHighscoreList : public HighscoreList { 25class LocalHighscoreList : public HighscoreList {
22 public: 26 public:
23 LocalHighscoreList(); 27 LocalHighscoreList();
28 int addHighscore(Highscore* h);
29 void writeHighscores();
24}; 30};
25 31
26class GlobalHighscoreList : public HighscoreList { 32class GlobalHighscoreList : public HighscoreList {
@@ -45,6 +51,11 @@ class DisplayLocalHighscoreListState : public State {
45 State* operator() (SDL_Renderer* renderer); 51 State* operator() (SDL_Renderer* renderer);
46}; 52};
47 53
54class DisplayAndReturnLocalHighscoreListState : public State {
55 public:
56 State* operator() (SDL_Renderer* renderer);
57};
58
48class DisplayGlobalHighscoreListState : public State { 59class DisplayGlobalHighscoreListState : public State {
49 public: 60 public:
50 State* operator() (SDL_Renderer* renderer); 61 State* operator() (SDL_Renderer* renderer);
@@ -59,4 +70,25 @@ class DisplayGlobalHighscoreListState : public State {
59 static int LoadHighscoreList(void* pParam); 70 static int LoadHighscoreList(void* pParam);
60}; 71};
61 72
73class EnterHighscoreState : public State {
74 public:
75 EnterHighscoreState(int level);
76 State* operator() (SDL_Renderer* renderer);
77
78 private:
79 int level;
80 int lp;
81 char* hsname;
82 SDL_Texture* newName;
83};
84
85class NewHighscoreState : public State {
86 public:
87 NewHighscoreState(Highscore* h);
88 State* operator() (SDL_Renderer* renderer);
89
90 private:
91 Highscore* h;
92};
93
62#endif 94#endif
diff --git a/resources/hlo_paartm.bmp b/resources/hlo_paartm.bmp index 72a1bf2..191f1ab 100644 --- a/resources/hlo_paartm.bmp +++ b/resources/hlo_paartm.bmp
Binary files differ
diff --git a/resources/hlo_passartm.bmp b/resources/hlo_passartm.bmp index 6c57e98..5e20b7a 100644 --- a/resources/hlo_passartm.bmp +++ b/resources/hlo_passartm.bmp
Binary files differ