Προς το περιεχόμενο

Παιχνίδι 2048 σε C


johnny.tifosi

Προτεινόμενες αναρτήσεις

 

Edit: το workflow που περιέγραψα σε πολλά μηνύματα είναι αυτό που λέει και ο pmav99 απλά δεν θα το εξήγησα καλά για αυτό δεν έγινε κατανοητό.

Εσύ μια χαρά τα εξήγησες. Αυτοί είναι δεινόσαυροι :P :D

  • Like 1
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

  • Απαντ. 272
  • Δημ.
  • Τελ. απάντηση

Συχνή συμμετοχή στο θέμα

Συχνή συμμετοχή στο θέμα

Δημοσιευμένες Εικόνες

Εγώ παίζει να είμαι και... τερατόσαυρος :lol:

 

@geomagas:

 

Δεν θα ήθελα να πειράζω απευθείας το blessed repo, μέχρι να αισθανθώ πως έχω εξοικειωθεί αρκετά. Εως τότε, θα σε πείραζε να το κρατήσουμε όπως το έχουμε τώρα;

 

@pmav99 + @imitheos:

 

Ευχαριστώ παιδιά! Με 2-3 ποστ νομίζω με γλιτώσατε από πολύ διάβασμα :)

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δεν θα ήθελα να πειράζω απευθείας το blessed repo, μέχρι να αισθανθώ πως έχω εξοικειωθεί αρκετά. Εως τότε, θα σε πείραζε να το κρατήσουμε όπως το έχουμε τώρα;

Κανένα πρόβλημα, δεν με πειράζει καθόλου. Όσο θέλεις.

Απλά από την αρχή αισθανόμουν λίγο άβολα να φαίνεται ότι "πιστώνομαι" το project κάποιου άλλου. That's all.

 

@pmav99 + @imitheos:

 

Ευχαριστώ παιδιά! Με 2-3 ποστ νομίζω με γλιτώσατε από πολύ διάβασμα :)

Αυτό ξαναπέστο! (Γκρόααααρρρρ!)

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

@imitheos:

 

Μια 2η απορία είναι πως συγχρονίζονται στο git/github 2 ή περισσότεροι contributors αν τα διαφορετικά τους branches πειράζουν/αλλάζουν κοινά πράγματα στη κοινή αφετηρία τους, που είναι το blessed/master (αν δηλαδή κάνει merge ο geomagas τα branches των 2 contributors στο blessed/master, ποιανού contributor οι αλλαγές θα υπερισχύσουν... το πάει χρονικά; )

 

Εννοείται πως δεν πάει χρονικά :) Το δεύτερο merge δηλαδή δεν θα αναιρέσει τις αλλαγές του πρώτου.

Σκέφτηκα να γράψω ένα απλό παράδειγμα διένεξης για να φανεί αν πάει χρονικά :) και τι γίνονται οι αλλαγές. Λόγω του νήματος το παράδειγμα θα είναι σε C αλλά και να μη ξέρει κάποιος C πιστεύω είναι τόσο γελοία εύκολο που θα γίνει κατανοητό σε όλους.

 

Η ιδέα είναι ότι έχουμε το printf("Hello World\n"); και οι δύο developers migf1, geomagas έχουν μια ιδέα για να το βελτιώσουν. Ο migf1 αλλάζει την printf σε puts ενώ ο geomagas προσθέτει ακόμη ένα string. Έπειτα ο integrator / maintainer / όπως_θες_πες_τον θα πρέπει στο blessed repo να λύσει την διένεξη.

 

Χάριν ευκολίας το blessed repo από το οποίο θα κάνουν clone οι devs θα είναι τοπικός κατάλογος αλλά φανταστείτε ότι μιλάμε για το github.

 

Τελικά βγήκε περισσότερο κατεβατό από ό,τι περίμενα οπότε ελπίζω να βοηθήσω και όχι να μπερδέψω περισσότερο. Όποιος δεν βαριέται να το διαβάσει ας μου πει ό,τι απορίες έχει.

 

 

% mkdir blessed
% cd blessed
% git init
Initialized empty Git repository in /tmp/blessed/.git/
% git config --local user.name "Benevolent Maintainer"
% git config --local user.email "BM@Valhalla"
% cat >> hw.c << EOF
> #include <stdio.h>
>
> int main(void)
> {
> printf("Hello World\n");
>
> return 0;
> }
> EOF
% git add hw.c
% git commit -m "Greetings, World"
[master (root-commit) dcd4d37] Greetings, World
 1 file changed, 8 insertions(+)
 create mode 100644 hw.c
Δημιουργείται το blessed repo και μετά από πολλά χρόνια και πολλά commits, ο lead developer αφήνει τη δουλειά σε άλλους και παίρνει τον ρόλο του integrator (κάτι σαν τον Linus ένα πράγμα) δηλαδή κάνει merge branches.

 

