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

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


johnny.tifosi

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

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

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

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

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

Γιατί όχι πολλά gui με ποικιλλόμορφα skins το καθένα;;; :lol:

Παιδιά, αν παρασύρομαι να μου το πείτε, έτσι; Μην ντρέπεστε!

 

Πέρα από την πλάκα, συνεχίζω να μην έχω κοιτάξει επαρκώς τον κώδικα, αλλά παρατήρησα ότι υπάρχει πχ το tui.h και το tui.c.

 

Θεωρώ ότι θα πρέπει να υπάρχει ένα ui.h, αρκετά generic για να περιέχει τις δομές και functions που η engine περιμένει από το εκάστοτε ui να υλοποιήσει. Από κεί κι έπειτα, θα υλοποιούνται uis, κατά προτίμηση σε ξεχωριστά subdirectories το καθένα, γράφοντας μόνο το αντίστοιχο *ui.c (και το υπάρχον tui.c θα πρέπει να αποκτήσει το δικό του subdirectory).

 

Με τα κατάλληλα targets στο makefile, θα μπορεί να επιλέγεται κάθε φορά το επιθυμητό (g)ui.

To tui_skin.c χρησιμοποιείται αποκλειστικά και μόνο από το tui.c (το οποίο tui.c με τη σειρά του χρησιμοποιείται αποκλειστικά και μόνο από το main.c). Θα μπορούσα τα tui_skin.c και tui.c να τα είχα συμπτύξει σε ένα αρχείο (π.χ. στο tui.c) και να μην υπήρχε καθόλου public interface για το tui_skin. Τα χώρισα όμως σε 2 αρχεία, για να είναι πιο maintainable.

 

Η λογική λοιπόν ήταν (είναι βασικά) οτιδήποτε σχετικό με ui (δηλαδή με i/o) να βρίσκεται στο tui.c, και να βγάζει public μονάχα ότι είναι απολύτως απαραίτητο. Ακόμα και τα strings του ui τα έχω "κρύψει" από το main.c, ώστε να είναι όλα μαζεμένα μονάχα στο tui.c.

 

Για παράδειγμα, αντί να γράφω ας πούμε στο main.c

printfxy( x,y, fg, bg, "%s", "GAME OVER! Watch replay (y/)? ");
if ( 'y' = tui_sys_getkey() ) {
    ...
}

έχω tui_ συνάρτηση ειδικά για το input και το output της συγκεκριμένης περίπτωσης, την int tui_draw_iobar_prompt_watchreplay(), οπότε στο main.c γράφω:

if ( 'y' == tolower( tui_draw_iobar_prompt_watchreplay(tui)) ) {
    ...
}
Για αυτό έγραφα και νωρίτερα στον φίλο fuzzy, πως δεν είναι δύσκολο να αντικατασταθεί το τωρινό tui του game με οποιοδήποτε gui. Ο constructor του tui object ήδη δέχεται ως ορίσματα ότι χρειάζεται να ξέρει από το core game, δηλαδή την τρέχουσα gamestate, και το moves-history.

 

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

 

Δυο μόνο εξαιρέσεις υπάρχουν, όταν γίνεται resize το board στο main game (το board είναι "κρυμμένο" μέσα στην gamestate), τότε η realloc() μπορεί να το πάει σε άλλη θέση στη μνήμη, οπότε μένει εκτεθειμένος ο αντίστοιχος εσωτερικός δείκτης του tui. Σε αυτή λοιπόν την περίπτωση, όταν ο manager (το main.c δηλαδή εδώ πέρα) κάνει resize το board, πρέπει μετά να καλέσει και την int tui_update_board_reference( Tui *tui, Board *board ); για να ενημερωθεί ο εσωτερικός δείκτης του tui object. Αυτό ακριβώς κάνει λοιπόν η συνάρτηση void _do_new_variant(), στο main.c, που είναι υπεύθυνη για το resizing του board. Το κάνει resize στη γραμμή 328, κάνει μετά ότι έχει να κάνει, και στη γραμμή 339 καλεί την tui_update_board_reference() για να ενημερώσει το tui.

 

Η 2η περίπτωση, που είναι αντίστοιχης λογικής, έχει να κάνει με το MovesHistory, και την συνάρτηση tui_update_mvhist_reference(). Επειδή το MovesHistory struct έχει πολύ πράμα μέσα του, όταν φορτώνω ένα replay-file για να απολοποιήσω τον κώδικα, στην σχετική συνάρτηση _do_replay_load() (στο main.c) φορτώνω το replay-file σε ένα νέο, tmp MovesHistory object, και κατόπιν κάνω free το τρέχον και το βάζω να δείχνει στο tmp. Επειδή όμως έτσι γίνεται invalidate ο αντίστοιχος εσωτερικός δείκτης του tui object, η συνάρτηση αυτή καλεί και την tui_update_mvhist_reference() και την tui_update_board_reference() στις γραμμές 626 & 627, αντίστοιχα, για να ενημερωθεί το tui object.

 

That's it. Με αυτές τις 2 εξαιρέσεις που θέλουν λίγη προσοχή, το tui βλέπει ότι θέλει ανά πάσα στιγμή.

 

Από εκεί και πέρα όμως, το να φτιαχτεί ένα γενικό, κοινό ui abstraction που να καλύπτει και tui's (text ui's) και gui's (graphic ui's) είναι νομίζω too much, γιατί δεν είναι κι εύκολο. Χώρια ότι το τωρινό tui ΔΕΝ είναι event-driven, ενώ όλα τα GUI είναι.

 

Αυτό που μου φαίνεται πιο βατό, είναι να φτιαχτεί ένα κοινό abstraction για διάφορα gui's (δηλαδή με event-driven Λογική), αλλά νομίζω πως ο χρόνος που θα επενδυθεί για κάτι τέτοιο θα ήταν καλύτερα να αξιοποιηθεί χρησιμοποιώντας και εμπλουτίζοντας με περισσότερα features ένα μόνο gui. Για να κάνουμε δικό μας gui abstraction πρέπει να υλοποιήσουμε δικιά μας event queue, δικό μας event messaging system, δικά μας widgets, κλπ, κλπ... και κατόπιν να προσαρμόζουμε πάνω του τα διάφορα gui... νομίζω δε αξίζει (εκτός αν υπάρχει άλλος τρόπος που δεν μου έρχεται τώρα).

 

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

 

ΥΓ. Τελικά το flash-ing δεν είναι τόσο απλό (λόγω του decoupling που έχω κάνει). Επειδή θέλω να αποφύγω να βάλω μέσα στο Board struct πληροφορίες που έχουν να κάνουν με το tui (αυτό αναιρεί όλη την προσπάθεια για decoupling) μια λύση που μου έρχεται είναι η board_generate_ntiles() να επιστρέφει τις θέσεις των auto-generated tiles, τα οποία όμως ανάλογα με το variant του game μπορεί να είναι περισσότερα του 1ος (π.χ. στο 6x6 variant δημιουργούνται 2 tiles μετά από κάθε κίνηση).

 

Άρα θέλουμε δυναμικό πίνακα, τον οποίον μετά με κάποιον τρόπο να τον περνάμε στο tui (είτε απευθείας ως όρισμα σε κάποια από τις μεθόδους του, είτε να το βάζουμε π.χ. στο gamestate και να τον τραβάει από εκεί μέσω το εσωτερικού δείκτη το tui).

 

Αυτό με τη σειρά του θα πρέπει να μπει και στα replay-files, για να μπορούν να γίνονται flash τα generated tiles και κατά την παρακολούθηση των replays.

 

Με λίγα λόγια, θα το αφήσω όπως είναι προς το παρόν :P Αν φυσικά υπάρχει καμιά ιδέα για πιο απλή υλοποίηση, πολύ ευχαρίστως :)

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

Για ιδέες ίσως να μπορείτε να κοιτάξετε και αυτό (δεν το έχω εγκαταστήσει)

https://github.com/bfontaine/term2048

Αχά, ώστε υπάρχει ανταγωνισμός! :lol:

Του έριξα μια πολύ γρήγορη ματιά, αλλά νομίζω πως μάλλον εκείνοι πρέπει να δουν το δικό μας για να πάρουν ιδέες :P

 

Πέρα από την πλάκα, το συγκεκριμένο δείχνει (βασικά είναι) πάρα πολύ απλοϊκό συγκριτικά με το δικό μας. Ποιο ακριβώς κομμάτι του θώρησες πως μπορεί να μας δώσει ιδέες;

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

όπως είπα δεν το εγκατέστησα. Ούτε και το δικό σου το έχω κάνει compile :P

Αύριο με τον κηδεμόνα σου! :lol:

 

Μμ δεν ειχε παιξει τα άλλα modes στα οποία δεν χωράει border. Ισως αν "εβαφες" απλά το tile με το νούμερο στα νέα tiles. Ή αν είχαν καποιο delay στο spawn, μερικα ms μετά την κίνηση του χρήστη.