Το project αρέσει στους migf1 και geomagas οι οποίοι κάνουν clone το repo και αρχίζουν να δουλεύουν ο καθένας σε κάποιο θέμα (χωρίς φυσικά να ξέρει ο ένας τι θέμα δουλεύει ο άλλος).

 

Ο migf1 επειδή απλά τυπώνεται ένα literal δεν βλέπει νόημα να χρησιμοποιείται η printf και την αλλάζει σε puts ώστε το πρόγραμμα να είναι πιο γρήγορο.

(migf1) % git clone blessed migf1
Cloning into 'migf1'...
(migf1) % cd migf1
(migf1) % git checkout -b use_puts
Switched to a new branch 'use_puts'

(migf1) % "edit hw.c"
(migf1) % "compile & test"
(migf1) % git add --update
(migf1) % git commit -m "Use puts instead of printf"
[use_puts 437ba81] Use puts instead of printf
 1 file changed, 1 insertion(+), 1 deletion(-)
(migf1) % "edit hw.c"
(migf1) % git add --update
(migf1) % git commit -m "puts automatically outputs a newline"
[use_puts 080dd88] puts automatically outputs a newline
 1 file changed, 1 insertion(+), 1 deletion(-)

(migf1) % git push origin use_puts
Writing objects: 100% (6/6), 627 bytes | 0 bytes/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To blessed
 * [new branch]      use_puts -> use_puts
Ο migf1 είναι contributor στο blessed repo στο github οπότε έχει πρόσβαση και έτσι έστειλε το branch στο blessed repo να το δουν και άλλοι. Το branch αρέσει σε όλους και αφού πήρε ack από 2 ακόμη devs, ο integrator θα πάει να το κάνει merge.

 

Όλη αυτή την ώρα όμως δούλευε και ο geomagas που είχε την ιδέα να εμφανίζεται και η απάντηση στην πιο σημαντική ερώτηση.

(geomagas) % git clone blessed geomagas
Cloning into 'geomagas'...
(geomagas) % cd geomagas

(geomagas) % git checkout -b answer_to_all
Switched to a new branch 'answer_to_all'
(geomagas) % "edit hw.c"
(geomagas) % git add --update
(geomagas) % git commit -m "The answer to everything"
[answer_to_all ad30fd6] The answer to everything
 1 file changed, 1 insertion(+), 1 deletion(-)
(geomagas) % git push origin answer_to_all
Writing objects: 100% (3/3), 337 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To blessed
 * [new branch]      answer_to_all -> answer_to_all
Ως εδώ όλα καλά. Δύο devs που δουλεύουν σε ξεχωριστά branches οπότε δεν υπάρχει κανένα πρόβλημα και ο ένας αγνοεί την ύπαρξη του άλλου. Ας δούμε τώρα το θέμα από την πλευρά του integrator.

 

 

 

 

 

 

Ο integrator διάβασε τα διθυραμβικά σχόλια που γράφουν όλοι για την αύξηση της ταχύτητας χάρη στο commit του migf1 και πάει να κάνει merge το branch. Πρώτα φυσικά θα δει τι αλλαγές επιφέρει το branch.

% git log --stat -p --reverse master..use_puts
commit 437ba81abfdb675ca75aff2a1e8a1f09c6f808b4
Author:     Migf1 <migf1@bifrost>
AuthorDate: Sat Jul 19 18:31:28 2014 +0300
Commit:     Migf1 <migf1@bifrost>
CommitDate: Sat Jul 19 18:31:28 2014 +0300

    Use puts instead of printf
---
 hw.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hw.c b/hw.c
index 719ae3b..fb2c6f3 100644
--- a/hw.c
+++ b/hw.c
@@ -2,7 +2,7 @@

 int main(void)
 {
-printf("Hello World\n");
+puts("Hello World\n");

 return 0;
 }

commit 080dd880e6b3b4626cd87c08d0bf9cb204bc000b
Author:     Migf1 <migf1@bifrost>
AuthorDate: Sat Jul 19 18:33:11 2014 +0300
Commit:     Migf1 <migf1@bifrost>
CommitDate: Sat Jul 19 18:33:11 2014 +0300

    puts automatically outputs a newline
---
 hw.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hw.c b/hw.c
index fb2c6f3..3396db7 100644
--- a/hw.c
+++ b/hw.c
@@ -2,7 +2,7 @@

 int main(void)
 {
-puts("Hello World\n");
+puts("Hello World");

 return 0;
 }
Λέει στο git να το εμφανίζει τα commits (log) μαζί με ποια αρχεία έχουν αλλαχτεί (--stat) καθώς και το diff με τις αλλαγές (-p) ταξινομημένα με τη σειρά που εφαρμόστηκαν (--reverse) και βλέπει τα παραπάνω δύο commits. Δεν βλέπει κάποιο λάθος στον κώδικα ή στο Coding Style οπότε προχωρά στο merge. Επειδή ξέρει ότι οι αλλαγές μπορούν να εφαρμοστούν κατευθείαν δεν χρειάζεται να πάρει μέτρα και απλά κάνει το merge.