Έχω θέμα τελικά με το decoupling (το περιέγραψα συνοπτικά στο χτεσινό ποστ). Θέλω να αποφύγω να βάλω μέσα στο struct Board πληροφορίες που αφορούν αποκλειστικά το tui.

 

Θα μπορούσα δηλαδή να βάλω για παράδειγμα ένα isgenerated flag μέσα στο struct _tile, το οποίο θα το θέτει σε 1 (true) η board_generate_ntiles() και το tui να το κοιτάει πριν κάνει draw το tile, κι αν είναι 1 (true) να το ζωγραφίζει με κάποιο εφέ (flash, delay, κλπ) και κατόπιν να του μηδενίζει το flag (false).

 

Αυτό είναι ο πιο γρήγορος & εύκολος τρόπος που μπορώ να σκεφτώ αυτή τη στιγμή, αλλά imho χαλάει όλο το concept του decoupling (επίσης μεγαλώνει αχρείαστα πολύ το μέγεθος των replay-files, αφού στο grid εκτός από το value θα πρέπει να σώζεται και το isgenerated flag του κάθε tile).

 

Ένας πιο... "decoupling friendly" τρόπος είναι νομίζω αυτός που περιέγραψα χτες. Δηλαδή, η συνάρτηση board_generate_ntiles() να δημιουργεί δυναμικά και να επιστρέφει έναν πίνακα με τις θέσεις των auto-generated tiles, οπότε o manager (το main.c στη δική μας περίπτωση) να μπορεί μετά να τον προωθήσει περαιτέρω αυτόν τον πίνακα.

 

Π.χ. είτε απευθείας ως όρισμα στις συναρτήσεις tui_draw_board() και tui_redraw() ή να περνάει τον πίνακα σε κάποιον σχετικό δείκτη ας πούμε της τρέχουσας game-state (μάλλον πάλι γίνεται compromise το decoupling έτσι), και το tui να το βλέπει μέσω του εσωτερικού του tui->gamestate δείκτη.

 

Σε κάθε περίπτωση, για να μπορεί να αναπαραχθεί το εφέ και στα replays, θα πρέπει η σχετική πληροφορία (είτε είναι embed μέσα στα tiles ως boolean flag, είτε ως δυναμικός πίνακας θέσεων μέσα στο gamestate) να καταγράφεται και μέσα στα αρχεία των replays.

 

Επίσης σε κάθε περίπτωση, χρειάζεται να προστεθούν αντίστοιχες μέθοδοι πρόσβασης στη νέα αυτή πληροφορία (ή στο gs.c ή στο board.c, ανάλογα πως θα το υλοποιήσουμε) και φυσικά θέλουν ανάλογες μετατροπές και οι αντίστοιχες ρουτίνες serialization/deserialization.

 

Τέλος, το γεγονός πως ανάλογα το variant του game μπορεί να δημιουργούνται περισσότερα του ενός tiles μετά από κάθε κίνηση, δημιουργεί μια ακόμα επιπλοκή: μιας και δουλεύουμε με ένα μόνο thread, τα auto-generated tiles θα εμφανίζοντα σειριακά, το ένα μετά το άλλο.

 

Δηλαδή, στην 6x6 παραλλαγή του game θα πρέπει μετά από κάθε κίνησή μας να περιμένουμε τα 2 tiles να εμφανιστούν το ένα μετά το άλλο και όχι ταυτόχρονα (για περίπου τον ίδιο λόγο + ότι δεν είναι event-driven το tui δεν μπορούμε να σταματήσουμε το auto-play των replays).

 

Και μιας που είπα auto-play των replays, το flashing-delay εκεί λογικά θα πρέπει να είναι μικρότερο συγκριτικά με το real-time play (γιατί ήδη σπάει νεύρα που δεν γίνεται stop, αν έχει και flashing-delays θα κάνει 4πλάσια ώρα να τελειώσει).

 

Γενικώς δηλαδή, ότι κι αν κάνω, νομίζω πως είναι πολλή έξτρα δουλειά για κάτι που μάλλον δεν είναι και τόσο κρίσιμο.

 

Νομίζω πως είναι πιο φρόνιμο αυτό το functionality να προστεθεί όταν θα φτιαχτεί GUI. Αν είστε πολλοί που το θεωρείτε κρίσιμο feature, να προσπαθήσω να το φτιάξω ... θα χάσετε παλαιότερα replay σας όμως :P

 

Πάντως το επόμενο που θέλω να κάνω είναι στα replays να εμφανίζει κάτω-δεξιά όχι μόνο την προηγούμενη κίνηση του ταμπλό, αλλά και την επόμενη (τα UP/DOWN/LEFT/RIGHT εννοώ).

 