% git merge use_puts
Updating dcd4d37..080dd88
Fast-forward
 hw.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Μετά από λίγο βλέπει και την request του geomagas οπότε τρέχει πάλι την ίδια εντολή για να δει τις αλλαγές.

% git log --stat -p --reverse master..answer_to_all
commit ad30fd6106562a11b0bfbf2f4ae727c0d3f18c36
Author:     Geomagas <geomagas@vanaheimr>
AuthorDate: Sat Jul 19 18:48:31 2014 +0300
Commit:     Geomagas <geomagas@vanaheimr>
CommitDate: Sat Jul 19 18:48:31 2014 +0300

    The answer to everything
---
 hw.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hw.c b/hw.c
index 719ae3b..7295338 100644
--- a/hw.c
+++ b/hw.c
@@ -2,7 +2,7 @@

 int main(void)
 {
-printf("Hello World\n");
+printf("Hello World. The Answer is 42\n");

 return 0;
 }
Άρτιο και αυτό το branch οπότε και αυτό πάει για merge. Θυμάται όμως ότι στο ίδιο θέμα έκανε merge και κάποιο άλλο branch οπότε σκέφτεται να μην το κάνει κατευθείαν. Αντί λοιπόν να κάνει merge στο master και αν υπάρχει πρόβλημα να γ..θεί ο Δίας, θα δημιουργήσει ένα προσωρινό scratch branch για να το χρησιμοποιήσει μόνο για το merge. (Μια εναλλακτική είναι να χρησιμοποιηθούν διάφορες παράμετροι του merge όπως πχ η --ff-only ώστε να απορριφθεί το merge αν δεν είναι fast forward).

 

% git checkout -b tmp_4_merge
Switched to a new branch 'tmp_4_merge'

% git merge answer_to_all
Auto-merging hw.c
CONFLICT (content): Merge conflict in hw.c
Automatic merge failed; fix conflicts and then commit the result.
Όχι ρε παιδί μου, κάτι δεν πήγε καλά και υπάρχει διένεξη. Όπως βλέπουμε το merge σταμάτησε και περιμένει να βάλουμε εμείς το χεράκι μας. Δεν πάει δηλαδή χρονικά και αυτή η αλλαγή να κάνει overwrite την προηγούμενη γιατί έτσι θα χάνονταν δεδομένα και το VCS θα ήταν για τα σκουπίδια. Ας τρέξουμε diff να δούμε τι στο καλό γίνεται.

 

% git diff
diff --cc hw.c
index 3396db7,7295338..0000000
--- a/hw.c
+++ b/hw.c
@@@ -2,7 -2,7 +2,11 @@@

  int main(void)
  {
++<<<<<<< HEAD
 +puts("Hello World");
++=======
+ printf("Hello World. The Answer is 42\n");
++>>>>>>> answer_to_all

  return 0;
  }
Ωπ τι σόι diff είναι αυτό ? Αυτή η μορφή χρησιμοποιείται από το diff για να μας δείξει την διένεξη. Ο κώδικας που επηρεάστηκε ταυτόχρονα από δύο πηγές φαίνεται μέσα στα < >. Το τμήμα του κώδικα ανάμεσα στα < και = είναι η μία πλευρά και ανάμεσα στα = και > είναι η άλλη πλευρά.

 

Για να επιλυθεί η διένεξη πρέπει να κάνουμε edit το αρχείο και να επιλέξουμε πώς θέλουμε να είναι το συγκεκριμένο τμήμα. Μετά απλά κάνουμε git add --update και git merge --continue για να ολοκληρωθεί το merge.

 

Πρέπει όμως να παρθεί μια απόφαση για το ποια εκδοχή είναι η σωστή. Ο maintainer γνωρίζει και αυτός C εννοείται οπότε μπορεί να ψάξει ο ίδιος και να την επιλύσει. Αν ο maintainer έχει να κάνει με κάποιο νέο developer και πιστεύει ότι θα τον τρομάξει αυτή η διαδικασία της επίλυσης, μπορεί να επιλέξει να το λύσει ο ίδιος.

 

Αυτό όμως του εισάγει φόρτο και του αφαιρεί χρόνο από την πραγματική του δουλειά που είναι να ενσωματώνει τις αλλαγές. Για αυτό το λόγο συνήθως επιλέγεται η αρχή "Clean your mess" δηλαδή να λύνονται οι διενέξεις από αυτόν που έγραψε τον κώδικα. Αυτός γνωρίζει τον κώδικα που έγραψε καλύτερα από όλους οπότε μπορεί να λύσει την διένεξη με το λιγότερο δυνατό κόπο.

 