EDIT:

 

On a 2nd thought, η διαχείριση της αποθηκευμένης σε replay-file πληροφορίας για το πότε ένα tile πρέπει να ζωγραφιστεί με εφέ ή όχι, δεν βολεύει να γίνει με isgenerated flag... γιατί τα replay-files δεν χρησιμοποιούν την board_generate_ntiles() συνάρτηση. Μάλλον λοιπόν βολεύει καλύτερα έτσι κι αλλιώς να γίνει με έναν δυναμικό πίνακα των θέσεων που έχουν μέσα στον πίνακα τα tiles που πρέπει να ζωγραφιστούν με εφέ, ο οποίος θα αποθηκεύεται στο εκάστοτε τρέχον game-state (τα replays έτσι κι αλλιώς, όπως και οι undo/redo στοίβες, ουσιαστικά κρατάνε πλήρη στιγμιότυπα των game-states).

 

EDIT2:

 

Βλακείες γραφώ στο παραπάνω που έσβησα. Δεν θα έχουν πρόβλημα τα replays αν υπάρχει αποθηκευμένο isgenerated flag σε κάθε tile μαζί με το value του.

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

ωραίο θα ήταν να υπάρχει επιλογή όταν ξεκινάει για CLI/GUI...αλλά δεν ξέρω κατά πόσο είναι εύκολο στον υπάρχοντα κώδικα να προστεθεί το GUI, δηλαδή πόσο resuable-extensible είναι ο κώδικας με το τρόπο που είναι δομημένος.

Επίσης ωραίο θα ήταν να είναι platform independent οπότε να παίζει σε Win/OSX/Linux...Ίσως κάτι σε TK/Python (TKinter) για το GUI ή GTK+...

Ο πιο αποτελεσματικός τρόπος να γίνει αυτό είναι να χωριστεί τελείως το game engine μέρος του παιχνιδιού με το UI (να γίνουν ξεχωριστά processes) και να μιλάνε μεταξύ τους με κάποιο τρόπο (named pipe καλό για αρχή) σύμφωνα με κάποιο αυθαίρετα επιλεγμένο line protocol, ακριβώς όπως συμβαίνει με το UCI.

 

Δεν έκατσα να το σκεφτώ αλλά λογικά μ' αυτό τον τρόπο το replay functionality έρχεται "for free": ένα replay είναι απλά το stream δεδομένων που επικοινωνήθηκαν ανάμεσα σε engine και UI κατά τη διάρκεια του παιχνιδιού.

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

Ο πιο αποτελεσματικός τρόπος να γίνει αυτό είναι να χωριστεί τελείως το game engine μέρος του παιχνιδιού με το UI (να γίνουν ξεχωριστά processes) και να μιλάνε μεταξύ τους με κάποιο τρόπο (named pipe καλό για αρχή) σύμφωνα με κάποιο αυθαίρετα επιλεγμένο line protocol, ακριβώς όπως συμβαίνει με το UCI.

Δεν είναι ανάγκη να είναι ξεχωριστά processes, εκτός αν θέλεις η engine να εξυπηρετεί πολλά games ταυτόχρονα. Νομίζω κάτι τέτοιο θα ήταν μεγάλο βήμα αυτή τη στιγμή. Όχι ότι είναι αδύνατο, αλλά θέλει αρκετή παραπάνω δουλειά.

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

Ξεχωριστά processes => max flexibility (γράψε αν θες interface σε VB.NET με WPF).

 

Δεν έχω δει κώδικα αλλά είναι ψιλοπροφανές ότι ο διαχωρισμός δεν είναι ρεαλιστικός τουλάχιστον σε πρώτη φάση. Όμως το είπα ως βέλτιστη λύση, όχι ως ευκολότερη.

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

Και τα 2 αυτά που έχετε προτείνει θέλουν πολύ έξτρα δουλειά, και νομίζω δεν αξίζει να επενδυθεί τόσος χρόνος και κόπος για το συγκεκριμένο πρότζεκτ.

 

Νομίζω όμως πως είναι εφικτή μια ενδιάμεση λύση (τώρα μου ήρθε, οπότε μπορεί να κάνω και λάθος). Οι (de)serialization ρουτίνες υπάρχουν ήδη, οπότε μπορούν να μπουν σε κάθε iteration του game-loop και να περνάνε το output τους ως input στο ui. Κάπως έτσι...

 

int main( void )
{
    ...
    init_game( gs, mvhist );
    ...
    ui = init_gui();
    if ( NULL == ui ) {
        ui = init_tui();
        if ( NULL == tui ) {
            goto cleanup_and_exit_failure;
        }
    }

    /* game loop */
    for (; {
        serial1 = gs_to_text( gs );
        serial2 = mvhist_to_text( mvhist );

        ui_draw( ui, serial1, serial2 );

        free( serial1 );
        free( serial2 );

        idcmd = ui_get_user_command( ui );
        update_game( idcmd, gs, mvhist );

    }
    ...
}
Εναλλακτικά τα serials μπορεί να μπαίνουν σε file σε κάθε iteration, το οποίο θα διαβάζεται από το ui (εννοείται πως τα tui & gui θα κάνουν deserialize τα serial1 & serial2).

 

To ui μπορεί να είναι ένας void pointer που θα συγκεκριμενοποιείται μέσα στα tui και gui. Μόνο η ui_get_user_command() χρειάζεται να abstract-οποιηθεί για multiple gui/tui's.

 

Επαναλαμβάνω όμως, πως για το συγκεκριμένο πρότζεκτ δεν νομίζω πως αξίζει να γίνει κάτι τέτοιο. Ένα x-platform gui θεωρώ πως είναι παραπάνω από αρκετό, και η υπόλοιπη "προγραμματιστική ενέργεια" να αναλωθεί σε άλλα συστατικά (π.χ. AI).

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

Επειδή μ' έχεις βάλει λίγο στην πρίζα, καθόμουν χθες το βράδυ και έφτιαχνα ένα παράδειγμα (όχι τίποτα σπουδαίο, απλά για να "οπτικοποιήσω" αυτά που είπα παραπάνω).

 

Είτε ακολουθήσουμε κάτι τέτοιο είτε κάτι άλλο, το απόγευμα θα προσπαθήσω να το ποστάρω if only για να υπάρχει.

 

Με το AI, αν βρεις το χρόνο κάποια στιγμή να επεκταθείς σχετικά με το πως το φαντάζεσαι, προσωπικά με ενδιαφέρει.

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

Ρε συ, αν το φτιάξεις εννοείται πως θα το χρησιμοποιήσουμε! Πλάκα κάνεις; Εννοείται!

 

Δεν το έχω σκεφτεί το AI, αλλά σε γενικές γραμμές φαντάζομαι πως σε κάθε gamestate θα δημιουργεί ένα game-tree με τις επόμενες κινήσεις για πιθανά σενάρια, αξιολογώντας τες με κάποιο heuristic (μπορεί να μπει και όριο στο ύψος του tree, αν π.χ. βάλουμε 2-3 επίπεδα αυθεντίας), θα κόβει όσες είναι αδύνατες (alpha-pruning) και κατόπιν θα επιστρέφει σε ποιο direction προτείνει να παιχτεί το ταμπλό.

 

Είχα δώσει κι ένα link από stackoverflow, που έχει έτοιμες διάφορες προτάσεις/υλοποιήσεις AI για το game.

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

Όπως είπα και πριν, θέλοντας να "οπτικοποιήσω" το επιχείρημα με τα πολλαπλά uis, έφτιαξα ένα (ας το πούμε) "proof of concept".

 

Δεν είναι τίποτα σπουδαίο, θέλοντας να κρατήσω τον κώδικα μικρό σκέφτηκα να φτιάξω ένα mockup του "Πέτρα Ψαλίδι Χαρτί". Περιλαμβάνει τρία "διαφορετικά" uis, δύο text και ένα gtk.

 

Τα βασικά:

rps.c:

#include "ui.h"

int main(int argc,char *argv[])
    {
    return play(the_engine(),argc,argv);
    }

engine.h:

typedef struct engine
    {
    int human_points;
    int ai_points;
    }
    engine;

engine *the_engine();

int ai_move(int human_move);

engine.c:

#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "engine.h"

engine e=
    {
    human_points:0,
    ai_points:0
    };

engine *the_engine()
    {
    srand(time(NULL));
    return &e;
    }

int ai_move(int human_move)
    {
    char *moves="rps";
    int mymove=rand()%3;
    int yourmove=strchr(moves,human_move)-moves;
    if(mymove!=yourmove)
        {
        if(mymove==(yourmove+1)%3)
            e.ai_points++;
        else
            e.human_points++;
        }
    return moves[mymove];
    }

Το UI abstraction:

Τώρα υπάρχει το ui.h που τα "δένει" όλα:

#include "engine.h"

int play(engine *eng,int argc,char *argv[]);

...και τρία διαφορετικά UIs.

ui1/ui.c:

#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>

#include "../ui.h"

int mygetch ( void )
    {
  int ch;
  struct termios oldt, newt;
  tcgetattr ( STDIN_FILENO, &oldt );
  newt = oldt;
  newt.c_lflag &= ~( ICANON | ECHO );
  tcsetattr ( STDIN_FILENO, TCSANOW, &newt );
  ch = getchar();
  tcsetattr ( STDIN_FILENO, TCSANOW, &oldt );
  return ch;
    }
    
char *move_str(int move)
    {
    switch(move)
        {
        case 'p': return "Paper";
        case 's': return "Scissors";
        case 'r': return "Rock";
        }
    return "Invalid";
    }
    
int play(engine *eng,int argc,char *argv[])
    {
    printf("Playing with text ui #1...\n");
    int in;
    while(1)
        {
        printf("(r)ock, (p)aper, (s)cissors or (q)uit: ");
        in=mygetch();
        printf("%c\n",in);
        if(in=='q') break;
        if(!strchr("rps",in))
            printf("%s\n",move_str(in));
        else
            printf("%s vs %s\n",move_str(in),move_str(ai_move(in)));
        }
    printf("You scored %d and I scored %d.\n",eng->human_points,eng->ai_points);
    return 0;
    }

ui2/ui.c:

#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>

#include "../ui.h"

int mygetch ( void )
    {
  int ch;
  struct termios oldt, newt;
  tcgetattr ( STDIN_FILENO, &oldt );
  newt = oldt;
  newt.c_lflag &= ~( ICANON | ECHO );
  tcsetattr ( STDIN_FILENO, TCSANOW, &newt );
  ch = getchar();
  tcsetattr ( STDIN_FILENO, TCSANOW, &oldt );
  return ch;
    }
    
int play(engine *eng,int argc,char *argv[])
    {
    printf("Playing with ui #2...\n");
    printf("Whoever gets 10 points first, wins.\n");
    int in;
    while((eng->human_points<10)&&(eng->ai_points<10))
        {
        printf("(r)ock, (p)aper, (s)cissors? : ");
        if(strchr("rps",in=mygetch()))
            {
            printf("%c - %c\n",in,ai_move(in));
            printf("You: %d, me: %d\n",eng->human_points,eng->ai_points);
            }
        else
            printf("!\n");
        }
    printf("%s\n",eng->ai_points==10?"I won!!!":"You won... :(");
    return 0;
    }

gui/ui.c:

#include <gtk/gtk.h>

#include "../ui.h"

int play(engine *eng,int argc,char *argv[])
    {
  gtk_init(&argc, &argv);

  GtkWidget *window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
 
    g_signal_connect_swapped(G_OBJECT(window),"destroy",G_CALLBACK(gtk_main_quit),NULL);

  gtk_window_set_default_size(GTK_WINDOW(window),400,300);
  gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER);
  gtk_window_set_title(GTK_WINDOW(window),"Rock-Paper-Scissors");
    
  GtkWidget *frame=gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window),frame);

  GtkWidget *label=gtk_label_new("(Not Implemented Yet...)");
  gtk_fixed_put(GTK_FIXED(frame),label,10,10);

    /* Long story short, implement ypur gui here */
    
  gtk_widget_show_all(window);
  gtk_main();

  return 0;
    }