Έτσι θα στείλει ένα μήνυμα στον geomaga και θα του πει "ρε ψηλέ δεν γίνεται merge το branch σου. φτιάξε τη διένεξη και ξανα στείλε το". (Στην πραγματικότητα αναμένεται ότι ο εκάστοτε dev που στέλνει το branch του έχει φροντίσει από πριν να εφαρμόζεται cleanly ώστε να μην χρειάζεται ο integrator να μπει σε αυτή τη διαδικασία αλλά shit happens)

 

 

 

 

 

Βλέπει ο geomagas το παραπάνω μήνυμα και πιάνει δουλειά.

(geomagas) % git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)
(geomagas) % git fetch origin
(geomagas) % git merge origin/master
Updating dcd4d37..080dd88
Fast-forward
 hw.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Τώρα το master του είναι ανανεωμένο και περιέχει την αλλαγή με το puts του migf1.

(geomagas) % git checkout answer_to_all
Switched to branch 'answer_to_all'
(geomagas) % git rebase master
First, rewinding head to replay your work on top of it...
Applying: The answer to everything
Using index info to reconstruct a base tree...
M	hw.c
Falling back to patching base and 3-way merge...
Auto-merging hw.c
CONFLICT (content): Merge conflict in hw.c
Failed to merge in the changes.

When you have resolved this problem, run "git rebase --continue".
Αλλάζει στο branch του και τρέχει rebase ώστε να το φέρει στη μορφή που θα είχε αν ξεκινούσε τώρα από το master. Όπως ήταν αναμενόμενο παίρνει το ίδιο conflict που πήρε ο integrator.

 

Διαβάζει το ίδιο diff και βλέπει ότι η διένεξη προκλήθηκε από την αλλαγή σε puts του migf1. Ρε τον τρελό λέει πάει να κερδίσει 5ms με την puts. Άντε ας του κάνω το χατήρι.

 

Έτσι ενσωματώνει τη δική του εκδοχή με το 42 αλλά με τη χρήση puts αντί για printf. Δηλαδή δεν κρατήθηκε αυτούσια καμμία από τις δύο εκδοχές αλλά συγχωνεύτηκαν κάπως το οποίο θα ήταν δύσκολο να το σκεφτεί ο integrator που δεν γνωρίζει καλά τον κώδικα.

(geomagas) % git add --update
(geomagas) % git rebase --continue
Applying: The answer to everything
Έγινε το rebase οπότε απλά ξανακάνουμε push το branch μας.

(geomagas) % git push origin answer_to_all
To /tmp/blessed
 ! [rejected]        answer_to_all -> answer_to_all (non-fast-forward)
error: failed to push some refs to '/tmp/blessed'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Ώπα τι έχουμε εδώ ? Το push δεν πετυχαίνει. Φυσικά δεν πετυχαίνει γιατί κάναμε rebase το branch :)

 

Το push κοιτάει το branch στο οποίο κάνουμε push να είναι πρόγονος του δικού μας και να στείλει τα commits που δεν υπάρχουν. Στη δική μας περίπτωση όμως λόγω του rebase έχουμε την παρακάτω μορφή.

Remote A -> B -> C -> D
Local  A -> B -> C -> D'
Αν μιλούσαμε για κάποιο long-standing branch (όπως το master) που θα υπάρχει για καιρό και πάνω σε αυτό θα βασίσουν και άλλοι devs τη δουλειά τους, τότε δεν κάνουμε rebase γιατί θα τους πηδήξουμε. Τώρα όμως μιλάμε για ένα feature branch που δουλεύεται από έναν και έχει τελειώσει οπότε είμαστε σίγουροι ότι δεν υπάρχει πρόβλημα να γίνει push.

 

Έτσι θα κάνουμε force push για να πούμε στο git ότι δεν μας νοιάζει που θα χαθεί το commit D γιατί αυτό που θέλαμε να κάνουμε με το D το επιτύχαμε με το D'.

 

(geomagas) % git push -f origin answer_to_all
Writing objects: 100% (3/3), 339 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To blessed
 + ad30fd6...5852c02 answer_to_all -> answer_to_all (forced update)
Ο geomagas στέλνει ένα μήνυμα στον integrator ότι έφτιαξε σωστά το branch του και πάει να πιει μια μπύρα.

 

 

 

 

 

Έρχεται τώρα και πάλι ο integrator και βλέπει το νέο branch με επιλυμένη την διένεξη

% git log --stat -p --reverse master..answer_to_all
commit 5852c02e344130f1ad6192232893190f1363efa1
Author:     Geomagas <geomagas@vanaheimr>
AuthorDate: Sat Jul 19 18:48:31 2014 +0300
Commit:     Geomagas <geomagas@vanaheimr>
CommitDate: Sat Jul 19 19:02:12 2014 +0300

    The answer to everything
---
 hw.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hw.c b/hw.c
index 3396db7..66964a0 100644
--- a/hw.c
+++ b/hw.c
@@ -2,7 +2,7 @@

 int main(void)
 {
-puts("Hello World");
+puts("Hello World. The Answer is 42\n");

 return 0;
 }