Έτσι, μπορούμε να έχουμε διάφορα uis που αναπτύσσονται ανεξάρτητα, χωρίς να επηρεάζει το ένα το άλλο, και όποτε ολοκληρώνεται καθένα, θα ενσωματώνεται σε ένα makefile σαν κι αυτό:

CC=gcc
CFLAGS=-c -Wall
GTKFLAGS=`pkg-config --cflags gtk+-2.0`
GTKLIBS=`pkg-config --libs gtk+-2.0`

all: rps_ui1 rps_ui2 rps_gui

# UIs

ui1.o: ui1/ui.c
    $(CC) $(CFLAGS) ui1/ui.c -o ui1.o
    
ui2.o: ui2/ui.c
    $(CC) $(CFLAGS) ui2/ui.c -o ui2.o
    
gui.o: gui/ui.c
    $(CC) $(CFLAGS) $(GTKFLAGS) gui/ui.c -o gui.o
    
# engine

engine.o: engine.c
    $(CC) $(CFLAGS) engine.c
    
# exes

rps.o: rps.c
    $(CC) $(CFLAGS) rps.c

rps_ui1: engine.o ui1.o rps.o
    $(CC) engine.o ui1.o rps.o -o rps_ui1
    
rps_ui2: engine.o ui2.o rps.o
    $(CC) engine.o ui2.o rps.o -o rps_ui2
    