Ο geomagas πρόσθεσε την αλλαγή του αλλά έλαβε υπόψην και την αλλαγή του migf1 οπότε πλέον χρησιμοποιείται η puts. Τώρα μπορούμε πάλι να δημιουργήσουμε ένα προσωρινό branch για να δοκιμάσουμε αν θα πετύχει το merge ή εναλλακτικά μπορούμε να δοκιμάσουμε το παρακάτω.

% git checkout master
Switched to branch 'master'
% git merge --ff-only answer_to_all
Updating 080dd88..5852c02
Fast-forward
 hw.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Το merge ήταν fast forward οπότε και έγινε σωστά χωρίς κανένα πρόβλημα.

 

Δύο συγκρουόμενα branches ενσωματώθηκαν επιτυχώς.

 

 

 

Με λίγα λόγια λοιπόν κανένα VCS δεν εφαρμόζει commits σειριακά με βάση το χρόνο σβήνοντας αλλαγές προηγούμενων commits. Όταν ένα patch δεν εφαρμόζεται cleanly, τότε βαράει error και περιμένει ανθρώπινη παρέμβαση ώστε να επιλυθεί το πρόβλημα.

  • Like 5
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Ωραίος ο imitheos (αν και το puts() εκτός από extra-speed :lol: προστατεύει κι από τυχόν format-string exploits :P)

 

Παρεμπιπτόντως, τελικά νομίζω πως μου αρέσει καλύτερα το fork+pull-request μοντέλο, με το rebase να το κάνουν οι contributors στα δικά τους branches για επίλυση διενέξεων.

 

ΥΓ. Καλώς εχόντων των πραγμάτων, σήμερα θα κάνω commit με συμπληρωμένα και τα υπόλοιπα σχόλια στον κώδικα (θα ειδοποιήσω όταν είναι).

 

...

Αυτό ξαναπέστο! (Γκρόααααρρρρ!)

 

 

"Ευχαριστώ παιδιά! Με 2-3 ποστ νομίζω με γλιτώσατε από πολύ διάβασμα :)"

 

300x300.jpg

 

 

 

...

Απλά από την αρχή αισθανόμουν λίγο άβολα να φαίνεται ότι "πιστώνομαι" το project κάποιου άλλου. That's all.

Σιγά το πρότζεκτ. Άμα βάλει κανείς AI και GUI κάτι θα πάει να γίνει, αλλά τότε δεν θα είναι αποκλειστικά δικό μου προτζεκτ ;)

 

Btw, για έξτρα GUIs/TUIs σκέφτηκα να το αφήσουμε ως έχει για fallback, και να προσθέσουμε στην main() πρόβλεψη για υλοποίηση όπως είχες προτείνει (δηλαδή με το game-loop υλοποιημένο μέσα στο εκάστοτε έξτρα GUI/TUI) γιατί η εναλλακτική να τα αυτονομήσουμε με δικό μας messaging protocol, που με τη σειρά του σημαίνει πως πρέπει να προσθέσουμε και UI related stuff μέσα στο gamestate (ή ακόμα "χειρότερα" να φτιάξουμε κοινόχρηστο ui-state) είναι μεγάλη βαβούρα... τι λες/λέτε;

 

ΥΓ. Άσχετα με τα παραπάνω, ψήνεται κανείς να φτιάξει αυτόνομο Replay Viewer σε όποιο GUI επιθυμεί, σε όποια γλώσσα επιθυμεί, με μοναδικό του input τα υπάρχοντα serialized replay-files;

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Παρεμπιπτόντως, τελικά νομίζω πως μου αρέσει καλύτερα το fork+pull-request μοντέλο, με το rebase να το κάνουν οι contributors στα δικά τους branches για επίλυση διενέξεων.

 

Η διαφορά μεταξύ του shared repo και των forked repos δεν έχει να κάνει με το που θα γίνεται η επίλυση των διενέξεων. Αυτή ούτως ή άλλως (πρέπει να) γίνεται στα branches προτού γίνουν merge με το master. Η διαφορά έχει να κάνει με το ποιος έχει write privileges στο κεντρικό repository.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Btw, για έξτρα GUIs/TUIs σκέφτηκα να το αφήσουμε ως έχει για fallback, και να προσθέσουμε στην main() πρόβλεψη για υλοποίηση όπως είχες προτείνει (δηλαδή με το game-loop υλοποιημένο μέσα στο εκάστοτε έξτρα GUI/TUI) γιατί η εναλλακτική να τα αυτονομήσουμε με δικό μας messaging protocol, που με τη σειρά του σημαίνει πως πρέπει να προσθέσουμε και UI related stuff μέσα στο gamestate (ή ακόμα "χειρότερα" να φτιάξουμε κοινόχρηστο ui-state) είναι μεγάλη βαβούρα... τι λες/λέτε;

 