rps_gui: engine.o gui.o rps.o
    $(CC) engine.o gui.o rps.o -o rps_gui $(GTKLIBS)
    
# cleanup etc
    
clean:
    rm -f *.o rps_*
    
.PHONY: clean
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Άρα εννοούσες άλλο πράγμα από αυτό που εννοούσα εγώ λέγοντας abstract ui :)

 

Σε τόσο απλό παράδειγμα, είναι οκ αλλά σε πιο σύνθετες καταστάσεις δεν είμαι σίγουρος κατά πόσο θα βολέψει. Μπορεί και να βολεύει, δεν ξέρω (μια δοκιμή θα μας πείσει :) )

 

Btw, μια λίγο πιο OOP προσέγγιση στο παράδειγμα που δίνεις θα μπορούσε να είναι κάπως σαν την παρακάτω:

 

Download: rock_paper.zip

 

main.c

 

 

#include <stdio.h>
#include <stdlib.h>

#include "engine.h"
#include "ui.h"

int main( void )
{
	Engine *engine = NULL;
	Ui *ui = NULL;
	int uiid = UIID_NONE;

	engine = new_engine();
	if ( NULL == engine ) {
		goto exit_failure;
	}

#if defined( USE_GTK )
	uiid = UIID_GTK;
#elif defined( USE_TUI1 )
	uiid = UIID_TUI1;
#endif

	ui = new_ui( uiid, engine);
	if ( NULL == ui ) {
		goto exit_failure;
	}

	ui_play( ui );

	ui_free( ui );
	engine_free( engine );
	exit( EXIT_SUCCESS );

exit_failure:
	fputs( "*** Fatal internal error! Bye..", stderr );

	ui_free( ui );
	engine_free( engine );
	exit( EXIT_FAILURE );
}

 

engine.h

 

 

#ifndef ENGINE_H
#define ENGINE_H

typedef struct _Engine Engine;  /* forward declaration of opaque data-type */

#ifndef ENGINE_C
extern Engine *new_engine( void );
extern Engine *engine_free( Engine *e );
extern int    engine_ai_move( Engine *e, int human_move );
extern int    engine_get_human_points( Engine *e );
extern int    engine_get_ai_points( Engine *e );
#endif

#endif

 

engine.c

 

 

#define ENGINE_C

#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "engine.h"

struct _Engine {
	int human_points;
	int ai_points;
};

/* -------------------------------------------------- */
Engine *new_engine( void )
{
	Engine *e = calloc(1, sizeof(*e) );
	if ( NULL == e ) {
		return NULL;
	}

	srand( time(NULL) );
	return e;
}

/* -------------------------------------------------- */
Engine *engine_free( Engine *e )
{
	if ( e ) {
		free( e );
	}
	return NULL;
}

/* -------------------------------------------------- */
int engine_ai_move( Engine *e, int human_move )
{
	char *moves = "rps";
	int  mymove = rand() % 3;
	int  yourmove = strchr( moves, human_move ) - moves;
	if ( mymove != yourmove )
	{
		if ( mymove == (yourmove+1) % 3 ) {
			e->ai_points++;
		}
		else {
			e->human_points++;
		}
	}
	return moves[mymove];
}

/* -------------------------------------------------- */
int engine_get_human_points( Engine *e )
{
	return e->human_points;
}

/* -------------------------------------------------- */
int engine_get_ai_points( Engine *e )
{
	return e->ai_points;
}

 

ui.h

 

 

#ifndef UI_H
#define UI_H

#include "engine.h"

typedef struct _Ui Ui;  /* forward declaration of opaque data-type */

enum {
	UIID_NONE = 0,
	UIID_TUI1,
	UIID_GTK
};

#ifndef UI_C
extern Ui *new_ui( int uiid, Engine *e );
extern Ui *ui_free( Ui *ui );
extern int ui_play( Ui *ui );

#endif

#endif

 

ui.c

 

 

#define UI_C

#include <stdio.h>
#include <stdlib.h>

#include "ui.h"
#include "ui_gtk/ui_gtk.h"
#include "ui_tui1/ui_tui1.h"

struct _Ui {
	int    id;
	Engine *engine;
	int (*play)(Engine *e);   /* function pointer */
};

/* -------------------------------------------------- */
int ui_default_play( Engine *e )
{
	puts( "I'm the default ui_play routine" );
	getchar();
	return 0;
}