Ναι γιατί όχι; Όμως, επειδή αυτό είναι περίληψη της περίληψης σχετικά με αυτό που έχεις στο μυαλό σου ( μαντεύω σωστά; ) ή θα πρέπει να επεκταθείς ή να το υλοποιήσεις όπως το σκέφτεσαι και όποιος θέλει αρχίζει και "κουμπώνει" πράγματα. Τι λες;

 

ΥΓ. Άσχετα με τα παραπάνω, ψήνεται κανείς να φτιάξει αυτόνομο Replay Viewer σε όποιο GUI επιθυμεί, σε όποια γλώσσα επιθυμεί, με μοναδικό του input τα υπάρχοντα serialized replay-files;

Καλή ιδέα. Εγώ θα προσπαθήσω να φτιάξω καναδυό που σκέφτηκα.

 

Αλλά πριν απ' αυτό, χθες έκατσα (επιτέλους) και είδα λίγο τον κώδικα, όχι πολλά πράγματα, βασικά το board.c και λίγο τα skins. Μ' έπιασε λοιπόν αυτό που με πιάνει πάντα: το σύνδρομο "πως-θα-το-έφτιαχνα-εγώ-από-το-μηδέν". Ε, αν το έφτιαχνα εγώ δεν θα το ονόμαζα 2048, αλλά 11! (211=2048)

 

Μέσα στη διαδικασία, συνειδητοποίησα ότι το παιχνίδι δεν έχει καθόλου να κάνει στην ουσία με δυνάμεις του 2. Οι τιμές των tiles μπορούν άνετα να γίνουν map σε ένα σύνολο τιμών {0,1,2,3,4,5....} αντί για {0,2,4,8,16.....}. Από το πρώτο σύνολο στο δεύτερο, προφανώς πάμε εύκολα με 2ν. Έτσι, θα μπορούσε πχ το παρακάτω (από το tui_skin.h):

extern const ConColors *tui_skin_get_colors_tile0( const TuiSkin *skin );
extern const ConColors *tui_skin_get_colors_tile2( const TuiSkin *skin );
extern const ConColors *tui_skin_get_colors_tile4( const TuiSkin *skin );
extern const ConColors *tui_skin_get_colors_tile8( const TuiSkin *skin );
extern const ConColors *tui_skin_get_colors_tile16( const TuiSkin *skin );
extern const ConColors *tui_skin_get_colors_tile32( const TuiSkin *skin );
extern const ConColors *tui_skin_get_colors_tile64( const TuiSkin *skin );
extern const ConColors *tui_skin_get_colors_tile128( const TuiSkin *skin );
extern const ConColors *tui_skin_get_colors_tile256( const TuiSkin *skin );
extern const ConColors *tui_skin_get_colors_tile512( const TuiSkin *skin );
extern const ConColors *tui_skin_get_colors_tile1024( const TuiSkin *skin );
extern const ConColors *tui_skin_get_colors_tile2048( const TuiSkin *skin );

...να αντικατασταθεί με μία 

extern const ConColors *tui_skin_get_colors_tile( int value, const TuiSkin *skin );

...όπου, στην υλοποίησή της, θα γίνεται πχ μία αναζήτηση σε έναν πίνακα, με index το tile value.

 

Το παραπάνω είναι ένα μόνο παράδειγμα, υπάρχουν πολλά σχετικά με το πως θα απλοποιούνταν η "λογική" του παιχνιδιού εσωτερικά μόνο με αυτή την αλλαγή. Αλλά το σημαντικό είναι ότι αρχίζουμε να έχουμε έναν "κοινό παρονομαστή" για διάφορες παραλλαγές του παιχνιδιού, όπως αυτή ή αυτή, ή ότι μας κατέβει στο κεφάλι, αφού τα tiles θα ορίζονται από έναν μονοδιάστατο πίνακα.

 

Συν το ότι τα saved games από μία υλοποίηση gui θα μπορούν άνετα να αναπαρασταθούν στο replay μίας άλλης, αφού προφανώς θα έχει αλλάξει και το format των .sav

 

Επιπλέον, αλλάζει προφανώς ο τρόπος υπολογισμού της τιμής του κάθε tile, όταν αυτό συνενώνεται με άλλο. Για παράδειγμα, ο αντίστοιχος κώδικας στην _merge_adjacent_from_beg() θα γινόταν:

        if ( backup[k] == backup[k+1] ) {
            backup[k]++; // used to be: += backup[k+1];
           ...

Και εντάξει, μπορεί να μην κάνει σημαντική διαφορά στις επιδόσεις κατά τη διάρκεια του παιχνιδιού, αλλά ίσως στην υλοποίηση ενός ai να γλιτώναμε αρκετά cpu cycles.

 

Έχω κι άλλα ...επιχειρήματα, αλλά τα έγραψα βιαστικά όπως μου ερχόταν. Το συζητάμε.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δεν είμαι σίγουρος ότι κατάλαβα. Βασικά, αν το ψάξεις θα δεις πως υπάρχουν παραλλαγές που δεν ισχύει το 2^n... π.χ.: http://louhuang.com/2048-numberwang/.

 

Γενικώς νομίζω πως το να υπολογίζονται δυναμικά τα αποτελέσματα δίνει μεγαλύτερη ευελιξία από το να βασίζονται σε predefined tables (άσχετα αν εγώ στον κώδικα σε πολλά σημεία έχω κάνει hardcoded διάφορα πράγματα, όπως τα skins που έθιξες στο παράδειγμα).

 

Για τα replays ομως είμαι σίγουρος ότι δεν κατάλαβα τι εννοείς :)

 

Πάντως αλγοριθμικά δεν έχω ασχοληθεί καθόλου, έχω κρατήσει αυτούσιο το approach του φίλου tifosi που έκανε το αρχικό ποστ. Προφανώς είμαι ανοιχτός για οποιαδήποτε βελτίωση.

 

@pmav99: Δεν εννοούσα πως η διαφορά των μοντέλων είναι στο που γίνεται η επίλυση των διενέξεων, αλλά ότι προτιμώ την επίλυση να την κάνουν οι contributors αντί για τον maintainer.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

@pmav99: Δεν εννοούσα πως η διαφορά των μοντέλων είναι στο που γίνεται η επίλυση των διενέξεων, αλλά ότι προτιμώ την επίλυση να την κάνουν οι contributors αντί για τον maintainer

Όπως είπε και ο pmav αυτό γίνεται σε όλα τα μοντέλα. Και στο σενάριο του ενός repo πάλι ο contributor θα την κάνει απλά κερδίζεις ότι δεν έχεις πολλά repo με πολλά remotes (origin, upstream, dev1, dev2, κτλ) για να παρακολουθείς την ανάπτυξη.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

EDIT:

 

Για το GUI, εννοούσα αυτό που είχαμε γράψει σε προηγούμενα ποστ, αλλά το τρέχον tui να μείνει ως fallback ως έχει (δλδ με το gameloop στην main()).

 

Δηλαδή, χοντρά-χοντρα κάπως έτσι...

 

int main( void )
{
   init bla bla;

   if ( (ui = new_ui(GTK)) ) {
      ui.play();
   }
   else {
      περίπου όπως είναι τώρα.
   }
}
Δεν το έχω πολυ-σκεφτεί ακόμα.

 

Όπως είπε και ο pmav αυτό γίνεται σε όλα τα μοντέλα. Και στο σενάριο του ενός repo πάλι ο contributor θα την κάνει απλά κερδίζεις ότι δεν έχεις πολλά repo με πολλά remotes (origin, upstream, dev1, dev2, κτλ) για να παρακολουθείς την ανάπτυξη.

Εγώ το έγραψα επειδή σε ένα από τα ποστς που εξηγούσες είχες γράψει πως μπορεί να τα κάνει κι ο maintainer, αλλά είναι καλύτερα να τα κάνουν οι contributors επειδή ξέρουν τον κώδικα που έχουν γράψει.

 

EDIT:

 

Προστέθηκαν επιτέλους πλήρη σχόλια στον κώδικα, στο github.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δεν είμαι σίγουρος ότι κατάλαβα. Βασικά, αν το ψάξεις θα δεις πως υπάρχουν παραλλαγές που δεν ισχύει το 2^n... π.χ.: http://louhuang.com/2048-numberwang/.

Ναι προφανώς υπάρχουν, όπως και οι δύο που ανέφερα. Οι οποίες μάλιστα δεν έχουν καν σχέση με αριθμούς. Αν δεις δηλαδή την ευρύτερη εικόνα, έχεις ένα παιχνίδι όπου:

  • τα tiles παίρνουν κάποιες τιμές
  • οι οποίες μετασχηματίζονται με κάποιο αλγόριθμο (όταν βρεθούν δύο ομότιμα κολλητά κλπ κλπ)
  • και οι οποίες γίνονται map με κάποιον τρόπο σε κάποια αναπαράσταση για τον χρήστη.

Το 2048 είναι ένα μόνο instance αυτού του παιχνιδιού, όπου η τελική απεικόνιση (βλ. #3) γίνεται χρησιμοποιώντας δυνάμεις του 2. Στην pokemon έκδοση χρησιμοποιούνται εικόνες pokemon, στην color χρώματα και στην numberwang κάτι άλλο αρκετά insane για να με αποθαρρύνει να το ψάξω παραπάνω... :P

 

Έχοντας αυτά υπ' όψιν, νομίζω ότι δεν υπάρχει λόγος να το κρατήσεις 2ν-bound, τη στιγμή που μπορεί να γενικευτεί τόσο πολύ.

 

Τώρα, ως προς τις επιδόσεις, δεν το "μέτρησα" με το ai (θα πρέπει να φτιάξω ένα ai πρώτα :P) αλλά θεωρητικά, όταν έχεις να υπολογίσεις ένα μεγάλο tree/graph από game states, άλλο είναι να κάνεις +=var[expr] και άλλο ++. Όχι ότι είναι απαραίτητα κίνητρο για να αλλάξεις τη σχεδίασή σου, αλλά δες το σαν bonus. :)

 