/* -------------------------------------------------- */
Ui *new_ui( int uiid, Engine *e )
{
	Ui *ui = NULL;

	if ( NULL == e || NULL == e ) {
		return NULL;
	}

	ui = calloc( 1, sizeof(*ui) );
	if ( NULL == ui ) {
		return NULL;
	}

	switch( uiid ) {
		case UIID_GTK:
			ui->play = ui_gtk_play;
			break;

		case UIID_TUI1:
			ui->play = ui_tui1_play;
			break;
		default:
			ui->play = ui_default_play;
			break;
	}

	ui->engine = e;
	return ui;
}

/* -------------------------------------------------- */
Ui *ui_free( Ui *ui )
{
	if ( ui ) {
		free( ui );
	}
	return NULL;
}

/* -------------------------------------------------- */
int ui_play( Ui *ui )
{
	if ( NULL == ui ) {
		return 0;
	}

	return (ui->play)( ui->engine );
}

 

ui_gtk/ui_gtk.h

 

 

#ifndef UI_GTK_H
#define UI_GTK_H

#include "../engine.h"

#ifndef UI_GTK_C
extern int ui_gtk_play( Engine *e );
#endif

#endif

 

ui_gtk/ui_gtk.c

 

 

#define UI_GTK_C

#include <gtk/gtk.h>
#include "ui_gtk.h"

/* -------------------------------------------------- */
int ui_gtk_play( Engine *e  )
{
	gtk_init(NULL, NULL);

	GtkWidget *window = gtk_window_new( GTK_WINDOW_TOPLEVEL) ;

	g_signal_connect_swapped(
		G_OBJECT( window ),
		"destroy",
		G_CALLBACK( gtk_main_quit ),
		NULL
		);

	gtk_window_set_default_size( GTK_WINDOW(window), 400, 300 );
	gtk_window_set_position( GTK_WINDOW(window), GTK_WIN_POS_CENTER );
	gtk_window_set_title( GTK_WINDOW(window), "Rock-Paper-Scissors" );

	GtkWidget *frame = gtk_fixed_new();
	gtk_container_add( GTK_CONTAINER(window), frame );

	GtkWidget *label = gtk_label_new( "(Not Implemented Yet...)" );
	gtk_fixed_put( GTK_FIXED(frame), label, 10,10 );

	/* Long story short, implement your gui here */

	gtk_widget_show_all( window );
	gtk_main();

	return 0;
}

 

ui_tui1/ui_tui1.h

 

 

#ifndef UI_TUI1_H
#define UI_TUI1_H

#include "../engine.h"

#ifndef UI_TUI1_C
extern int ui_tui1_play( Engine *e );
#endif

#endif

 

ui_tui1/ui_tui1.c

 

 

#define UI_TUI1_C

#include <stdio.h>
#include <string.h>

#include "ui_tui1.h"

/* -------------------------------------------------- */
int ui_tui1_play( Engine *eng )
{
	int humpts = 0, aipts = 0;

	printf("Playing with ui #2...\n");
	printf("Whoever gets 10 points first, wins.\n");
	int in;
	while( humpts < 10 && aipts < 10 )
	{

		printf("(r)ock, (p)aper, (s)cissors? : ");
		if ( strchr("rps", in=getchar()) )
		{
			printf( "%c - %c\n", in, engine_ai_move(eng,in) );
			humpts = engine_get_human_points(eng);
			aipts  = engine_get_ai_points(eng);
			printf( "You: %d, me: %d\n", humpts, aipts );
		}
		else {
		    printf("!\n");
		}
	}
	printf( "%s\n", aipts == 10 ? "I won!!!":"You won... :(" );
	return 0;
}

 

Και ουσιαστικά παίζεις με το UIID_XXX του ui που θέλεις να κάνεις enable (το οποίο μπορεί να επιλεχθεί και με predefined directive... π.χ. του έχω βάλει παραπάνω τα USE_GTK και USE_TU1.

 

Να βάλουμε ένα γενετικό ή ενα aco για το ΑΙ;

Δηλαδή;

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

Βλέπω το προχώρησες! :)

 

Ναι, κάτι τέτοια σκεφτόμουν κι εγώ σαν "επόμενο βήμα". Ειδικότερα, τις συναρτήσεις που διαβάζουν τα points, για να κρύψεις το engine structure από το gui, και ξεχωριστή δομή για τα ui ώστε (πιθανό σενάριο) να συνυπάρχουν πολλά μαζί και να διαλέγει ο χρήστης στο startup.

 

Άρα εννοούσες άλλο πράγμα από αυτό που εννοούσα εγώ λέγοντας abstract ui :)

 

Για λέγε, για λέγε!

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

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

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

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

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

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

Σύνδεση

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

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

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