Για τα replays ομως είμαι σίγουρος ότι δεν κατάλαβα τι εννοείς :)

Αν στο save έχω δυνάμεις του 2, για να κάνω replay σε έναν replayer που χρησιμοποιεί άλλη απεικόνιση, θα πρέπει να μετασχηματίσω τα values σε κάτι άλλο. Αν όμως έχω {0,1,2,3,...11} αυτά μπορώ να τα απεικονίσω άνετα τόσο με 2ν όσο και με οτιδήποτε άλλο (από χρώματα μέχρι ήχους!)

 

Έπιασα να φτιάξω έναν naive replayer (που λέγαμε παραπάνω) σε javascript, και θα τον ανεβάσω κάπου να φαίνεται πριν φτιάξω αντίστοιχο branch. Θα προσπαθήσω να δείξω εκεί, στην πράξη, τι εννοώ.

 

Για το GUI, εννοούσα αυτό που είχαμε γράψει σε προηγούμενα ποστ, αλλά το τρέχον tui να μείνει ως fallback ως έχει (δλδ με το gameloop στην main()).

 

Δηλαδή, χοντρά-χοντρα κάπως έτσι...

 

int main( void )
{
   init bla bla;

   if ( (ui = new_ui(GTK)) ) {
      ui.play();
   }
   else {
      περίπου όπως είναι τώρα.
   }
}

Αχά. Τώρα το 'πιασα.

 

Προστέθηκαν επιτέλους πλήρη σχόλια στον κώδικα, στο github.

 

Merged! ;)

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Είμαι τελείως πτώμα τώρα για να σκεφτώ καθαρά (βασικά θα την πέσω για νάνι σε λίγο, γιατί δεν με βλέπω να την παλεύω άλλο).

 

Μου άρεσε που θα κάνεις replay viewer... ανυπομονώ να τον δω (έχω καταλάβει τι λες)

 

Πάντως, όπως είναι τώρα τα replay-files δεν χρειάζεται να κάνεις κανένα calculation στην αναπαραγωγή. Εμφανίζεις ότι ακριβώς περιέχει το replay-file... δλδ τα περιεχόμενά του είναι ακριβές dump όλων των raw τιμών. Τις διαβάζεις και τις εμφανίζεις απευθείας ως έχουν.

 

EDIT:

 

Για να είμαι ακριβής, για το replay δεν χρειάζονται όλα τα data του αρχείου.

 

α) Κάνεις skip την 1η γραμμή

 

β) από τη 2η γραμμή διαβάζεις το 1ο νούμερο, που σου λέει πόσες γραμμές να κάνεις ακόμα skip (η 2η γραμμή του αρχείου είναι το top-node της undo stack, όπου το 1ο νούμερο σου λέει πόσα nodes έχει η undo stack)

 

γ) μόλις τελειώσει η undo-stack, ξεκινάει η redo-stack... κι αυτή την κάνεις skip με την μεθοδολογία του βήματος β) ... να συμπληρώσω όμως πως αν κάποια από τις στοίβες είναι κενή, τότε στο αρχείο εμφανίζεται μονάχα μια γραμμή, που λεει: "NULL:\r\n"

 

δ) όταν τελειώσει και η redo-stack, αρχίζουν οι γραμμές που χρειάζεσαι για το replay. Η 1η από αυτές έχει meta-data (delay, total moves κι ένα άλλο που δεν το θέλεις) κι αμέσως μετά και μέχρι το τέλος του αρχείου είναι τα nodes της repaly-stack.

 

EDIT 2:

 

Προσπαθώ να τα εξηγήσω και με σχόλια μέσα στον κώδικα. Π.χ. εδώ (αλλά κι αλλού).

 

Ότι απορία υπάρχει, γράψε την εδώ.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Εδώ λες

 

* NOTE: For a 4x4 board, the sentinel-value is 2048, but for
* a 5x5 board the sentinel-value is 16384, etc. See the
* enumerated definitions _VAL_SENTINEL_X at the top of
* this file.
Εδώ διαβάζω κάτι λάθος ή οι τιμές δεν συμβαδίζουν με την περιγραφή ?

 

enum {
_VAL_SENTINEL_4 = 2048, /* for 4x4 board (default) */
_VAL_SENTINEL_5 = 65536, /* for 5x5 board */
_VAL_SENTINEL_6 = 2048, /* for 6x6 board */
_VAL_SENTINEL_8 = 16384 /* for 8x8 board */
};
  • Like 1
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα

  • Δημιουργία νέου...